Inleiding tot testen met Spock en Groovy

1. Inleiding

In dit artikel kijken we naar Spock, een Groovy-testraamwerk. Hoofdzakelijk wil Spock een krachtiger alternatief zijn voor de traditionele JUnit-stack, door gebruik te maken van Groovy-functies.

Groovy is een op JVM gebaseerde taal die naadloos integreert met Java. Naast interoperabiliteit biedt het aanvullende taalconcepten zoals dynamisch zijn, optionele typen hebben en metaprogrammering.

Door gebruik te maken van Groovy introduceert Spock nieuwe en expressieve manieren om onze Java-applicaties te testen, die simpelweg niet mogelijk zijn in gewone Java-code. In dit artikel zullen we enkele van Spock's high-level concepten verkennen, met enkele praktische stapsgewijze voorbeelden.

2. Maven Afhankelijkheid

Laten we voordat we beginnen onze Maven-afhankelijkheden toevoegen:

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

We hebben zowel Spock als Groovy toegevoegd, net als elke standaardbibliotheek. Omdat Groovy een nieuwe JVM-taal is, moeten we de gmavenplus plug-in om het te kunnen compileren en uitvoeren:

 org.codehaus.gmavenplus gmavenplus-plugin 1.5 compileer testCompile 

Nu zijn we klaar om onze eerste Spock-test te schrijven, die in Groovy-code zal worden geschreven. Merk op dat we Groovy en Spock alleen gebruiken voor testdoeleinden en daarom zijn deze afhankelijkheden testbereik.

3. Opbouw van een Spock-test

3.1. Specificaties en kenmerken

Terwijl we onze tests in Groovy schrijven, moeten we ze toevoegen aan het src / test / groovy directory, in plaats van src / test / java. Laten we onze eerste test in deze map maken en deze een naam geven Specificatie. Groovy:

class FirstSpecification breidt specificatie {} uit

Merk op dat we het Specificatie koppel. Elke Spock-klasse moet dit uitbreiden om het framework ervoor beschikbaar te maken. Hierdoor kunnen we onze eerste implementeren voorzien zijn van:

def "één plus één moet gelijk zijn aan twee" () {verwacht: 1 + 1 == 2}

Voordat we de code uitleggen, is het ook vermeldenswaard dat in Spock wat we een voorzien zijn van is enigszins synoniem aan wat we zien als een test in JUnit. Zo wanneer we verwijzen naar een voorzien zijn van we verwijzen eigenlijk naar een test.

Laten we nu onze voorzien zijn van. Daarbij zouden we onmiddellijk enkele verschillen tussen het en Java moeten kunnen zien.

Het eerste verschil is dat de naam van de feature method wordt geschreven als een gewone string. In JUnit zouden we een methodenaam hebben gehad die camelcase of underscores gebruikt om de woorden te scheiden, wat niet zo expressief of menselijk leesbaar zou zijn geweest.

Het volgende is dat onze testcode in een verwachten blok. We zullen de blokken binnenkort in meer detail bespreken, maar in wezen zijn ze een logische manier om de verschillende stappen van onze tests op te splitsen.

Ten slotte realiseren we ons dat er geen beweringen zijn. Dat komt omdat de bewering impliciet is en wordt doorgegeven als onze bewering gelijk is waar en falen wanneer het gelijk is false. Nogmaals, we zullen beweringen binnenkort in meer details behandelen.

3.2. Blokken

Soms merken we bij het schrijven van JUnit een test dat er geen expressieve manier is om het in delen op te splitsen. Als we bijvoorbeeld gedragsgestuurde ontwikkeling volgen, kunnen we uiteindelijk de gegeven wanneer dan onderdelen met opmerkingen:

@Test openbare leegte gegevenTwoAndTwo_whenAdding_thenResultIsFour () {// Gegeven int first = 2; int tweede = 4; // When int resultaat = 2 + 2; // AssertTrue (resultaat == 4)}

Spock pakt dit probleem aan met blokken. Blokken zijn een Spock-native manier om de fasen van onze test op te splitsen met behulp van labels. Ze geven ons labels voor gegeven wanneer dan en meer:

  1. Opstelling (Aliased by Given) - Hier voeren we alle instellingen uit die nodig zijn voordat een test wordt uitgevoerd. Dit is een impliciet blok, waarbij code die helemaal geen blok bevat er onderdeel van wordt
  2. Wanneer - Hier bieden we een stimulus aan wat wordt getest. Met andere woorden, waar we onze testmethode aanroepen
  3. Dan - Hier horen de beweringen thuis. In Spock worden deze geëvalueerd als gewone booleaanse beweringen, die later zullen worden behandeld
  4. Verwachten - Dit is een manier om ons uit te voeren stimulus en bewering binnen hetzelfde blok. Afhankelijk van wat we expressiever vinden, kunnen we ervoor kiezen om dit blok al dan niet te gebruiken
  5. Schoonmaken - Hier breken we alle testafhankelijkheidsbronnen af ​​die anders zouden worden achtergelaten. We willen bijvoorbeeld bestanden uit het bestandssysteem verwijderen of testgegevens verwijderen die naar een database zijn geschreven

Laten we proberen onze test opnieuw te implementeren, deze keer volledig gebruikmakend van blokken:

def "twee plus twee moet gelijk zijn aan vier" () {gegeven: int links = 2 int rechts = 2 wanneer: int resultaat = links + rechts dan: resultaat == 4}

Zoals we kunnen zien, helpen blokken onze test beter leesbaar te maken.

3.3. Groovy-functies gebruiken voor beweringen

Binnen de dan en verwachten blokken, beweringen zijn impliciet.

Meestal wordt elke instructie geëvalueerd en mislukt als dat niet het geval is waar. Wanneer dit wordt gekoppeld aan verschillende Groovy-functies, wordt de behoefte aan een assertion-bibliotheek goed weggenomen. Laten we een lijst bewering om dit aan te tonen:

def "Zou van lijst moeten kunnen verwijderen" () {gegeven: def lijst = [1, 2, 3, 4] wanneer: lijst.remove (0) dan: lijst == [2, 3, 4]}

Hoewel we Groovy in dit artikel slechts kort bespreken, is het de moeite waard om uit te leggen wat hier gebeurt.

Ten eerste biedt Groovy ons eenvoudigere manieren om lijsten te maken. We kunnen onze elementen gewoon tussen vierkante haken aangeven, en intern een lijst zal worden geïnstantieerd.

Ten tweede, omdat Groovy dynamisch is, kunnen we gebruiken def wat gewoon betekent dat we geen type voor onze variabelen declareren.

Ten slotte, in de context van het vereenvoudigen van onze test, is de meest bruikbare functie die is aangetoond, overbelasting van de operator. Dit betekent dat intern, in plaats van een referentievergelijking te maken zoals in Java, de is gelijk aan () methode wordt aangeroepen om de twee lijsten te vergelijken.

Het is ook de moeite waard om aan te tonen wat er gebeurt als onze test mislukt. Laten we het laten breken en dan bekijken wat er naar de console wordt uitgevoerd:

Voorwaarde niet vervuld: lijst == [1, 3, 4] | | | false [2, 3, 4] bij FirstSpecification. Moet uit de lijst kunnen worden verwijderd (FirstSpecification.groovy: 30)

Terwijl alles wat er aan de hand is, roept is gelijk aan () op twee lijsten is Spock intelligent genoeg om de falende bewering uit te splitsen, wat ons nuttige informatie geeft voor foutopsporing.

3.4. Uitzonderingen claimen

Spock biedt ons ook een expressieve manier om uitzonderingen te controleren. In JUnit gebruiken sommige van onze opties mogelijk een proberen te vangen block, verklaren verwacht bovenaan onze test, of door gebruik te maken van een bibliotheek van derden. De native beweringen van Spock hebben een manier om met uitzonderingen om te gaan:

def "Moet een index buiten het bereik krijgen bij het verwijderen van een niet-bestaand item" () {gegeven: def lijst = [1, 2, 3, 4] wanneer: lijst.remove (20) dan: gegooid (IndexOutOfBoundsException) lijst. maat () == 4}

Hier hebben we geen extra bibliotheek hoeven te introduceren. Een ander voordeel is dat de gegooid() methode zal het type uitzondering bevestigen, maar de uitvoering van de test niet stoppen.

4. Gegevensgestuurd testen

4.1. Wat is een gegevensgestuurde test?

Eigenlijk, datagestuurd testen is wanneer we hetzelfde gedrag meerdere keren testen met verschillende parameters en beweringen. Een klassiek voorbeeld hiervan is het testen van een wiskundige bewerking, zoals het kwadrateren van een getal. Afhankelijk van de verschillende permutaties van operanden, zal het resultaat anders zijn. In Java is de term waarmee we misschien meer vertrouwd zijn, geparametriseerde testen.

4.2. Implementeren van een geparametriseerde test in Java

Voor een bepaalde context is het de moeite waard om een ​​geparametriseerde test te implementeren met JUnit:

@RunWith (Parameterized.class) openbare klasse FibonacciTest {@Parameters openbare statische verzamelingsgegevens () {return Arrays.asList (nieuw object [] [] {{1, 1}, {2, 4}, {3, 9}} ); } private int input; privé int verwacht; openbare FibonacciTest (int input, int verwacht) {this.input = input; this.expected = verwacht; } @Test openbare ongeldige test () {assertEquals (fExpected, Math.pow (3, 2)); }}

Zoals we kunnen zien, is er nogal wat breedsprakigheid en is de code niet erg leesbaar. We moesten een tweedimensionale objectenreeks maken die buiten de test leeft, en zelfs een omhulselobject om de verschillende testwaarden te injecteren.

4.3. Datatables gebruiken in Spock

Een gemakkelijke overwinning voor Spock in vergelijking met JUnit is hoe het netjes geparametriseerde tests implementeert. Nogmaals, in Spock staat dit bekend als Gegevensgestuurd testen. Laten we nu dezelfde test opnieuw uitvoeren, alleen deze keer gebruiken we Spock met Gegevenstabellen, wat een veel gemakkelijkere manier biedt om een ​​geparametriseerde test uit te voeren:

def "getallen tot de macht van twee" (int a, int b, int c) 4 3 

Zoals we kunnen zien, hebben we gewoon een eenvoudige en expressieve gegevenstabel met al onze parameters.

Het hoort ook waar het moet, naast de test, en er is geen standaardplaat. De test is expressief, met een door mensen leesbare naam en puur verwachten en waar blok om de logische secties op te splitsen.

4.4. Wanneer een datatabel faalt

Het is ook de moeite waard om te zien wat er gebeurt als onze test mislukt:

Voorwaarde niet vervuld: Math.pow (a, b) == c | | | | | 4.0 2 2 | 1 false Verwacht: 1 Werkelijk: 4.0

Nogmaals, Spock geeft ons een zeer informatieve foutmelding. We kunnen precies zien welke rij van onze Datatable een storing heeft veroorzaakt en waarom.

5. Spottend

5.1. Wat is bespottelijk?

Bespotten is een manier om het gedrag van een klas te veranderen waarmee onze geteste dienst samenwerkt. Het is een handige manier om bedrijfslogica te testen, los van de afhankelijkheden ervan.

Een klassiek voorbeeld hiervan is het vervangen van een klasse die een netwerkoproep doet door iets dat gewoon doet alsof. Voor een meer diepgaande uitleg is het de moeite waard om dit artikel te lezen.

5.2. Bespotten met behulp van Spock

Spock heeft zijn eigen mocking-framework, gebruikmakend van interessante concepten die door Groovy naar de JVM zijn gebracht. Laten we eerst een instantiëren Bespotten:

PaymentGateway paymentGateway = Mock ()

In dit geval wordt het type van onze mock afgeleid door het type variabele. Omdat Groovy een dynamische taal is, kunnen we ook een type-argument opgeven, zodat we onze mock niet aan een bepaald type hoeven toe te wijzen:

def paymentGateway = Mock (PaymentGateway)

Nu, wanneer we een methode aanroepen op onze BetalingGateway bespotten, er wordt een standaardantwoord gegeven, zonder dat er een echte instantie wordt aangeroepen:

wanneer: def resultaat = paymentGateway.makePayment (12,99) dan: resultaat == false

De term hiervoor is mild spottend. Dit betekent dat nepmethoden die niet zijn gedefinieerd, verstandige standaardwaarden zullen retourneren, in plaats van een uitzondering te genereren. Dit is zo ontworpen in Spock, om spot en dus tests minder broos te maken.

5.3. Stubbing-methode roept op Bespot

We kunnen ook methoden configureren die op onze mock worden aangeroepen om op een bepaalde manier op verschillende argumenten te reageren. Laten we proberen onze BetalingGateway bespotten om terug te keren waar wanneer we een betaling doen van 20:

gegeven: paymentGateway.makePayment (20) >> waar wanneer: def resultaat = paymentGateway.makePayment (20) dan: resultaat == waar

Wat hier interessant is, is hoe Spock gebruikmaakt van de overbelasting van Groovy-operators om methodeaanroepen te stubben. Met Java moeten we echte methoden aanroepen, wat aantoonbaar betekent dat de resulterende code meer uitgebreid en mogelijk minder expressief is.

Laten we nu nog een paar soorten stubbing proberen.

Als we ons geen zorgen meer maakten over ons methode-argument en altijd wilden terugkeren waar, we kunnen gewoon een onderstrepingsteken gebruiken:

paymentGateway.makePayment (_) >> true

Als we tussen verschillende antwoorden willen wisselen, kunnen we een lijst geven, waarvoor elk element op volgorde wordt geretourneerd:

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

Er zijn meer mogelijkheden, en deze kunnen worden behandeld in een meer geavanceerd toekomstig artikel over bespotten.

5.4. Verificatie

Een ander ding dat we zouden willen doen met spotten, is beweren dat er verschillende methoden op werden aangeroepen met verwachte parameters. Met andere woorden, we zouden interacties met onze spot moeten verifiëren.

Een typische use case voor verificatie zou zijn als een methode op onze mock een leegte retourtype. In dit geval, omdat er geen resultaat is waarop we kunnen opereren, is er geen afgeleid gedrag dat we kunnen testen via de te testen methode. Over het algemeen, als iets werd geretourneerd, zou de te testen methode erop kunnen werken, en het is het resultaat van die bewerking wat we beweren.

Laten we proberen te verifiëren dat een methode met een ongeldig retourtype wordt genoemd:

def "Moet verifiëren dat de melding werd aangeroepen" () {gegeven: def notifier = Mock (Notifier) ​​wanneer: notifier.notify ('foo') dan: 1 * notifier.notify ('foo')} 

Spock maakt opnieuw gebruik van de overbelasting van de Groovy-operator. Door onze nepmethode-aanroep met één te vermenigvuldigen, zeggen we hoe vaak we verwachten dat deze is aangeroepen.

Als onze methode helemaal niet was aangeroepen of als alternatief niet zo vaak was aangeroepen als we hadden gespecificeerd, zou onze test ons geen informatieve Spock-foutmelding hebben gegeven. Laten we dit bewijzen door te verwachten dat het twee keer is gebeld:

2 * notifier.notify ('foo')

Laten we hierna kijken hoe de foutmelding eruitziet. We zullen dat zoals gewoonlijk; het is vrij informatief:

Te weinig aanroepen voor: 2 * notifier.notify ('foo') (1 aanroep)

Net als stubbing, kunnen we ook lossere verificatie-matching uitvoeren. Als het ons niet kon schelen wat onze methodeparameter was, zouden we een onderstrepingsteken kunnen gebruiken:

2 * kennisgever.notify (_)

Of als we zeker wilden weten dat het niet met een bepaald argument werd aangeroepen, zouden we de operator not kunnen gebruiken:

2 * notifier.notify (! 'Foo')

Nogmaals, er zijn meer mogelijkheden, die mogelijk worden behandeld in een toekomstig meer geavanceerd artikel.

6. Conclusie

In dit artikel hebben we een korte doorsnede gegeven door het testen met Spock.

We hebben aangetoond hoe we, door gebruik te maken van Groovy, onze tests expressiever kunnen maken dan de typische JUnit-stack. We hebben de structuur van specificaties en Kenmerken.

En we hebben laten zien hoe gemakkelijk het is om datagestuurde tests uit te voeren, en ook hoe bespotten en beweringen eenvoudig zijn via native Spock-functionaliteit.

De implementatie van deze voorbeelden is te vinden op GitHub. Dit is een op Maven gebaseerd project, dus het zou gemakkelijk moeten zijn zoals het is.