Introduksjon til testing med Spock og Groovy

1. Introduksjon

I denne artikkelen ser vi på Spock, et Groovy-testrammeverk. Hovedsakelig har Spock som mål å være et kraftigere alternativ til den tradisjonelle JUnit-stakken, ved å utnytte Groovy-funksjonene.

Groovy er et JVM-basert språk som sømløst integreres med Java. I tillegg til interoperabilitet, tilbyr den flere språkkonsepter som å være en dynamisk, ha valgfrie typer og metaprogrammering.

Ved å bruke Groovy introduserer Spock nye og uttrykksfulle måter å teste Java-applikasjonene våre på, som ganske enkelt ikke er mulig i vanlig Java-kode. Vi vil utforske noen av Spocks konsepter på høyt nivå i løpet av denne artikkelen, med noen praktiske trinnvise eksempler.

2. Maven avhengighet

Før vi begynner, la oss legge til våre Maven-avhengigheter:

 org.spockframework spock-core 1.0-groovy-2.4 test org.codehaus.groovy groovy-all 2.4.7 test 

Vi har lagt til både Spock og Groovy som vi ville gjort med alle standardbiblioteker. Ettersom Groovy er et nytt JVM-språk, må vi imidlertid inkludere gmavenplus plugin for å kunne kompilere og kjøre den:

 org.codehaus.gmavenplus gmavenplus-plugin 1.5 kompilere testCompile 

Nå er vi klare til å skrive vår første Spock-test, som vil bli skrevet i Groovy-kode. Merk at vi bare bruker Groovy og Spock til testformål, og det er derfor disse avhengighetene testes.

3. Struktur av en Spock Test

3.1. Spesifikasjoner og funksjoner

Når vi skriver testene våre i Groovy, må vi legge dem til src / test / groovy katalog, i stedet for src / test / java. La oss lage vår første test i denne katalogen, og gi den navnet Spesifikasjon. Grovaktig:

klasse FirstSpecification utvider spesifikasjonen {}

Merk at vi utvider Spesifikasjon grensesnitt. Hver Spock-klasse må utvide dette for å gjøre rammeverket tilgjengelig for det. Det gjør det slik at vi kan implementere vår første trekk:

def "en pluss en skal være lik to" () {forvent: 1 + 1 == 2}

Før du forklarer koden, er det også verdt å merke seg at i Spock, det vi refererer til som en trekk er noe synonymt med det vi ser på som en test i JUnit. Så når vi refererer til a trekk vi refererer faktisk til a test.

La oss nå analysere vår trekk. Ved å gjøre dette, bør vi umiddelbart kunne se noen forskjeller mellom det og Java.

Den første forskjellen er at navnet på funksjonsmetoden er skrevet som en vanlig streng. I JUnit ville vi hatt et metodenavn som bruker kamelskjerm eller understreker for å skille ordene, noe som ikke ville vært like uttrykksfulle eller menneskelig lesbare.

Den neste er at testkoden vår lever i en forventer blokkere. Vi vil dekke blokker nærmere i løpet av kort tid, men egentlig er de en logisk måte å dele opp de forskjellige trinnene i testene våre.

Til slutt innser vi at det ikke er påstander. Det er fordi påstanden er implisitt og går når uttalelsen vår er lik ekte og mislykkes når det er lik falsk. Igjen vil vi dekke påstander i flere detaljer om kort tid.

3.2. Blokker

Noen ganger når vi skriver JUnit en test, kan vi merke at det ikke er en uttrykksfull måte å dele den opp i deler. For eksempel, hvis vi fulgte atferdsdrevet utvikling, kan vi ende opp med å betegne gitt når da deler ved hjelp av kommentarer:

@Test offentlig ugyldighet givenTwoAndTwo_whenAdding_thenResultIsFour () {// Gitt int først = 2; int sekund = 4; // Når int resultat = 2 + 2; // Så assertTrue (resultat == 4)}

Spock adresserer dette problemet med blokker. Blokker er en Spock-innfødt måte å bryte opp fasene i testen vår ved hjelp av etiketter. De gir oss merkelapper for gitt når da og mer:

  1. Oppsett (Aliasert av gitt) - Her utfører vi alle oppsett som trengs før en test kjøres. Dette er en implisitt blokk, med kode som ikke er i noen blokk i det hele tatt blir en del av den
  2. Når - Det er her vi gir en stimulans til det som er under test. Med andre ord, der vi påkaller vår metode under test
  3. Deretter - Det er her påstandene hører hjemme. I Spock blir disse evaluert som vanlige boolske påstander, som vil bli dekket senere
  4. Forvent - Dette er en måte å utføre våre på stimulans og påstand innenfor samme blokk. Avhengig av hva vi finner mer uttrykksfulle, kan vi velge eller ikke bruke denne blokken
  5. Rydde opp - Her river vi ned eventuelle testavhengighetsressurser som ellers ville ligget igjen. For eksempel vil vi kanskje fjerne filer fra filsystemet eller fjerne testdata skrevet til en database

La oss prøve å implementere testen vår igjen, denne gangen med full bruk av blokker:

def "to pluss to skal være lik fire" () {gitt: int venstre = 2 int høyre = 2 når: int resultat = venstre + høyre deretter: resultat == 4}

Som vi kan se, hjelper blokker testen vår til å bli mer lesbar.

3.3. Utnytte Groovy-funksjoner for påstander

Innen deretter og forventer påstander, er påstander implisitte.

For det meste blir hvert utsagn evaluert og mislykkes deretter hvis det ikke er det ekte. Når du kobler dette til forskjellige Groovy-funksjoner, gjør det en god jobb med å fjerne behovet for et påstandsbibliotek. La oss prøve en liste påstand om å demonstrere dette:

def "Bør kunne fjerne fra listen" () {gitt: def list = [1, 2, 3, 4] når: list.remove (0) så: list == [2, 3, 4]}

Mens vi bare berører Groovy kort i denne artikkelen, er det verdt å forklare hva som skjer her.

For det første gir Groovy oss enklere måter å lage lister på. Vi kan bare erklære elementene våre med firkantede parenteser, og internt a liste vil bli instantiert.

For det andre, ettersom Groovy er dynamisk, kan vi bruke def som bare betyr at vi ikke deklarerer en type for våre variabler.

Til slutt, i sammenheng med å forenkle testen vår, er den mest nyttige funksjonen som vises, overbelastning av operatøren. Dette betyr at internt, i stedet for å lage en referansesammenligning som i Java, er lik() metoden vil bli påkalt for å sammenligne de to listene.

Det er også verdt å demonstrere hva som skjer når testen vår mislykkes. La oss få det til å bryte og deretter se hva som sendes ut til konsollen:

Betingelsen ikke oppfylt: liste == [1, 3, 4] | | | false [2, 3, 4] ved FirstSpecification.Skal kunne fjerne fra listen (FirstSpecification.groovy: 30)

Mens alt som skjer er å ringe er lik() på to lister er Spock intelligent nok til å utføre en oversikt over den sviktende påstanden, noe som gir oss nyttig informasjon for feilsøking.

3.4. Hevder unntak

Spock gir oss også en uttrykksfull måte å se etter unntak på. I JUnit kan noen av alternativene våre bruke en prøvefangst blokkere, erklære forventet øverst i testen, eller ved å bruke et tredjepartsbibliotek. Spocks opprinnelige påstander kommer med en måte å håndtere unntak ut av boksen:

def "Bør få en indeks utenfor grensene når du fjerner et ikke-eksisterende element" () {gitt: def list = [1, 2, 3, 4] når: list.remove (20) deretter: kastet (IndexOutOfBoundsException) -liste. størrelse () == 4}

Her har vi ikke hatt å introdusere et ekstra bibliotek. En annen fordel er at kastet () metoden vil hevde typen unntak, men ikke stoppe utførelsen av testen.

4. Datadrevet testing

4.1. Hva er en datadrevet testing?

I hovedsak, datadrevet testing er når vi tester den samme oppførselen flere ganger med forskjellige parametere og påstander. Et klassisk eksempel på dette vil være å teste en matematisk operasjon som å kvadratere et tall. Avhengig av de forskjellige permutasjonene av operander, vil resultatet være annerledes. I Java er begrepet vi kanskje er mer kjent med, parameterisert testing.

4.2. Implementering av en parametrisert test i Java

I noen sammenhenger er det verdt å implementere en parametrisert test ved hjelp av JUnit:

@RunWith (Parameterized.class) public class FibonacciTest {@Parameters public static Collection data () {return Arrays.asList (new Object [] [] {{1, 1}, {2, 4}, {3, 9}} ); } privat int input; privat int forventet; offentlig FibonacciTest (int input, int forventet) {this.input = input; dette.forventet = forventet; } @Test offentlig ugyldighetstest () {assertEquals (fExpected, Math.pow (3, 2)); }}

Som vi kan se, er det ganske mye ordlighetsgrad, og koden er ikke veldig lesbar. Vi har måttet lage et todimensjonalt objektarray som lever utenfor testen, og til og med et wrapperobjekt for å injisere de forskjellige testverdiene.

4.3. Bruke data i Spock

En enkel gevinst for Spock sammenlignet med JUnit er hvordan den rent implementerer parametrerte tester. Igjen, i Spock, er dette kjent som Datadrevet testing. La oss nå implementere den samme testen igjen, bare denne gangen bruker vi Spock med Datatabeller, som gir en langt mer praktisk måte å utføre en parameterisert test på:

def "tall til kraften til to" (int a, int b, int c) 4 3 

Som vi kan se, har vi bare en enkel og uttrykksfull datatabell som inneholder alle parametrene våre.

Dessuten hører den hjemme der den skal gjøre, ved siden av testen, og det er ingen kjeleplate. Testen er uttrykksfull, med et lesbart navn og ren forventer og hvor blokker for å bryte opp de logiske delene.

4.4. Når en datatabell mislykkes

Det er også verdt å se hva som skjer når testen vår mislykkes:

Tilstand ikke oppfylt: Math.pow (a, b) == c | | | | | 4,0 2 2 | 1 feil Forventet: 1 Faktisk: 4.0

Igjen, Spock gir oss en veldig informativ feilmelding. Vi kan se nøyaktig hvilken rad av datatabelen som forårsaket en feil, og hvorfor.

5. Hån

5.1. Hva er spott?

Mocking er en måte å endre oppførselen til en klasse som vår testede tjeneste samarbeider med. Det er en nyttig måte å være i stand til å teste forretningslogikk isolert av avhengighet.

Et klassisk eksempel på dette ville være å erstatte en klasse som ringer et nettverk med noe som rett og slett later til. For en mer inngående forklaring er det verdt å lese denne artikkelen.

5.2. Spott med Spock

Spock har sitt eget spottende rammeverk, og bruker interessante konsepter som er brakt til JVM av Groovy. La oss først starte en Håne:

PaymentGateway betalingGateway = Mock ()

I dette tilfellet blir typen av mock utledet av den variable typen. Ettersom Groovy er et dynamisk språk, kan vi også gi et typeargument, slik at vi ikke trenger å tilordne spottet vårt til en bestemt type:

def betalingGateway = Mock (PaymentGateway)

Nå, når vi kaller en metode på vår PaymentGateway håne, et standardrespons vil bli gitt, uten at en reell forekomst blir påkalt:

når: def resultat = betalingGateway.makePayment (12.99) så: resultat == false

Begrepet for dette er mild spott. Dette betyr at spotte metoder som ikke er definert, vil gi fornuftige standarder, i motsetning til å kaste et unntak. Dette er av design i Spock, for å gjøre narr og dermed tester mindre sprø.

5.3. Stubbemetode kaller på Håner

Vi kan også konfigurere metoder som blir påkalt mocken vår for å svare på forskjellige måter på forskjellige argumenter. La oss prøve å få vår PaymentGateway spotte å komme tilbake ekte når vi foretar en betaling på 20:

gitt: paymentGateway.makePayment (20) >> true når: def result = betalingGateway.makePayment (20) da: result == true

Det som er interessant her er hvordan Spock benytter seg av Groovys operatøroverbelastning for å stoppe metodeanrop. Med Java må vi kalle virkelige metoder, noe som uten tvil betyr at den resulterende koden er mer detaljert og potensielt mindre uttrykksfull.

La oss nå prøve noen flere typer stubbing.

Hvis vi sluttet å bry oss om metodeargumentet vårt og alltid ønsket å komme tilbake ekte, vi kunne bare bruke en understreking:

paymentGateway.makePayment (_) >> true

Hvis vi ønsket å veksle mellom forskjellige svar, kunne vi gi en liste som hvert element vil bli returnert i rekkefølge:

paymentGateway.makePayment (_) >>> [true, true, false, true]

Det er flere muligheter, og disse kan dekkes i en mer avansert fremtidig artikkel om hån.

5.4. Bekreftelse

En annen ting vi kanskje vil gjøre med mocks er å hevde at forskjellige metoder ble kalt på dem med forventede parametere. Med andre ord, vi burde verifisere interaksjoner med spottene våre.

Et typisk brukstilfelle for verifisering ville være hvis en metode på mocken vår hadde en tomrom returtype. I dette tilfellet, fordi det ikke er noe resultat for oss å operere, er det ingen utledet atferd for oss å teste via metoden som testes. Vanligvis, hvis noe ble returnert, kunne metoden som testes fungere på det, og resultatet av denne operasjonen ville være det vi hevder.

La oss prøve å bekrefte at en metode med en ugyldig returtype heter:

def "Skal bekreft varsel ble kalt" () {gitt: def notifier = Mock (Notifier) ​​når: notifier.notify ('foo') så: 1 * notifier.notify ('foo')} 

Spock utnytter Groovy-operatørens overbelastning igjen. Ved å multiplisere mocks-metoden vår med en, sier vi hvor mange ganger vi forventer at den har blitt kalt.

Hvis metoden vår ikke hadde blitt kalt i det hele tatt eller alternativt ikke hadde blitt kalt så mange ganger som vi spesifiserte, ville testen vår ikke ha gitt oss en informativ Spock-feilmelding. La oss bevise dette ved å forvente at det hadde blitt kalt to ganger:

2 * varsler. Varsle ('foo')

Etter dette, la oss se hvordan feilmeldingen ser ut. Vi vil som vanlig; det er ganske informativt:

For få påkallinger for: 2 * varsler. Varsle ('foo') (1 påkalling)

Akkurat som stubbing, kan vi også utføre løsere bekreftelsestilpasning. Hvis vi ikke brydde oss om hva metodeparameteren vår var, kunne vi bruke et understrek:

2 * varsler. Varsle (_)

Eller hvis vi ønsket å forsikre oss om at det ikke ble kalt med et bestemt argument, kunne vi bruke ikke-operatoren:

2 * varsler. Varsle (! 'Foo')

Igjen er det flere muligheter, som kan bli dekket i en fremtidig mer avansert artikkel.

6. Konklusjon

I denne artikkelen har vi gitt et raskt stykke gjennom testing med Spock.

Vi har demonstrert hvordan vi, ved å utnytte Groovy, kan gjøre testene våre mer uttrykksfulle enn den typiske JUnit-stakken. Vi har forklart strukturen til spesifikasjoner og funksjoner.

Og vi har vist hvor enkelt det er å utføre datadrevet testing, og også hvor hån og påstander er enkle via native Spock-funksjonalitet.

Implementeringen av disse eksemplene finner du på GitHub. Dette er et Maven-basert prosjekt, så det skal være enkelt å kjøre som det er.


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