Forskellen mellem stub, mock og spy i Spock Framework

1. Oversigt

I denne vejledning vi skal diskutere forskellene imellem Mock, Stubog Spion i Spock-rammen. Vi illustrerer, hvad rammen tilbyder i forhold til interaktionsbaseret test.

Spock er en testramme for Java og Groovy der hjælper med at automatisere processen med manuel test af softwareapplikationen. Det introducerer sine egne mocks, stubs og spioner og leveres med indbyggede funktioner til tests, der normalt kræver yderligere biblioteker.

Først illustrerer vi, hvornår vi skal bruge stubbe. Så gennemgår vi hån. I sidste ende beskriver vi det nyligt introducerede Spion.

2. Maven-afhængigheder

Lad os tilføje vores Maven-afhængigheder, inden vi starter:

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

Bemærk, at vi har brug for 1.3-RC1-groovy-2.5 version af Spock. Spion vil blive introduceret i den næste stabile version af Spock Framework. Lige nu Spion er tilgængelig i kandidat til første udgivelse til version 1.3.

For en oversigt over den grundlæggende struktur for en Spock-test, se vores indledende artikel om test med Groovy og Spock.

3. Interaktionsbaseret test

Interaktionsbaseret test er en teknik, der hjælper os med at teste objekternes adfærd - specifikt hvordan de interagerer med hinanden. Til dette kan vi bruge dummy implementeringer kaldet mocks and stubs.

Selvfølgelig kunne vi bestemt meget let skrive vores egne implementeringer af mocks og stubs. Problemet vises, når mængden af ​​vores produktionskode vokser. Det bliver svært at skrive og vedligeholde denne kode i hånden. Det er derfor, vi bruger spottende rammer, som giver en kortfattet måde at kort beskrive forventede interaktioner på. Spock har indbygget støtte til mocking, stubbing og spionage.

Som de fleste Java-biblioteker bruger Spock JDK dynamisk proxy til spottende grænseflader og Byte Buddy eller cglib-proxyer til spottende klasser. Det skaber mock implementeringer ved kørsel.

Java har allerede mange forskellige og modne biblioteker til spottende klasser og grænseflader. Selvom hver af disse kan bruges i Spock, der er stadig en væsentlig grund til, at vi skal bruge Spock-mocks, stubs og spioner. Ved at introducere alle disse til Spock, vi kan udnytte alle Groovys muligheder for at gøre vores test mere læsbare, lettere at skrive og bestemt sjovere!

4. Opkald til stubmetode

Sommetider, i enhedstest er vi nødt til at give klassen en dummy-opførsel. Dette kan være en klient til en ekstern tjeneste eller en klasse, der giver adgang til databasen. Denne teknik er kendt som stubbing.

En stub er en kontrollerbar erstatning for en eksisterende klasse afhængighed i vores testede kode. Dette er nyttigt til at foretage et metodekald, der reagerer på en bestemt måde. Når vi bruger stub, er vi ligeglad med, hvor mange gange en metode vil blive påberåbt. I stedet for vil vi bare sige: returner denne værdi, når der kaldes med disse data.

Lad os gå til eksempelkoden med forretningslogik.

4.1. Kode under test

Lad os oprette en modelklasse kaldet Vare:

public class Item {private final String id; privat endelig Navn på streng; // standard konstruktør, getters, lig}

Vi er nødt til at tilsidesætte er lig med (Objekt andet) metode til at få vores påstande til at fungere. Spock vil bruge lige med under påstande, når vi bruger det dobbelte ligetegn (==):

nyt emne ('1', 'navn') == nyt emne ('1', 'navn')

Lad os nu oprette en grænseflade ItemProvider med en metode:

offentlig grænseflade ItemProvider {List getItems (List itemIds); }

Vi har også brug for en klasse, der vil blive testet. Vi tilføjer en ItemProvider som en afhængighed i ItemService:

public class ItemService {private final ItemProvider itemProvider; public ItemService (ItemProvider itemProvider) {this.itemProvider = itemProvider; } Liste getAllItemsSortedByName (List itemIds) {List items = itemProvider.getItems (itemIds); returner items.stream () .sorted (Comparator.comparing (Item :: getName)) .collect (Collectors.toList ()); }}

Vi ønsker, at vores kode afhænger af en abstraktion snarere end en specifik implementering. Derfor bruger vi en grænseflade. Dette kan have mange forskellige implementeringer. For eksempel kunne vi læse emner fra en fil, oprette en HTTP-klient til ekstern tjeneste eller læse dataene fra en database.

I denne kode vi bliver nødt til at stoppe den eksterne afhængighed, fordi vi kun vil teste vores logik indeholdt i getAllItemsSortedByName metode.

4.2. Brug af et stubbed objekt i koden under test

Lad os initialisere ItemService objekt i Opsætning() metode ved hjælp af en Stub til Vareleverandør afhængighed:

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

Nu, Lad os lave itemProvider returnere en liste med varer på hver påkaldelse med det specifikke argument:

itemProvider.getItems (['offer-id', 'offer-id-2']) >> [new Item ('offer-id-2', 'Zname'), new Item ('offer-id', 'Aname ')]

Vi bruger >> operand til at stoppe metoden. Det getItems metoden returnerer altid en liste med to emner, når der kaldes med [‘Offer-id ',‘ offer-id-2'] liste. [] er en Groovy genvej til oprettelse af lister.

Her er hele testmetoden:

def 'skal returnere varer sorteret efter navn' () {given: def ids = ['offer-id', 'offer-id-2'] itemProvider.getItems (ids) >> [new Item ('offer-id-2 ',' Zname '), nyt element (' offer-id ',' Aname ')] når: Listeelementer = itemService.getAllItemsSortedByName (ids) derefter: items.collect {it.name} == [' Aname ',' Zname ']}

Der er mange flere stubningsfunktioner, vi kan bruge, såsom: at bruge begrænsninger, der matcher argumenter, bruge værdisekvenser i stubs, definere forskellig opførsel under bestemte forhold og kædemetodesvar.

5. Spottklassemetoder

Lad os nu tale om hånende klasser eller grænseflader i Spock.

Sommetider, Vi vil gerne vide, om en metode til det afhængige objekt blev kaldt med specificerede argumenter. Vi ønsker at fokusere på objektenes opførsel og udforske, hvordan de interagerer ved at se på metoden kald.Mocking er en beskrivelse af obligatorisk interaktion mellem objekterne i testklassen.

Vi tester interaktionerne i den eksempelkode, vi har beskrevet nedenfor.

5.1. Kode med interaktion

For et simpelt eksempel vil vi gemme varer i databasen. Efter succes ønsker vi at offentliggøre en begivenhed på meddelelsesmægleren om nye varer i vores system.

Eksempelbeskedsmægleren er en RabbitMQ eller Kafka, så generelt beskriver vi bare vores kontrakt:

offentlig grænseflade EventPublisher {void publish (String addedOfferId); }

Vores testmetode gemmer ikke-tomme emner i databasen og offentliggør derefter begivenheden. At gemme element i databasen er irrelevant i vores eksempel, så vi vil bare kommentere:

ugyldige saveItems (List itemIds) {List notEmptyOfferIds = itemIds.stream () .filter (itemId ->! itemId.isEmpty ()) .collect (Collectors.toList ()); // gem i databasen notEmptyOfferIds.forEach (eventPublisher :: publish); }

5.2. Bekræftelse af interaktion med spottede objekter

Lad os nu teste interaktionen i vores kode.

Først, vi skal spotte EventPublisher i vores Opsætning() metode. Så grundlæggende opretter vi et nyt instansfelt og spotter det ved hjælp af Mock (klasse) fungere:

klasse ItemServiceTest udvider Specifikation {ItemProvider itemProvider ItemService itemService EventPublisher eventPublisher def setup () {itemProvider = Stub (ItemProvider) eventPublisher = Mock (EventPublisher) itemService = new ItemService (itemProvider, eventPublisher)}

Nu kan vi skrive vores testmetode. Vi passerer 3 strenge: ”,‘ a ’,‘ b 'og vi forventer, at vores eventPublisher vil offentliggøre 2 begivenheder med 'a' og 'b' strenge:

def 'skal offentliggøre begivenheder om nye ikke-tomme gemte tilbud' () {given: def offerIds = ['', 'a', 'b'] when: itemService.saveItems (offerIds) then: 1 * eventPublisher.publish (' a ') 1 * eventPublisher.publish (' b ')}

Lad os se nærmere på vores påstand i finalen derefter afsnit:

1 * eventPublisher.publish ('a')

Det forventer vi itemService vil kalde en eventPublisher.publish (String) med 'a' som argument.

I stubbing har vi talt om begrænsninger i argumenter. De samme regler gælder for mocks. Vi kan bekræfte det eventPublisher.publish (String) blev kaldt to gange med ethvert ikke-nul og ikke-tomt argument:

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

5.3. Kombination af hån og stubbing

I Spock, -en Mock kan opføre sig det samme som en Stub. Så vi kan sige til spottede objekter, at det for en given metodeopkald skal returnere de givne data.

Lad os tilsidesætte en Vareleverandør med Mock (klasse) og opret et nyt ItemService:

given: itemProvider = Mock (ItemProvider) itemProvider.getItems (['item-id']) >> [new Item ('item-id', 'name')] itemService = new ItemService (itemProvider, eventPublisher) when: def items = itemService.getAllItemsSortedByName (['item-id']) derefter: items == [new Item ('item-id', 'name')] 

Vi kan omskrive stubningen fra givet afsnit:

1 * itemProvider.getItems (['item-id']) >> [new Item ('item-id', 'name')]

Så generelt siger denne linje: itemProvider.getItems kaldes en gang med [‘Item-‘id '] argument og returner givet array.

Vi ved allerede, at mocks kan opføre sig som stubs. Alle regler vedrørende argumentbegrænsninger, returnering af flere værdier og bivirkninger gælder også for Mock.

6. Spioneringskurser i Spock

Spioner giver mulighed for at pakke et eksisterende objekt. Dette betyder, at vi kan lytte ind i samtalen mellem den, der ringer op og det rigtige objekt, men beholde den oprindelige objektadfærd. I bund og grund, Spion delegerer metodeopkald til det oprindelige objekt.

I kontrast til Mock og Stub, vi kan ikke oprette en Spion på en grænseflade. Det indpakker et faktisk objekt, så derudover bliver vi nødt til at sende argumenter til konstruktøren. Ellers påkræves typens standardkonstruktør.

6.1. Kode under test

Lad os oprette en simpel implementering til EventPublisher. LoggingEventPublisher vil udskrive id'et for hvert tilføjet element i konsollen. Her er implementering af grænseflademetoden:

@Override public void publish (String addedOfferId) {System.out.println ("Jeg har offentliggjort:" + addedOfferId); }

6.2. Test med Spion

Vi skaber spioner på samme måde som mocks og stubs ved hjælp af Spy (klasse) metode. LoggingEventPublisher har ingen andre klasseafhængigheder, så vi behøver ikke bestå konstruktør args:

eventPublisher = Spy (LoggingEventPublisher)

Lad os nu teste vores spion. Vi har brug for en ny forekomst af ItemService med vores spionerede objekt:

givet: eventPublisher = Spy (LoggingEventPublisher) itemService = ny ItemService (itemProvider, eventPublisher) når: itemService.saveItems (['item-id']) derefter: 1 * eventPublisher.publish ('item-id')

Vi bekræftede, at eventPublisher.publish metoden blev kun kaldt en gang. Derudover blev metodekaldet videregivet til det rigtige objekt, så vi får vist output af println i konsollen:

Jeg har offentliggjort: vare-id

Bemærk, at når vi bruger stub på en metode til Spion, så kalder det ikke den rigtige objektmetode. Generelt bør vi undgå at bruge spioner. Hvis vi skal gøre det, skal vi måske omorganisere koden under specifikation?

7. Gode enhedstests

Lad os afslutte med en hurtig oversigt over, hvordan brugen af ​​spottede objekter forbedrer vores tests:

  • vi skaber deterministiske testpakker
  • vi har ingen bivirkninger
  • vores enhedstest vil være meget hurtige
  • vi kan fokusere på logikken i en enkelt Java-klasse
  • vores tests er uafhængige af miljøet

8. Konklusion

I denne artikel beskrev vi grundigt spioner, mocks og stubs i Groovy. Viden om dette emne vil gøre vores tests hurtigere, mere pålidelige og lettere at læse.

Implementeringen af ​​alle vores eksempler findes i Github-projektet.


$config[zx-auto] not found$config[zx-overlay] not found