In dit artikel maken we kennis met het Spock test framework. Na het bespreken van de core features van het framework, beschrijf ik de syntax en het data driven testen. Als laatste bespreek ik de configuratie om de testen goed te laten draaien in een Maven gebaseerde build configuratie. Dit is nodig om zaken, zoals code coverage en het uitvoeren van testen door middel van Maven goed te laten verlopen. Alle gebruikte code is beschikbaar in een github repository, zodat je hier meteen mee aan de slag kan.
Een aantal voordelen als je test met het Spock framework zijn:
- Het schrijven van de testen in Groovy, wat je minder boilerplate oplevert;
- Het eenvoudig data driven testen;
- De inzichtelijkheid van de testcode en de resultaten;
- Door de syntax is de testcode ook heel leesbaar, zodat het als documentatie gebruikt kan worden.
De core features
Het Spock framework is een test framework dat zowel voor Groovy als Java projecten gebruikt kan worden. De testen worden in Groovy geschreven. Ook als je geen Groovy-ervaring hebt, kun je als Javaan hier snel mee aan de slag. In Spock heet een test een specificatie die eigenschappen van een systeem beschrijft. Het framework is dus geïnspireerd door BDD (behavior driven development). In de testcode werk je dan ook met de BDD keywords "given, when, then” zoals je later in dit artikel zult zien. Het Spock framework heeft ook modules voor de integratie met Spring, Spring Boot, Tapestry, Guice en Grails.
De syntax
Een specificatie schrijf je door de klasse te extenden van de Specification klasse, zoals je kunt zien op afbeelding 1.
Afbeelding 1
De conventie is om je klasse-naam op Spec of Specification te laten eindigen. De instantie velden worden voor elke test (feature genaamd in Spock) opnieuw geïnstantieerd. Het is te vergelijken met JUnit als deze velden in een met @Before geannoteerde methode geïnitialiseerd zouden zijn. Om een veld in Groovy te declareren, gebruik je het keyword "def". Mocht je een veld willen delen in de testen, dan kun je dit aangeven met de annotatie @Shared zonder def.
Fixtures gebruik je om een uitgangssituatie voor de testen klaar te zetten en op te schonen na het uitvoeren van de testen. De fixtures zijn optioneel. In afbeelding 2 staan de methode namen met een beschrijving. De fixtures zijn te vergelijken met de JUnit annotaties. In onderstaande tabel zie je de mapping tussen Spock features en JUnit annotaties.
Spock |
JUnit |
Setup |
@Before |
Cleanup |
@After |
setupSpec |
@BeforeClass |
cleanupSpec |
@AfterClass |
Afbeelding 2
In de feature methoden beschrijf je het verwachte gedrag van het testen systeem. Dit zijn de daadwerkelijke testen, in JUnit met @Test geannoteerde methoden. De feature methoden worden door middel van een String gedefinieerd. Een voorbeeld van een specificatie zou kunnen zijn:
def "Calculate morgage rate"() {
// Testcode
}
Het grote voordeel hiervan vind ik dat de test omschrijvingen meer zeggen dan bijvoorbeeld wanneer je standaard Java gebruikt, nog meer wanneer je data driven testen beschrijft. De testcode binnen de methode structuureer je doormiddel van blokken (afbeelding 3).
Afbeelding 3
Een test bevat altijd minstens één blok. Zonder blok ziet het framework een methode niet als testmethode en zal deze ook niet uitgevoerd worden. In het setup: block zet je de uitgangssituatie klaar voor de test, een synoniem hiervoor is “given:”. De “when:” en “then:” blokken moet je samen gebruiken. In het “when:” blok beschrijf je de actie die uitgevoerd moet worden en in het “then:” blok de controles die je wilt uitvoeren.
In het “then:” blok bestaan twee methoden die erg handig zijn, de “thrown” en “with” methode. De thrown(Exception.class) methode gebruik je om excepties te testen. Deze methode geeft de exceptie terug, zodat je eventueel ook de inhoud van de exceptie kunt controleren. In listing 1 is een voorbeeld opgenomen.
Listing 1
Met de with-methode kun je als parameter het resultaat en een closure meegeven om het resultaat te controleren. Door middel van de with-methode is de test beter leesbaar. Een voorbeeld van het gebruik van with staat in listing 2. Zonder with had hier meer code gestaan, voor iedere eigenschap was een assert regel geweest. Bijvoorbeeld voor de kleur had gestaan “assert result.color == color”. Daarnaast is door de with-constructie in één oogopslag te zien dat de eigenschappen van het resultaat nagekeken worden.
Listing 2
Om een conditie te testen, gebruik je het Groovy assert keyword. Het grote voordeel van assert is de verbositeit die assert meegeeft wanneer een conditie faalt. Het is niet zomaar een “assertion failed expected true but was false” zoals falende testen met JUnit bijvoorbeeld. In afbeelding 4 zien we een voorbeeld van een falende conditie in Spock.
Afbeelding 4
Hier zie je de falende conditie van een stack die getest wordt. In JUnit had je alleen de melding gekregen dat 1 niet gelijk is aan 2. Met de Groovy assert zie je de inhoud van de stack (1 element in dit voorbeeld " push me"), het resultaat van de size-methode en als laatste de falende conditie. Deze verbositeit kan veel tijd schelen bij de analyse van falende testen.
Het “expect:” blok gebruik je wanneer je het te langdradig vindt om when: en then: blokken te gebruiken. Een expect: blok mag alleen variabele declaraties en condities bevatten.
Het “cleanup:” blok gebruik je om resources vrij te geven, de code binnen dit blok wordt altijd uitgevoerd. Ook wanneer er excepties zijn opgetreden en dit alleen opgevolgd mag worden door een “where:” blok. Het is belangrijk om het “cleanup:” blok defensief te programmeren.
Als laatste het “where:” blok, dit blok komt altijd als laatste blok en wordt gebruikt voor data driven testen, dit behandel ik later in het artikel.
Elk blok mag optioneel een omschrijving bevatten. Met deze toevoeging kunnen de specificaties beter dienen als documentatie. Een blok dat hierbij nuttig kan zijn is het “and:” blok. Dit blok kan bij alle andere blokken gebruikt worden zonder de semantiek te veranderen. Een specificatie kan tevens helper methoden bevatten. Dit zijn methoden die geen keywords bevatten. Deze gebruik je om gedupliceerde code tussen testen in een helper methode te plaatsen.
Data driven testen
Deze feature van het Spock framework maakt het mogelijk om eenvoudig data driven testen te schrijven. In Junit kun je @Theory of @Parametrised gebruiken voor data driven tests, maar het nadeel hiervan is dat de data gedeeld wordt voor alle testen in de test klasse. In Spock kun je per test een dataset definiëren die gebruikt zal worden om de test aan te sturen.
Het definiëren van een datatabel is het meest simpel en dit wordt dan ook veel gebruikt. Een datatabel definieer je door een header, waarbij de kolomnamen de variabele namen zijn. Elke volgende regel is een dataset die zal worden gebruikt voor een iteratie van de test. Tussen de kolommen gebruik je het "|" teken. Optioneel kun je een dubbel pipe teken gebruiken om visueel de input van de output te scheiden. Elke iteratie zal als een separate test gerund worden zonder data met elkaar te delen. Als voorbeeld zie je het gebruik van een datatabel in listing 3.
Listing 3
Behalve het gemak dat je nu een testspecificatie hebt voor meerdere testgevallen, biedt het data driven testen met Spock nóg meer voordelen. Met de annotatie @Unroll worden de testen afzonderlijk gerapporteerd. Standaard is dit aangegeven met een index, maar het kan nog mooier. In de definitie van de testmethode kun je refereren naar de variabelen met het "#" teken. Op afbeelding 5 zie je het resultaat van listing 3 wanneer de test gedraaid wordt.
Afbeelding 5
Naast het inline definiëren van een datatabel kun je ook het "<<" teken gebruiken en een iterator als paramater meegeven. Hiermee kun je bijvoorbeeld data uit een tabel gebruiken als testdata.
Integratie met Maven
Wil je de testen met Maven runnen, dan moet hiervoor wel het één en ander geconfigureerd worden. Om Spock te gebruiken, moet je een test dependency "spock-core" toevoegen. Als tweede moet de Groovy testcode gecompileerd worden. Hiervoor gebruik je de Gmaven plugin. We moeten de executie binden aan de testCompile Maven fase. De Gmaven plugin verwacht de groovy code in een map genaamd "groovy" onder src/test, dit kun je overriden als je wilt.
Als laatste moet de Surefire plugin geconfigureerd worden om ook de groovy testen te zien en deze mee te nemen in de code coverage. Hiervoor voeg je een "<include>" blok toe met als inhoud "**/*.Spec.class".
Zoals je kunt lezen, is het erg eenvoudig om met het Spock framework te testen, zelfs zonder Groovy ervaring. Mocht je hiermee aan de slag willen gaan, dan kun je de repository klonen op github: https://github.com/bendh/spockarticle