Verschil tussen Stub, Mock en Spy in het Spock Framework

1. Overzicht

In deze tutorial we gaan de verschillen tussen bespreken Bespotten, Stomp, en Spion in het Spock-framework. We laten zien wat het framework biedt met betrekking tot op interactie gebaseerd testen.

Spock is een toetsingskader voor Java en Groovy dat helpt bij het automatiseren van het handmatig testen van de softwareapplicatie. Het introduceert zijn eigen spotjes, stubs en spionnen, en wordt geleverd met ingebouwde mogelijkheden voor tests die normaal gesproken extra bibliotheken vereisen.

Eerst laten we zien wanneer we stubs moeten gebruiken. Dan gaan we door middel van spot. Uiteindelijk zullen we de recent geïntroduceerde Spion.

2. Maven afhankelijkheden

Laten we voordat we beginnen onze Maven-afhankelijkheden toevoegen:

 org.spockframework spock-core 1.3-RC1-groovy-2.5 test org.codehaus.groovy groovy-all 2.4.7 test 

Merk op dat we de 1.3-RC1-groovy-2.5 versie van Spock. Spion wordt geïntroduceerd in de volgende stabiele versie van Spock Framework. Direct Spion is beschikbaar in de eerste release candidate voor versie 1.3.

Bekijk ons ​​inleidende artikel over testen met Groovy en Spock voor een samenvatting van de basisstructuur van een Spock-test.

3. Testen op basis van interactie

Testen op basis van interactie is een techniek die ons helpt het gedrag van objecten te testen - specifiek, hoe ze met elkaar omgaan. Hiervoor kunnen we dummy-implementaties gebruiken die mocks en stubs worden genoemd.

We zouden natuurlijk heel gemakkelijk onze eigen implementaties van spotjes en stubs kunnen schrijven. Het probleem doet zich voor wanneer de hoeveelheid van onze productiecode toeneemt. Het handmatig schrijven en onderhouden van deze code wordt moeilijk. Daarom gebruiken we mocking-frameworks, die een beknopte manier bieden om de verwachte interacties kort te beschrijven. Spock heeft ingebouwde ondersteuning voor bespotten, stoten en spioneren.

Zoals de meeste Java-bibliotheken, gebruikt Spock de dynamische JDK-proxy voor mocking-interfaces en Byte Buddy- of cglib-proxy's voor mocking-klassen. Het creëert mock-implementaties tijdens runtime.

Java heeft al veel verschillende en volwassen bibliotheken voor het bespotten van klassen en interfaces. Hoewel elk van deze kan worden gebruikt in Spock, er is nog steeds een belangrijke reden waarom we Spock-spot, stubs en spionnen zouden moeten gebruiken. Door deze allemaal aan Spock te introduceren, we kunnen alle mogelijkheden van Groovy benutten om onze tests leesbaarder, gemakkelijker te schrijven en zeker leuker te maken!

4. Stubbing Method-oproepen

Soms, in unit-tests moeten we een dummy-gedrag van de klas bieden. Dit kan een client zijn voor een externe service, of een klasse die toegang geeft tot de database. Deze techniek staat bekend als stubbing.

Een stub is een controleerbare vervanging van een bestaande klasse afhankelijkheid in onze geteste code. Dit is handig voor het maken van een methodeaanroep die op een bepaalde manier reageert. Als we stub gebruiken, maakt het niet uit hoe vaak een methode wordt aangeroepen. In plaats daarvan willen we zeggen: retourneer deze waarde wanneer deze wordt aangeroepen met deze gegevens.

Laten we naar de voorbeeldcode gaan met bedrijfslogica.

4.1. Code wordt getest

Laten we een modelklasse maken met de naam Item:

public class Item {private final String id; private laatste String naam; // standard constructor, getters, equals}

We moeten het is gelijk aan (Object andere) methode om onze beweringen te laten werken. Spock zal gebruiken is gelijk aan tijdens beweringen wanneer we het dubbele gelijkteken (==) gebruiken:

nieuw item ('1', 'naam') == nieuw item ('1', 'naam')

Laten we nu een interface maken ItemProvider met één methode:

openbare interface ItemProvider {List getItems (List itemIds); }

We hebben ook een klas nodig die wordt getest. We voegen een ItemProvider als afhankelijkheid in ItemService:

openbare klasse ItemService {privé laatste ItemProvider itemProvider; openbare ItemService (ItemProvider itemProvider) {this.itemProvider = itemProvider; } Lijst getAllItemsSortedByName (Lijst itemIds) {Lijstitems = itemProvider.getItems (itemIds); retourneer items.stream () .sorted (Comparator.comparing (Item :: getName)) .collect (Collectors.toList ()); }}

We willen dat onze code afhankelijk is van een abstractie in plaats van een specifieke implementatie. Daarom gebruiken we een interface. Dit kan veel verschillende implementaties hebben. We kunnen bijvoorbeeld items uit een bestand lezen, een HTTP-client naar een externe service maken of de gegevens uit een database lezen.

In deze code, we zullen de externe afhankelijkheid moeten stubben, omdat we alleen onze logica in het getAllItemsSortedByName methode.

4.2. Een stompobject gebruiken in de te testen code

Laten we het ItemService object in het opstelling() methode met behulp van een Stomp voor de ItemProvider afhankelijkheid:

ItemProvider itemProvider ItemService itemService def setup () {itemProvider = Stub (ItemProvider) itemService = nieuwe ItemService (itemProvider)}

Nu, laten we maken itemProvider retourneert een lijst met items bij elke aanroep met het specifieke argument:

itemProvider.getItems (['aanbieding-id', 'aanbieding-id-2']) >> [nieuw item ('aanbieding-id-2', 'Zname'), nieuw item ('aanbieding-id', 'Naam ')]

We gebruiken >> operand om de methode te stubben. De getItems methode retourneert altijd een lijst met twee items wanneer deze wordt aangeroepen met [‘Aanbieding-id ',‘ aanbieding-id-2'] lijst. [] is een Groovy snelkoppeling voor het maken van lijsten.

Hier is de hele testmethode:

def 'moet items teruggeven gesorteerd op naam' () {given: def ids = ['offer-id', 'offer-id-2'] itemProvider.getItems (ids) >> [nieuw item ('offer-id-2 ',' Zname '), nieuw item (' offer-id ',' Aname ')] wanneer: Lijstitems = itemService.getAllItemsSortedByName (ids) dan: items.collect {it.name} == [' Aname ',' Zname ']}

Er zijn nog veel meer stubbing-mogelijkheden die we kunnen gebruiken, zoals: het gebruik van beperkingen voor het matchen van argumenten, het gebruik van reeksen waarden in stubs, het definiëren van ander gedrag onder bepaalde omstandigheden en het koppelen van methode-reacties.

5. Spottende klassenmethoden

Laten we het nu hebben over het bespotten van klassen of interfaces in Spock.

Soms, we zouden graag willen weten of een methode van het afhankelijke object werd aangeroepen met gespecificeerde argumenten. We willen ons concentreren op het gedrag van de objecten en onderzoeken hoe ze met elkaar omgaan door te kijken naar de methodeaanroepen.Mocking is een beschrijving van de verplichte interactie tussen de objecten in de testklasse.

We zullen de interacties testen in de voorbeeldcode die we hieronder hebben beschreven.

5.1. Code met interactie

Voor een eenvoudig voorbeeld gaan we items in de database opslaan. Na succes willen we een evenement op de message broker publiceren over nieuwe items in ons systeem.

De voorbeeldberichtmakelaar is een RabbitMQ of Kafka, dus in het algemeen beschrijven we ons contract:

openbare interface EventPublisher {ongeldig publiceren (String addedOfferId); }

Onze testmethode zal niet-lege items in de database opslaan en vervolgens de gebeurtenis publiceren. Het opslaan van een item in de database is niet relevant in ons voorbeeld, dus we zullen gewoon een opmerking plaatsen:

void saveItems (List itemIds) {List notEmptyOfferIds = itemIds.stream () .filter (itemId ->! itemId.isEmpty ()) .collect (Collectors.toList ()); // opslaan in database notEmptyOfferIds.forEach (eventPublisher :: publish); }

5.2. Interactie met bespotte objecten verifiëren

Laten we nu de interactie in onze code testen.

Eerste, we moeten bespotten EventPublisher in onze opstelling() methode. Dus eigenlijk maken we een nieuw instantieveld en bespotten het door Mock (klasse) functie:

class ItemServiceTest breidt specificatie {ItemProvider itemProvider ItemService itemService EventPublisher eventPublisher def setup () {itemProvider = Stub (ItemProvider) eventPublisher = Mock (EventPublisher) itemService = new ItemService (itemProvider, eventPublisher)}

Nu kunnen we onze testmethode schrijven. We passeren 3 snaren: ”,‘ a ', ‘b' en we verwachten dat onze eventPublisher publiceert 2 evenementen met 'a' en 'b' strings:

def 'moet evenementen publiceren over nieuwe niet-lege opgeslagen aanbiedingen' () {gegeven: def offerIds = ['', 'a', 'b'] wanneer: itemService.saveItems (offerIds) dan: 1 * eventPublisher.publish (' a ') 1 * eventPublisher.publish (' b ')}

Laten we onze bewering in de finale eens nader bekijken dan sectie:

1 * eventPublisher.publish ('a')

Dat verwachten we itemService zal een eventPublisher.publish (tekenreeks) met 'a' als argument.

Bij stubbing hebben we het gehad over argumentbeperkingen. Dezelfde regels zijn van toepassing op spot. We kunnen dat verifiëren eventPublisher.publish (tekenreeks) werd twee keer aangeroepen met een niet-nul en niet-leeg argument:

2 * eventPublisher.publish ({it! = Null &&! It.isEmpty ()})

5.3. Mocking en stubbing combineren

In Spock, een Bespotten kan zich hetzelfde gedragen als een Stomp. We kunnen dus tegen bespotte objecten zeggen dat het, voor een bepaalde methodeaanroep, de gegeven gegevens moet retourneren.

Laten we een ItemProvider met Mock (klasse) en maak een nieuw ItemService:

gegeven: itemProvider = Mock (ItemProvider) itemProvider.getItems (['item-id']) >> [new Item ('item-id', 'name')] itemService = new ItemService (itemProvider, eventPublisher) wanneer: def items = itemService.getAllItemsSortedByName (['item-id']) dan: items == [nieuw item ('item-id', 'naam')] 

We kunnen het stubbing herschrijven van de gegeven sectie:

1 * itemProvider.getItems (['item-id']) >> [nieuw item ('item-id', 'naam')]

Dus over het algemeen zegt deze regel: itemProvider.getItems wordt een keer gebeld met ['item ID'] argument en retourneer de gegeven array.

We weten al dat spotprenten zich hetzelfde kunnen gedragen als stompjes. Alle regels met betrekking tot argumentbeperkingen, het retourneren van meerdere waarden en bijwerkingen zijn ook van toepassing op Bespotten.

6. Spionageklassen in Spock

Spionnen bieden de mogelijkheid om een ​​bestaand object in te pakken. Dit betekent dat we kunnen meeluisteren naar het gesprek tussen de beller en het echte object, maar het oorspronkelijke objectgedrag behouden. Eigenlijk, Spion delegeert methodeaanroepen naar het originele object.

In contrast met Bespotten en Stomp, kunnen we geen Spion op een interface. Het omhult een echt object, dus bovendien moeten we argumenten voor de constructor doorgeven. Anders wordt de standaardconstructor van het type aangeroepen.

6.1. Code wordt getest

Laten we een eenvoudige implementatie maken voor EventPublisher. LoggingEventPublisher zal in de console de id van elk toegevoegd item afdrukken. Hier is de implementatie van de interfacemethode:

@Override public void publish (String addedOfferId) {System.out.println ("Ik heb gepubliceerd:" + addedOfferId); }

6.2. Testen met Spion

We creëren spionnen op dezelfde manier als spot en stubs, door de Spy (klasse) methode. LoggingEventPublisher heeft geen andere klassenafhankelijkheden, dus we hoeven geen constructorargs door te geven:

eventPublisher = Spy (LoggingEventPublisher)

Laten we nu onze spion testen. We hebben een nieuw exemplaar nodig van ItemService met ons bespioneerde object:

gegeven: eventPublisher = Spy (LoggingEventPublisher) itemService = nieuwe ItemService (itemProvider, eventPublisher) wanneer: itemService.saveItems (['item-id']) dan: 1 * eventPublisher.publish ('item-id')

We hebben geverifieerd dat het eventPublisher.publish methode is slechts één keer aangeroepen. Bovendien is de methodeaanroep doorgegeven aan het echte object, dus we zullen de uitvoer zien van println in de console:

Ik heb gepubliceerd: item-id

Merk op dat wanneer we stub gebruiken op een methode van Spion, dan zal het de real object-methode niet aanroepen. Over het algemeen moeten we het gebruik van spionnen vermijden. Als we het moeten doen, moeten we de code misschien volgens specificatie herschikken?

7. Goede unit-tests

Laten we eindigen met een korte samenvatting van hoe het gebruik van bespotte objecten onze tests verbetert:

  • we creëren deterministische testsuites
  • we zullen geen bijwerkingen hebben
  • onze unit-tests zullen erg snel zijn
  • we kunnen ons concentreren op de logica in een enkele Java-klasse
  • onze tests zijn onafhankelijk van de omgeving

8. Conclusie

In dit artikel hebben we spionnen, bespottingen en stubs in Groovy grondig beschreven. Kennis over dit onderwerp zal onze tests sneller, betrouwbaarder en gemakkelijker leesbaar maken.

De implementatie van al onze voorbeelden is te vinden in het Github-project.