Introduktion til testning med Spock og Groovy

1. Introduktion

I denne artikel ser vi på Spock, en Groovy-testramme. Hovedsageligt har Spock til formål at være et mere kraftfuldt alternativ til den traditionelle JUnit-stak ved at udnytte Groovy-funktioner.

Groovy er et JVM-baseret sprog, der problemfrit integreres med Java. Ud over interoperabilitet tilbyder den yderligere sprogkoncepter, såsom at være en dynamik, have valgfrie typer og metaprogrammering.

Ved at bruge Groovy introducerer Spock nye og udtryksfulde måder at teste vores Java-applikationer på, hvilket simpelthen ikke er muligt i almindelig Java-kode. Vi udforsker nogle af Spocks koncepter på højt niveau i løbet af denne artikel med nogle praktiske trin for trin eksempler.

2. Maven-afhængighed

Lad os tilføje vores Maven-afhængigheder, inden vi kommer i gang:

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

Vi har tilføjet både Spock og Groovy, som vi ville med ethvert standardbibliotek. Da Groovy er et nyt JVM-sprog, skal vi dog medtage gmavenplus plugin for at kunne kompilere og køre det:

 org.codehaus.gmavenplus gmavenplus-plugin 1.5 kompiler testCompile 

Nu er vi klar til at skrive vores første Spock-test, som vil blive skrevet i Groovy-kode. Bemærk, at vi kun bruger Groovy og Spock til testformål, og det er grunden til, at disse afhængigheder test-scoped.

3. Struktur af en Spock-test

3.1. Specifikationer og funktioner

Når vi skriver vores tests i Groovy, skal vi føje dem til src / test / groovy i stedet for src / test / java. Lad os oprette vores første test i denne mappe ved at navngive den Specifikation. Grovagtig:

klasse FirstSpecification udvider Specifikation {}

Bemærk, at vi udvider Specifikation interface. Hver Spock-klasse skal udvide dette for at gøre rammen tilgængelig for den. Det gør det, der giver os mulighed for at implementere vores første funktion:

def "en plus en skal være lig med to" () {forvent: 1 + 1 == 2}

Før du forklarer koden, er det også værd at bemærke, at det i Spock, hvad vi kalder en funktion er noget synonymt med det, vi ser som en prøve i JUnit. Så når vi henviser til en funktion vi henviser faktisk til en prøve.

Lad os nu analysere vores funktion. Dermed skal vi straks kunne se nogle forskelle mellem det og Java.

Den første forskel er, at funktionsmetodens navn er skrevet som en almindelig streng. I JUnit ville vi have haft et metodenavn, der bruger camelcase eller understregning til at adskille ordene, hvilket ikke ville have været så udtryksfuldt eller menneskeligt læsbart.

Den næste er, at vores testkode lever i en forventer blok. Vi vil snart dække blokke mere detaljeret, men i det væsentlige er de en logisk måde at opdele de forskellige trin i vores test på.

Endelig indser vi, at der ikke er nogen påstande. Det skyldes, at påstanden er implicit, passerer når vores udsagn er lig rigtigt og mislykkes, når det er lig falsk. Igen vil vi snart dække påstander i flere detaljer.

3.2. Blokke

Nogle gange når vi skriver JUnit en test, bemærker vi måske, at der ikke er en udtryksfuld måde at opdele den op i dele. For eksempel, hvis vi fulgte adfærdsdrevet udvikling, kan det ende med at betegne givet når da dele ved hjælp af kommentarer:

@Test offentlig ugyldighed givenTwoAndTwo_whenAdding_thenResultIsFour () {// Givet int først = 2; int sekund = 4; // When int result = 2 + 2; // Så assertTrue (resultat == 4)}

Spock løser dette problem med blokke. Blokke er en Spock-indfødt måde at opdele faserne i vores test ved hjælp af etiketter. De giver os etiketter til givet når da og mere:

  1. Opsætning (Aliaset af givet) - Her udfører vi den nødvendige opsætning, før en test køres. Dette er en implicit blok, hvor kode overhovedet ikke er en del af den
  2. Hvornår - Det er her, vi leverer en stimulus til det, der testes. Med andre ord, hvor vi påberåber os vores metode under test
  3. Derefter - Det er her påstandene hører hjemme. I Spock vurderes disse som almindelige boolske påstande, som vil blive dækket senere
  4. Forventer - Dette er en måde at udføre vores på stimulus og påstand inden for samme blok. Afhængigt af hvad vi finder mere udtryksfulde, vælger vi måske eller ikke at bruge denne blok
  5. Ryd op - Her nedbryder vi eventuelle testafhængighedsressourcer, som ellers ville blive efterladt. For eksempel vil vi muligvis fjerne filer fra filsystemet eller fjerne testdata, der er skrevet til en database

Lad os prøve at implementere vores test igen, denne gang ved fuld brug af blokke:

def "to plus to skal være lig med fire" () {givet: int venstre = 2 int højre = 2 når: int resultat = venstre + højre derefter: resultat == 4}

Som vi kan se, hjælper blokke vores test med at blive mere læsbar.

3.3. Udnyttelse af groovy-funktioner til påstande

Indenfor derefter og forventer blokeringer, påstande er implicitte.

For det meste evalueres hver erklæring og mislykkes derefter, hvis den ikke er det rigtigt. Når dette kombineres med forskellige Groovy-funktioner, gør det et godt stykke arbejde med at fjerne behovet for et påstandsbibliotek. Lad os prøve en liste påstand om at demonstrere dette:

def "Bør være i stand til at fjerne fra listen" () {given: def list = [1, 2, 3, 4] when: list.remove (0) then: list == [2, 3, 4]}

Mens vi kun berører kortvarigt Groovy i denne artikel, er det værd at forklare, hvad der sker her.

For det første giver Groovy os enklere måder at oprette lister på. Vi kan bare kunne erklære vores elementer med firkantede parenteser og internt en liste vil blive instantieret.

For det andet, da Groovy er dynamisk, kan vi bruge def hvilket bare betyder, at vi ikke erklærer en type for vores variabler.

Endelig, i forbindelse med at forenkle vores test, er den mest nyttige funktion, der demonstreres, operatøroverbelastning. Dette betyder, at internt, snarere end at foretage en sammenligningssammenligning som i Java, lige med() metode vil blive påberåbt for at sammenligne de to lister.

Det er også værd at demonstrere, hvad der sker, når vores test mislykkes. Lad os få det til at gå i stykker og derefter se, hvad der sendes til konsollen:

Betingelse ikke opfyldt: liste == [1, 3, 4] | | | false [2, 3, 4] ved FirstSpecification.Skal være i stand til at fjerne fra listen (FirstSpecification.groovy: 30)

Mens alt, hvad der foregår, ringer lige med() på to lister er Spock intelligent nok til at udføre en opdeling af den svigtende påstand, hvilket giver os nyttige oplysninger til fejlfinding.

3.4. Påståede undtagelser

Spock giver os også en udtryksfuld måde at kontrollere undtagelser på. I JUnit bruger nogle af vores muligheder muligvis en prøve-fangst blokere, erklære forventet øverst i vores test eller ved hjælp af et tredjepartsbibliotek. Spocks oprindelige påstande kommer med en måde at håndtere undtagelser ud af kassen:

def "Skal få et indeks uden for grænserne, når du fjerner et ikke-eksisterende element" () {given: def list = [1, 2, 3, 4] when: list.remove (20) then: throw (IndexOutOfBoundsException) list. størrelse () == 4}

Her har vi ikke været nødt til at introducere et ekstra bibliotek. En anden fordel er, at kastet () metoden vil hævde typen af ​​undtagelsen, men ikke standse udførelsen af ​​testen.

4. Datadrevet test

4.1. Hvad er en datadrevet test?

I det væsentlige datadrevet test er, når vi tester den samme adfærd flere gange med forskellige parametre og påstande. Et klassisk eksempel på dette ville være at teste en matematisk operation som at kvadrere et tal. Afhængigt af de forskellige permutationer af operander, vil resultatet være anderledes. I Java er det udtryk, vi måske er mere fortrolige med, parametreret test.

4.2. Implementering af en parametreret test i Java

I nogle sammenhænge er det værd at implementere en parametreret test ved hjælp af 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; this.expected = forventet; } @ Test offentlig ugyldig test () {assertEquals (fExpected, Math.pow (3, 2)); }}

Som vi kan se, er der en hel del detaljer, og koden er ikke særlig læsbar. Vi har været nødt til at oprette et todimensionelt objektarray, der lever uden for testen og endda et indpakningsobjekt til injektion af de forskellige testværdier.

4.3. Brug af data i Spock

En let gevinst for Spock sammenlignet med JUnit er, hvordan den rent implementerer parametriserede tests. Igen i Spock er dette kendt som Datadrevet test. Lad os nu implementere den samme test igen, kun denne gang bruger vi Spock med Datatabeller, som giver en langt mere bekvem måde at udføre en parametreret test på:

def "tal til magten af ​​to" (int a, int b, int c) 4 3 

Som vi kan se, har vi bare en ligetil og udtryksfuld datatabel, der indeholder alle vores parametre.

Det hører også til, hvor det skal gøre, sammen med testen, og der er ingen kedelplade. Testen er udtryksfuld, med et menneskeligt læsbart navn og ren forventer og hvor blokere for at opdele de logiske sektioner.

4.4. Når en datatabel mislykkes

Det er også værd at se, hvad der sker, når vores test mislykkes:

Betingelse ikke opfyldt: Math.pow (a, b) == c | | | | | 4,0 2 2 | 1 falsk Forventet: 1 Faktisk: 4.0

Igen giver Spock os en meget informativ fejlmeddelelse. Vi kan se nøjagtigt, hvilken række af vores datatabel, der forårsagede en fejl, og hvorfor.

5. Hånende

5.1. Hvad er latterliggørelse?

Mocking er en måde at ændre adfærden på en klasse, som vores service under test samarbejder med. Det er en nyttig måde at være i stand til at teste forretningslogik isoleret af dens afhængigheder.

Et klassisk eksempel på dette ville være at erstatte en klasse, der foretager et netværksopkald med noget, der simpelthen foregiver at. For en mere detaljeret forklaring er det værd at læse denne artikel.

5.2. Spott ved hjælp af Spock

Spock har sin egen mocking-ramme, der gør brug af interessante koncepter bragt til JVM af Groovy. Lad os først instantiere en Mock:

PaymentGateway betalingGateway = Mock ()

I dette tilfælde udledes typen af ​​vores mock af den variable type. Da Groovy er et dynamisk sprog, kan vi også give et typeargument, så vi ikke behøver at tildele vores mock til en bestemt type:

def betalingGateway = Mock (PaymentGateway)

Nu, når vi kalder en metode på vores PaymentGateway spotte, et standard svar vil blive givet, uden at en reel forekomst påberåbes:

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

Udtrykket for dette er mild hån. Dette betyder, at mock-metoder, der ikke er defineret, returnerer fornuftige standardindstillinger i modsætning til at kaste en undtagelse. Dette er designet i Spock for at gøre mocks og dermed teste mindre sprøde.

5.3. Stubbemetode kalder på Håner

Vi kan også konfigurere metoder, der kaldes vores mock til at reagere på en bestemt måde på forskellige argumenter. Lad os prøve at få vores PaymentGateway mock at vende tilbage rigtigt når vi foretager en betaling på 20:

givet: betalingGateway.makePayment (20) >> sand når: def resultat = betalingGateway.makePayment (20) derefter: resultat == sand

Hvad der er interessant her er, hvordan Spock gør brug af Groovys operatøroverbelastning for at stoppe metodeopkald. Med Java er vi nødt til at kalde virkelige metoder, hvilket uden tvivl betyder, at den resulterende kode er mere detaljeret og potentielt mindre udtryksfuld.

Lad os nu prøve et par flere typer stubning.

Hvis vi holdt op med at bekymre os om vores metodeargument og altid ville vende tilbage rigtigt, vi kunne bare bruge en understregning:

paymentGateway.makePayment (_) >> sand

Hvis vi ønskede at skifte mellem forskellige svar, kunne vi give en liste, for hvilket hvert element returneres i rækkefølge:

paymentGateway.makePayment (_) >>> [sand, sand, falsk, sand]

Der er flere muligheder, og disse kan blive dækket i en mere avanceret fremtidig artikel om hån.

5.4. Verifikation

En anden ting, vi måske vil gøre med mocks, er at hævde, at forskellige metoder blev kaldt på dem med forventede parametre. Med andre ord burde vi verificere interaktion med vores mocks.

En typisk brugssag til verifikation ville være, hvis en metode på vores mock havde en ugyldig returtype. I dette tilfælde, da der ikke er noget resultat for os at operere på, er der ingen udledt adfærd for os at teste via den testede metode. Generelt, hvis noget blev returneret, kunne metoden under test fungere på det, og det er resultatet af denne operation, som vi hævder.

Lad os prøve at verificere, at en metode med en ugyldig returtype kaldes:

def "Skal bekræfte meddelelse blev kaldt" () {given: def notifier = Mock (Notifier) ​​when: notifier.notify ('foo') then: 1 * notifier.notify ('foo')} 

Spock udnytter Groovy-operatørens overbelastning igen. Ved at gange vores mocks-metodeopkald med en siger vi, hvor mange gange vi forventer, at den er blevet kaldt.

Hvis vores metode slet ikke var blevet kaldt eller alternativt ikke var blevet kaldt så mange gange som vi specificerede, ville vores test ikke have givet os en informativ Spock-fejlmeddelelse. Lad os bevise dette ved at forvente, at det var blevet kaldt to gange:

2 * besked. Underret ('foo')

Lad os derefter se, hvordan fejlmeddelelsen ser ud. Vi gør det som normalt; det er ret informativt:

For få påkald til: 2 * notifier. Notify ('foo') (1 påkaldelse)

Ligesom stubning kan vi også udføre løsere verifikationsmatchning. Hvis vi ikke var ligeglade med, hvad vores metodeparameter var, kunne vi bruge en understregning:

2 * besked. Underret (_)

Eller hvis vi ønskede at sikre, at det ikke blev kaldt med et bestemt argument, kunne vi bruge ikke-operatoren:

2 * besked. Underret (! 'Foo')

Igen er der flere muligheder, som kan blive dækket af en fremtidig mere avanceret artikel.

6. Konklusion

I denne artikel har vi givet et hurtigt stykke gennem test med Spock.

Vi har demonstreret, hvordan vi ved at udnytte Groovy kan gøre vores tests mere ekspressive end den typiske JUnit-stak. Vi har forklaret strukturen af specifikationer og funktioner.

Og vi har vist, hvor let det er at udføre datadrevet test, og også hvordan hån og påstande er lette via native Spock-funktionalitet.

Implementeringen af ​​disse eksempler findes på GitHub. Dette er et Maven-baseret projekt, så det skal være let at køre som det er.


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