JUnit 5 voor Kotlin-ontwikkelaars

1. Inleiding

De nieuw uitgebrachte JUnit 5 is de volgende versie van het bekende testframework voor Java. Deze versie bevat een aantal functies die specifiek gericht zijn op functionaliteit die in Java 8 is geïntroduceerd - het is voornamelijk gebouwd rond het gebruik van lambda-uitdrukkingen.

In dit korte artikel laten we zien hoe goed dezelfde tool is werkt met de Kotlin-taal.

2. Eenvoudige JUnit 5-tests

Op zijn eenvoudigst werkt een JUnit 5-test geschreven in Kotlin precies zoals verwacht. We schrijven een testklasse, annoteren onze testmethoden met de @Test annotatie, schrijf onze code en voer de beweringen uit:

class CalculatorTest {private val calculator = Calculator () @Test fun whenAdding1and3_thenAnswerIs4 () {Assertions.assertEquals (4, calculator.add (1, 3))}}

Alles werkt hier gewoon uit de doos. We kunnen gebruik maken van de standaard @Test, @BeforeAll, @BeforeEach, @AfterEach, en @Ten slotte annotaties. We kunnen ook communiceren met velden in de testklasse op precies dezelfde manier als in Java.

Merk op dat de benodigde invoer verschillend is, en wij doenbeweringen met behulp van de Beweringen class in plaats van de Beweren klasse. Dit is een standaardwijziging voor JUnit 5 en is niet specifiek voor Kotlin.

Laten we, voordat we verder gaan, de testnaam wijzigen en gebruiken backtick-ID's in Kotlin:

@Test fun `Het toevoegen van 1 en 3 moet gelijk zijn aan 4` () {Assertions.assertEquals (4, calculator.add (1, 3))}

Nu is het veel leesbaarder! In Kotlin kunnen we alle variabelen en functies declareren met behulp van backticks, maar het wordt niet aanbevolen om dit te doen voor normale gebruikssituaties.

3. Geavanceerde beweringen

JUnit 5 voegt enkele geavanceerde beweringen toe voorwerken met lambda's. Deze werken in Kotlin hetzelfde als in Java, maar moeten op een iets andere manier worden uitgedrukt vanwege de manier waarop de taal werkt.

3.1. Uitzonderingen claimen

JUnit 5 voegt een bewering toe voor wanneer een aanroep naar verwachting een uitzondering genereert. We kunnen testen dat een specifieke aanroep - in plaats van zomaar een aanroep in de methode - de verwachte uitzondering genereert. We kunnen zelfs een beroep doen op de uitzondering zelf.

Op Java zouden we een lambda doorgeven aan de oproep naar Assertions.assertThrows. We doen hetzelfde in Kotlin, maar we kunnen het maak de code beter leesbaar door een blok toe te voegen aan het einde van de assertion-aanroep:

@Test fun `Delen door nul zou de DivideByZeroException moeten opleveren` () {val exception = Assertions.assertThrows (DivideByZeroException :: class.java) {calculator.divide (5, 0)} Assertions.assertEquals (5, exception.numerator)}

Deze code werkt precies hetzelfde als het Java-equivalent, maar is gemakkelijker te lezen, aangezien we geen lambda hoeven door te geven tussen de haakjes waar we de assertThrows functie.

3.2. Meerdere beweringen

JUnit 5 voegt de mogelijkheid toe aan voer meerdere beweringen tegelijkertijd uit, en het evalueert ze allemaal en rapporteert over alle mislukkingen.

Hierdoor kunnen we meer informatie verzamelen in een enkele testrun in plaats van dat we gedwongen worden om de ene fout op te lossen om de volgende te raken. Om dit te doen, bellen we Assertions.assertAll, waarbij een willekeurig aantal lambda's wordt doorgegeven.

In Kotlinmoeten we dit iets anders aanpakken. De functie neemt eigenlijk een varargs parameter van het type Uitvoerbaar.

Momenteel is er geen ondersteuning voor het automatisch casten van een lambda naar een functionele interface, dus we moeten het met de hand doen:

fun `Het kwadraat van een getal moet gelijk zijn aan dat getal vermenigvuldigd met zichzelf` () {Assertions.assertAll (Uitvoerbaar bestand {Assertions.assertEquals (1, calculator.square (1))}, Uitvoerbaar bestand {Assertions.assertEquals (4, rekenmachine .square (2))}, Uitvoerbaar bestand {Assertions.assertEquals (9, calculator.square (3))})}

3.3. Leveranciers voor echte en valse tests

Af en toe willen we testen of een oproep een waar of false waarde. Historisch zouden we deze waarde berekenen en aanroepen beweren waar of bewerenFalse naargelang het geval. In JUnit 5 kan in plaats daarvan een lambda worden opgegeven die de waarde retourneert die wordt gecontroleerd.

Kotlin laat ons een lambda passeren op dezelfde manier die we hierboven hebben gezien voor het testen van uitzonderingen. We kunnen ook methodeverwijzingen doorgeven. Dit is vooral handig bij het testen van de geretourneerde waarde van een bestaand object, zoals we hier doen met List.isEmpty:

@Test fun `isEmpty moet true retourneren voor lege lijsten` () {val list = listOf () Assertions.assertTrue (list :: isEmpty)}

3.4. Leveranciers voor storingsberichten

In sommige gevallen willen we dat geef ons eigen foutbericht worden weergegeven bij een beweringsfout in plaats van de standaard.

Vaak zijn dit simpele snaren, maar soms willen we misschien een string gebruiken die duur is om te berekenen. In JUnit 5 kunnen we dat geef een lambda op om deze string te berekenen, en het is alleen beroep gedaan op mislukking in plaats van van tevoren te worden berekend.

Dit kan helpen laat de tests sneller verlopen en verkort de bouwtijden. Dit werkt precies hetzelfde als we eerder zagen:

@Test plezier `3 is gelijk aan 4` () {Assertions.assertEquals (3, 4) {" Drie is niet gelijk aan vier "}}

4. Gegevensgestuurde tests

Een van de grote verbeteringen in JUnit 5 is het native ondersteuning voor datagestuurde tests. Deze werken even goed in Kotlin, en het gebruik van functionele toewijzingen op verzamelingen kan maken onze tests gemakkelijker te lezen en te onderhouden.

4.1. TestFactory-methoden

De eenvoudigste manier om gegevensgestuurde tests af te handelen, is door de @TestFactory annotatie. Dit vervangt de @Test annotatie, en de methode retourneert een verzameling van DynamicNode instanties - normaal gesproken gemaakt door te bellen DynamicTest.dynamicTest.

Dit werkt precies hetzelfde in Kotlin, en dat kunnen we geef de lambda op een schonere manier door nogmaals, zoals we eerder zagen:

@TestFactory fun testSquares () = listOf (DynamicTest.dynamicTest ("wanneer ik 1 ^ 2 bereken, krijg ik 1") {Assertions.assertEquals (1, calculator.square (1))}, DynamicTest.dynamicTest ("wanneer ik bereken 2 ^ 2, dan krijg ik 4 ") {Assertions.assertEquals (4, calculator.square (2))}, DynamicTest.dynamicTest (" als ik 3 ^ 2 bereken, krijg ik 9 ") {Assertions.assertEquals (9, rekenmachine .square (3))})

We kunnen het echter beter doen. We kunnen gemakkelijk bouw onze lijst op door een aantal functionele mapping uit te voeren op een eenvoudige invoerlijst met gegevens:

@TestFactory fun testSquares () = listOf (1 tot 1, 2 tot 4, 3 tot 9, 4 tot 16, 5 tot 25) .map {(invoer, verwacht) -> DynamicTest.dynamicTest ("wanneer ik $ input ^ bereken 2 dan krijg ik $ verwacht ") {Assertions.assertEquals (verwacht, calculator.square (input))}}

We kunnen meteen meer testcases aan de invoerlijst toevoegen en deze voegt automatisch tests toe.

We kunnen ook maak de invoerlijst als een klasseveld en deel het tussen meerdere tests:

private val squaresTestData = listOf (1 tot 1, 2 tot 4, 3 tot 9, 4 tot 16, 5 tot 25) 
@TestFactory fun testSquares () = squaresTestData .map {(invoer, verwacht) -> DynamicTest.dynamicTest ("wanneer ik $ input ^ 2 bereken, krijg ik $ verwacht") {Assertions.assertEquals (verwacht, calculator.square (invoer) )}}
@TestFactory fun testSquareRoots () = squaresTestData .map {(verwacht, invoer) -> DynamicTest.dynamicTest ("wanneer ik de vierkantswortel van $ invoer bereken, krijg ik $ verwacht") {Assertions.assertEquals (verwacht, calculator.squareRoot ( invoer)) } }

4.2. Geparametriseerde tests

Er zijn experimentele uitbreidingen van JUnit 5 om gemakkelijkere manieren te bieden om geparametriseerde tests te schrijven. Deze worden gedaan met behulp van de @ParameterizedTest annotatie van de org.junit.jupiter: junit-jupiter-params afhankelijkheid:

 org.junit.jupiter junit-jupiter-params 5.0.0 

De nieuwste versie is te vinden op Maven Central.

De @MethodSource annotatie stelt ons in staat testparameters produceren door een statische functie aan te roepen die zich in dezelfde klasse bevindt als de test. Dit is mogelijk maar niet duidelijk in Kotlin. We moeten gebruik de @JvmStatic annotatie in een begeleidend object:

@ParameterizedTest @MethodSource ("squares") fun testSquares (invoer: Int, verwacht: Int) {Assertions.assertEquals (verwacht, invoer * invoer)} begeleidend object {@JvmStatic fun squares () = listOf (Arguments.of (1, 1), Arguments.of (2, 4))}

Dit betekent ook dat de methoden die worden gebruikt om parameters te produceren allemaal samen moeten zijn sinds we kunnen er maar één hebben begeleidend object per klas.

Alle andere manieren om geparametriseerde tests te gebruiken, werken in Kotlin precies hetzelfde als in Java. @CsvSource is hier van speciaal belang, omdat we dat kunnen gebruiken in plaats van @MethodSource voor eenvoudige testgegevens meestal om onze tests leesbaarder te maken:

@ParameterizedTest @CsvSource ("1, 1", "2, 4", "3, 9") fun testSquares (invoer: Int, verwacht: Int) {Assertions.assertEquals (verwacht, invoer * invoer)}

5. Getagde tests

De Kotlin-taal staat momenteel geen herhaalde annotaties toe over klassen en methoden. Dit maakt het gebruik van tags iets meer uitgebreid, zoals we verplicht zijn wikkel ze in de @Tags annotatie:

@Tags (Tag ("slow"), Tag ("logarithms")) @Test fun `Log naar basis 2 van 8 moet gelijk zijn aan 3` () {Assertions.assertEquals (3.0, calculator.log (2, 8) )}

Dit is ook vereist in Java 7 en wordt al volledig ondersteund door JUnit 5.

6. Samenvatting

JUnit 5 voegt een aantal krachtige testtools toe die we kunnen gebruiken. Deze werken bijna allemaal perfect met de Kotlin-taal, hoewel in sommige gevallen met een iets andere syntaxis dan we zien in de Java-equivalenten.

Vaak zijn deze wijzigingen in syntaxis echter gemakkelijker te lezen en te gebruiken bij het gebruik van Kotlin.

Voorbeelden van al deze functies zijn te vinden op GitHub.