Programmering

JUnit 5-tutorial, del 1: Enhedstest med JUnit 5, Mockito og Hamcrest

JUnit 5 er den nye de facto standard til udvikling af enhedstest i Java. Denne nyeste version har efterladt begrænsningerne i Java 5 og integreret mange funktioner fra Java 8, især understøttelse af lambda-udtryk.

I denne første halvdel af en todelt introduktion til JUnit 5 kommer du i gang med test med JUnit 5. Jeg viser dig, hvordan du konfigurerer et Maven-projekt til at bruge JUnit 5, hvordan man skriver tests ved hjælp af @Prøve og @ParameterizedTest annoteringer, og hvordan man arbejder med de nye livscykluskommentarer i JUnit 5. Du får også vist et kort eksempel på brug af filterkoder, og jeg viser dig, hvordan du integrerer JUnit 5 med et tredjeparts påstandsbibliotek - i dette tilfælde , Hamcrest. Endelig får du en hurtig introduktion til tutorial til integration af JUnit 5 med Mockito, så du kan skrive mere robuste enhedstest til komplekse, virkelige systemer.

download Hent koden Få kildekoden til eksempler i denne vejledning. Oprettet af Steven Haines til JavaWorld.

Test-drevet udvikling

Hvis du har udviklet Java-kode i nogen periode, er du sandsynligvis meget fortrolig med testdrevet udvikling, så jeg holder dette afsnit kort. Det er vigtigt at forstå hvorfor vi skriver dog enhedstests samt de strategier, som udviklere anvender, når de designer enhedstest.

Testdrevet udvikling (TDD) er en softwareudviklingsproces, der fletter kodning, test og design. Det er en test-første tilgang, der sigter mod at forbedre kvaliteten af ​​dine applikationer. Testdrevet udvikling er defineret ved følgende livscyklus:

  1. Tilføj en test.
  2. Kør alle dine tests og observer den nye test mislykkes.
  3. Implementér koden.
  4. Kør alle dine tests, og følg den nye test, der følger.
  5. Refaktor koden.

Figur 1 viser denne TDD-livscyklus.

Steven Haines

Der er et dobbelt formål med at skrive tests, før du skriver din kode. For det første tvinger det dig til at tænke over det forretningsproblem, du prøver at løse. For eksempel hvordan skal vellykkede scenarier opføre sig? Hvilke betingelser skal mislykkes? Hvordan skal de fejle? For det andet giver test først dig mere tillid til dine tests. Når jeg skriver tests efter at have skrevet kode, skal jeg altid bryde dem for at sikre, at de rent faktisk får fejl. Skrivetest undgår først dette ekstra trin.

At skrive prøver til den lykkelige sti er normalt let: I betragtning af gode input skal klassen returnere et deterministisk svar. Men at skrive negative (eller fiasko) testsager, især for komplekse komponenter, kan være mere kompliceret.

Overvej som et eksempel at skrive tests til et databaselager. På den glade sti indsætter vi en post i databasen og modtager det oprettede objekt tilbage, inklusive genererede nøgler. I virkeligheden skal vi også overveje muligheden for en konflikt, såsom at indsætte en post med en unik kolonneværdi, der allerede er indeholdt i en anden post. Derudover hvad sker der, når lageret ikke kan oprette forbindelse til databasen, måske fordi brugernavnet eller adgangskoden er ændret? Hvad sker der, hvis der er en netværksfejl under transit? Hvad sker der, hvis anmodningen ikke gennemføres i din definerede timeoutgrænse?

For at opbygge en robust komponent skal du overveje alle sandsynlige og usandsynlige scenarier, udvikle tests til dem og skrive din kode for at tilfredsstille disse tests. Senere i artiklen ser vi på strategier til oprettelse af forskellige fejlscenarier sammen med nogle af de nye funktioner i JUnit 5, der kan hjælpe dig med at teste disse scenarier.

Vedtagelse af JUnit 5

Hvis du har brugt JUnit i et stykke tid, vil nogle af ændringerne i JUnit 5 være en justering. Her er en oversigt på højt niveau af, hvad der er forskelligt mellem de to versioner:

  • JUnit 5 er nu pakket i org.junit.jupiter gruppe, som ændrer, hvordan du inkluderer det i dine Maven- og Gradle-projekter.
  • JUnit 4 krævede et minimum JDK på JDK 5; JUnit 5 kræver mindst JDK 8.
  • JUnit 4'er @Før, @BeforeClass, @Efterog @Efter skole kommentarer er blevet erstattet af @BeforeEach, @BeforeAll, @AfterEachog @Trods alt, henholdsvis.
  • JUnit 4'er @Ignorere kommentar er blevet erstattet af @Handicappet kommentar.
  • Det @Kategori kommentar er blevet erstattet af @Tag kommentar.
  • JUnit 5 tilføjer et nyt sæt påståelsesmetoder.
  • Løbere er blevet erstattet med udvidelser med en ny API til udvidelsesimplementatorer.
  • JUnit 5 introducerer antagelser, der forhindrer en test i at udføre.
  • JUnit 5 understøtter indlejrede og dynamiske testklasser.

Vi udforsker de fleste af disse nye funktioner i denne artikel.

Enhedstest med JUnit 5

Lad os starte simpelt med et end-to-end-eksempel på konfiguration af et projekt til at bruge JUnit 5 til en enhedstest. Liste 1 viser a MathTools klasse, hvis metode konverterer en tæller og nævneren til en dobbelt.

Liste 1. Et eksempel på et JUnit 5-projekt (MathTools.java)

 pakke com.javaworld.geekcap.math; offentlig klasse MathTools {offentlig statisk dobbelt convertToDecimal (int tæller, int nævneren) {hvis (nævneren == 0) {kast ny IllegalArgumentException ("Nævneren må ikke være 0"); } returner (dobbelt) tæller / (dobbelt) nævneren; }}

Vi har to primære scenarier til test af MathTools klasse og dens metode:

  • EN gyldig test, hvor vi sender ikke-nul heltal for tælleren og nævneren.
  • EN fejlscenarie, hvor vi sender en nulværdi for nævneren.

Liste 2 viser en JUnit 5 testklasse til test af disse to scenarier.

Annonce 2. En JUnit 5 testklasse (MathToolsTest.java)

 pakke com.javaworld.geekcap.math; import java.lang.IllegalArgumentException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; klasse MathToolsTest {@Test ugyldig testConvertToDecimalSuccess () {dobbelt resultat = MathTools.convertToDecimal (3, 4); Assertions.assertEquals (0,75, resultat); } @Test ugyldig testConvertToDecimalInvalidDenominator () {Assertions.assertThrows (IllegalArgumentException.class, () -> MathTools.convertToDecimal (3, 0)); }}

I lister 2 er testConvertToDecimalInvalidDenominator metoden udfører MathTools :: convertToDecimal metode inde i en hævder kaster opkald. Det første argument er den forventede type undtagelse, der skal kastes. Det andet argument er en funktion, der vil kaste denne undtagelse. Det hævder kaster metode udfører funktionen og validerer, at den forventede type undtagelse kastes.

Assertions-klassen og dens metoder

Detorg.junit.jupiter.api.Test annotation angiver en testmetode. Bemærk, at @Prøve annotering kommer nu fra JUnit 5 Jupiter API-pakken i stedet for JUnit 4'er org.junit pakke. Det testConvertToDecimalSuccess metoden først udfører MathTools :: convertToDecimal metode med tælleren 3 og nævneren 4, hævder derefter, at resultatet er lig med 0,75. Det org.junit.jupiter.api.Assertions klasse giver et sæt af statisk metoder til sammenligning af faktiske og forventede resultater. Det Påstande klasse har følgende metoder, der dækker de fleste af de primitive datatyper:

  • assertArrayEquals sammenligner indholdet af et faktisk array med et forventet array.
  • hævderLige sammenligner en faktisk værdi med en forventet værdi.
  • assertNotEquals sammenligner to værdier for at validere, at de ikke er ens.
  • hævder sandt validerer, at den angivne værdi er sand.
  • hævder Falsk validerer, at den angivne værdi er falsk.
  • assertLinesMatch sammenligner to lister over Snors.
  • hævder Null validerer, at den angivne værdi er nul.
  • hævderNotNull validerer, at den angivne værdi ikke er nul.
  • hævderSamme validerer, at to værdier henviser til det samme objekt.
  • assertNotSame validerer, at to værdier ikke henviser til det samme objekt.
  • hævder kaster validerer, at udførelsen af ​​en metode giver en forventet undtagelse (du kan se dette i testConvertToDecimalInvalidDenominator eksempel ovenfor).
  • assertTimeout validerer, at en leveret funktion gennemføres inden for en bestemt timeout.
  • assertTimeoutForebyggende validerer, at en leveret funktion fuldføres inden for en bestemt timeout, men når timeoutet er nået, dræber den funktionens udførelse.

Hvis nogen af ​​disse påstandsmetoder mislykkes, markeres enhedstesten som mislykket. Denne fejlmeddelelse skrives til skærmen, når du kører testen, og gemmes derefter i en rapportfil.

Brug af delta med assertEquals

Ved brug flyde og dobbelt værdier i et hævderLige, kan du også angive en delta der repræsenterer en forskelstærskel mellem de to. I vores eksempel kunne vi have tilføjet et delta på 0,001, hvis 0,75 faktisk blev returneret som 0,750001.

Analyserer dine testresultater

Ud over at validere en værdi eller adfærd, hævde metoder kan også acceptere en tekstbeskrivelse af fejlen, som kan hjælpe dig med at diagnosticere fejl. For eksempel:

 Assertions.assertEquals (0,75, resultat, "MathTools :: convertToDecimal-værdien returnerede ikke den korrekte værdi på 0,75 for 3/4"); Assertions.assertEquals (0,75, resultat, () -> "MathTools :: convertToDecimal-værdien returnerede ikke den korrekte værdi på 0,75 for 3/4"); 

Outputtet viser den forventede værdi på 0,75 og den aktuelle værdi. Det viser også den angivne besked, som kan hjælpe dig med at forstå sammenhængen med fejlen. Forskellen mellem de to variationer er, at den første altid opretter meddelelsen, selvom den ikke vises, mens den anden kun konstruerer meddelelsen, hvis påstanden mislykkes. I dette tilfælde er opbygningen af ​​meddelelsen triviel, så det betyder ikke rigtig noget. Der er stadig ikke behov for at konstruere en fejlmeddelelse til en test, der består, så det er normalt en bedste praksis at bruge den anden stil.

Endelig, hvis du bruger en IDE som IntelliJ til at køre dine tests, vil hver testmetode blive vist med dens metodenavn. Dette er fint, hvis dine metodenavne er læselige, men du kan også tilføje en @DisplayName kommentar til dine testmetoder for bedre at identificere testene:

@Test @DisplayName ("Test succesfuld decimalkonvertering") ugyldig testConvertToDecimalSuccess () {dobbelt resultat = MathTools.convertToDecimal (3, 4); Assertions.assertEquals (0,751, resultat); }

Kører din enhedstest

For at køre JUnit 5-test fra et Maven-projekt skal du medtage maven-surefire-plugin i Maven pom.xml fil og tilføj en ny afhængighed. Liste 3 viser pom.xml fil til dette projekt.

Annonce 3. Maven pom.xml for et eksempel på et JUnit 5-projekt

  4.0.0 com.javaworld.geekcap junit5 jar 1.0-SNAPSHOT org.apache.maven.plugins maven-compiler-plugin 3.8.1 8 8 org.apache.maven.plugins maven-surefire-plugin 3.0.0-M4 junit5 // maven.apache.org org.junit.jupiter junit-jupiter 5.6.0 test 

JUnit 5 afhængigheder

JUnit 5 pakker sine komponenter i org.junit.jupiter gruppe, og vi skal tilføje junit-jupiter artefakt, som er en sammenlægningsgenstand, der importerer følgende afhængigheder:

  • junit-jupiter-api definerer API til skrivning af test og udvidelser.
  • junit-jupiter-motor er den implementering af testmotoren, der kører enhedstestene.
  • junit-jupiter-params giver support til parametriserede tests.

Dernæst skal vi tilføje maven-surefire-plugin bygge plug-in for at køre testene.

Endelig skal du sørge for at medtage maven-compiler-plugin med en version af Java 8 eller nyere, så du kan bruge Java 8-funktioner som lambdas.

Kør det!

Brug følgende kommando til at køre testklassen fra din IDE eller fra Maven:

mvn ren test

Hvis du har succes, skal du se output svarende til følgende:

 [INFO] ------------------------------------------------------- -------- [INFO] TESTER [INFO] ----------------------------------- -------------------- [INFO] Kører com.javaworld.geekcap.math.MathToolsTest [INFO] Testkørsel: 2, Fejl: 0, Fejl: 0, springet over : 0, forløbet tid: 0,04 s - i com.javaworld.geekcap.math.MathToolsTest [INFO] [INFO] Resultater: [INFO] [INFO] Testkørsel: 2, Fejl: 0, Fejl: 0, Springet over: 0 [ INFO] [INFO] --------------------------------------------- --------------------------- [INFO] BYG SUCCES [INFO] --------------- -------------------------------------------------- ------- [INFO] Samlet tid: 3.832 s [INFO] Færdig kl .: 2020-02-16T08: 21: 15-05: 00 [INFO] ------------- -------------------------------------------------- ---------