Veiledning til dynamiske tester i Junit 5

1. Oversikt

Dynamisk testing er en ny programmeringsmodell introdusert i JUnit 5. I denne artikkelen ser vi på hva nøyaktig dynamiske tester er og hvordan du lager dem.

Hvis du er helt ny på JUnit 5, vil du kanskje sjekke forhåndsvisningen av JUnit 5 og vår primære guide.

2. Hva er en DynamicTest?

Standardtestene kommentert med @Test kommentar er statiske tester som er fullstendig spesifisert på kompileringstidspunktet. EN DynamicTest er en test generert under kjøretid. Disse testene genereres av en fabrikkmetode som er merket med @TestFabrikk kommentar.

EN @TestFabrikk metoden må returnere a Strøm, Samling, Iterabel, eller Iterator av DynamicTest tilfeller. Å returnere noe annet vil resultere i en JUnitException siden ugyldige returtyper ikke kan oppdages på kompileringstidspunktet. Bortsett fra dette, a @TestFabrikk metoden kan ikke være statiskc eller privat.

De DynamicTests utføres annerledes enn standarden @Tests og støtter ikke tilbakeringing i livssyklus. Betydning, den @BeforeEach og @EfterEach metoder vil ikke bli kalt for DynamicTests.

3. Å skape DynamicTests

La oss først se på forskjellige måter å lage DynamicTests.

Eksemplene her er ikke dynamiske, men de vil gi et godt utgangspunkt for å skape virkelig dynamiske.

Vi skal lage en Samling av DynamicTest:

@TestFactory Collection dynamicTestsWithCollection () {return Arrays.asList (DynamicTest.dynamicTest ("Add test", () -> assertEquals (2, Math.addExact (1, 1))), DynamicTest.dynamicTest ("Multiply Test", ( ) -> assertEquals (4, Math.multiplyExact (2, 2)))); }

De @TestFabrikk metode forteller JUnit at dette er en fabrikk for å lage dynamiske tester. Som vi ser, returnerer vi bare en Samling av DynamicTest. Hver av DynamicTest består av to deler, navnet på testen eller visningsnavnet, og en Kjørbar.

Utgangen vil inneholde visningsnavnet som vi har sendt til de dynamiske testene:

Legg til test (dynamicTestsWithCollection ()) Multiply Test (dynamicTestsWithCollection ())

Den samme testen kan endres for å returnere en Iterabel, Iterator, eller a Strøm:

@TestFactory Iterable dynamicTestsWithIterable () {return Arrays.asList (DynamicTest.dynamicTest ("Add test", () -> assertEquals (2, Math.addExact (1, 1))), DynamicTest.dynamicTest ("Multiply Test", ( ) -> assertEquals (4, Math.multiplyExact (2, 2)))); } @TestFactory Iterator dynamicTestsWithIterator () {return Arrays.asList (DynamicTest.dynamicTest ("Add test", () -> assertEquals (2, Math.addExact (1, 1))), DynamicTest.dynamicTest ("Multiply Test", () -> assertEquals (4, Math.multiplyExact (2, 2)))) .iterator (); } @TestFactory Strøm dynamicTestsFromIntStream () {return IntStream.iterate (0, n -> n + 2) .limit (10) .mapToObj (n -> DynamicTest.dynamicTest ("test" + n, () -> assertTrue (n % 2 == 0))); }

Vær oppmerksom på at hvis @TestFabrikk returnerer a Strøm, så lukkes den automatisk når alle testene er utført.

Resultatet vil være stort sett det samme som det første eksemplet. Den vil inneholde visningsnavnet som vi overfører til den dynamiske testen.

4. Opprette en Strøm av DynamicTests

For demonstrasjonsformål, vurder a DomainNameResolver som returnerer en IP-adresse når vi sender domenenavnet som input.

For enkelhets skyld, la oss ta en titt på det høye nivåskjelettet til fabrikkmetoden vår:

@TestFactory Strøm dynamicTestsFromStream () {// prøveinngang og utgangsliste inputList = Arrays.asList ("www.somedomain.com", "www.anotherdomain.com", "www.yetanotherdomain.com"); Liste outputList = Arrays.asList ("154.174.10.56", "211.152.104.132", "178.144.120.156"); // inngangsgenerator som genererer innganger ved hjelp av inputList /*...code her ... * / // en visningsgenerator som lager et // annet navn basert på input /*... code her ... * / // testutføreren, som faktisk har // logikken til å utføre testsaken /*...-koden her ... * / // kombinere alt og returnere en strøm av DynamicTest /*...-kode her ... * /}

Det er ikke mye kode relatert til DynamicTest her bortsett fra @TestFabrikk kommentar, som vi allerede er kjent med.

De to ArrayLists vil bli brukt som input til DomainNameResolver og henholdsvis forventet produksjon.

La oss nå ta en titt på inngangsgeneratoren:

Iterator inputGenerator = inputList.iterator ();

Inngangsgeneratoren er ingenting annet enn en Iterator av String. Den bruker vår inputList og returnerer domenenavnet en etter en.

Visningsgeneratoren er ganske enkel:

Funksjon displayNameGenerator = (input) -> "Løsning:" + input;

Oppgaven til en visningsnavngenerator er bare å gi et visningsnavn for testsaken som skal brukes i JUnit-rapporter eller JUnit-fanen i IDE.

Her bruker vi bare domenenavnet til å generere unike navn for hver test. Det er ikke nødvendig å opprette unike navn, men det vil hjelpe i tilfelle feil. Etter å ha dette, vil vi kunne fortelle domenenavnet testsaken mislyktes for.

La oss nå ta en titt på den sentrale delen av testen vår - testutførelseskoden:

DomainNameResolver resolver = new DomainNameResolver (); ThrowingConsumer testExecutor = (input) -> {int id = inputList.indexOf (input); assertEquals (outputList.get (id), resolver.resolveDomain (input)); };

Vi har brukt Kaster forbruker, hvilken er en @FunctionalInterface for å skrive prøvesaken. For hver inngang som genereres av datageneratoren, henter vi forventet produksjon fra outputList og den faktiske produksjonen fra en forekomst av DomainNameResolver.

Nå er den siste delen ganske enkelt å montere alle brikkene og komme tilbake som en Strøm av DynamicTest:

returner DynamicTest.stream (inputGenerator, displayNameGenerator, testExecutor);

Det er det. Når du kjører testen, vises rapporten som inneholder navnene som er definert av vår visningsgenerator:

Løsning: www.somedomain.com (dynamicTestsFromStream ()) Løsning: www.anotherdomain.com (dynamicTestsFromStream ()) Løsning: www.yetanotherdomain.com (dynamicTestsFromStream ())

5. Forbedre DynamicTest Bruke Java 8-funksjoner

Testfabrikken skrevet i forrige del kan forbedres drastisk ved å bruke funksjonene i Java 8. Den resulterende koden vil være mye renere og kan skrives i et mindre antall linjer:

@TestFactory Strøm dynamicTestsFromStreamInJava8 () {DomainNameResolver resolver = new DomainNameResolver (); List domainNames = Arrays.asList ("www.somedomain.com", "www.anotherdomain.com", "www.yetanotherdomain.com"); Liste outputList = Arrays.asList ("154.174.10.56", "211.152.104.132", "178.144.120.156"); return inputList.stream () .map (dom -> DynamicTest.dynamicTest ("Resolving:" + dom, () -> {int id = inputList.indexOf (dom); assertEquals (outputList.get (id), resolver.resolveDomain) (dom));})); }

Ovennevnte kode har samme effekt som den vi så i forrige avsnitt. De inputList.stream (). kart () gir strømmen av innganger (inngangsgenerator). Det første argumentet til dynamicTest () er visningsgeneratoren vår (“Løsning:” + dom) mens det andre argumentet, a lambda, er testutføreren vår.

Utgangen vil være den samme som den fra forrige avsnitt.

6. Ytterligere eksempel

I dette eksemplet undersøker vi kraften til de dynamiske testene for å filtrere inngangene basert på testtilfellene:

@TestFactory Strøm dynamicTestsForEmployeeWorkflows () {List inputList = Arrays.asList (ny ansatt (1, "Fred"), ny ansatt (2), ny ansatt (3, "John")); EmployeeDao dao = ny EmployeeDao (); Stream saveEmployeeStream = inputList.stream () .map (emp -> DynamicTest.dynamicTest ("saveEmployee:" + emp.toString (), () -> {Employee returnerte = dao.save (emp.getId ()); assertEquals ( returnerte.getId (), emp.getId ());})); Stream saveEmployeeWithFirstNameStream = inputList.stream () .filter (emp ->! Emp.getFirstName (). IsEmpty ()) .map (emp -> DynamicTest.dynamicTest ("saveEmployeeWithName" + emp.toString (), () -> { Ansatt returnerte = dao.save (emp.getId (), emp.getFirstName ()); assertEquals (returnert.getId (), emp.getId ()); assertEquals (returnert.getFirstName (), emp.getFirstName ()); })); returner Stream.concat (saveEmployeeStream, saveEmployeeWithFirstNameStream); }

De lagre (Lang) metoden trenger bare Ansatt ID. Derfor bruker den alle Ansatt tilfeller. De lagre (lang, streng) metoden trenger fornavn bortsett fra Ansatt ID. Derfor filtrerer den ut Ansatt tilfeller uten fornavn.

Til slutt kombinerer vi begge strømmer og returnerer alle testene som en enkelt Strøm.

La oss nå se på utdataene:

saveEmployee: Employee [id = 1, firstName = Fred] (dynamicTestsForEmployeeWorkflows ()) saveEmployee: Employee [id = 2, firstName =] (dynamicTestsForEmployeeWorkflows ()) saveEmployee: Employee [id = 3, firstName = John] (dynamicTestsForEmploWee) saveEmployeeWithNameEmployee [id = 1, firstName = Fred] (dynamicTestsForEmployeeWorkflows ()) saveEmployeeWithNameEmployee [id = 3, firstName = John] (dynamicTestsForEmployeeWorkflows ())

7. Konklusjon

De parametriserte testene kan erstatte mange av eksemplene i denne artikkelen. Imidlertid skiller de dynamiske testene seg fra de parametriserte testene, ettersom de støtter full testsyklus, mens parametriserte tester ikke gjør det.

Videre gir dynamiske tester mer fleksibilitet når det gjelder hvordan inngangen genereres og hvordan testene utføres.

JUnit 5 foretrekker utvidelser fremfor funksjoner. Som et resultat er hovedmålet med dynamiske tester å gi et utvidelsespunkt for tredjeparts rammer eller utvidelser.

Du kan lese mer om andre funksjoner i JUnit 5 i vår artikkel om gjentatte tester i JUnit 5.

Ikke glem å sjekke ut hele kildekoden til denne artikkelen på GitHub.


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