Gids voor dynamische tests in Junit 5

1. Overzicht

Dynamisch testen is een nieuw programmeermodel dat in JUnit 5 is geïntroduceerd. In dit artikel gaan we bekijken wat dynamische tests precies zijn en hoe we ze kunnen maken.

Als JUnit 5 helemaal nieuw voor je is, wil je misschien de preview van JUnit 5 en onze primaire gids bekijken.

2. Wat is een DynamicTest?

De standaardtests geannoteerd met @Test annotaties zijn statische tests die volledig zijn gespecificeerd tijdens het compileren. EEN DynamicTest is een test die tijdens runtime wordt gegenereerd. Deze tests worden gegenereerd door een fabrieksmethode die is geannoteerd met de @TestFactory annotatie.

EEN @TestFactory methode moet een Stroom, Verzameling, Herhaalbaar, of Iterator van DynamicTest gevallen. Als u iets anders retourneert, krijgt u een JUnitException aangezien de ongeldige retourtypen niet kunnen worden gedetecteerd tijdens het compileren. Afgezien hiervan, een @TestFactory methode kan niet stati zijnc of privaat.

De DynamicTests worden anders uitgevoerd dan de standaard @Tests en ondersteunen geen callbacks voor de levenscyclus. Betekenis, de @BeforeEach en de @Na elke methoden worden niet aangeroepen voor de DynamicTests.

3. Creëren DynamicTests

Laten we eerst eens kijken naar verschillende manieren om te creëren DynamicTests.

De voorbeelden hier zijn niet dynamisch van aard, maar ze bieden een goed startpunt voor het creëren van echt dynamische voorbeelden.

We gaan een Verzameling van DynamicTest:

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

De @TestFactory methode vertelt JUnit dat dit een fabriek is voor het maken van dynamische tests. Zoals we kunnen zien, retourneren we alleen een Verzameling van DynamicTest. Elk van de DynamicTest bestaat uit twee delen: de naam van de test of de weergavenaam en een Uitvoerbaar.

De uitvoer bevat de weergavenaam die we hebben doorgegeven aan de dynamische tests:

Test toevoegen (dynamicTestsWithCollection ()) Multiply Test (dynamicTestsWithCollection ())

Dezelfde test kan worden gewijzigd om een Herhaalbaar, Iterator, of een Stroom:

@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 ("Test toevoegen", () -> assertEquals (2, Math.addExact (1, 1))), DynamicTest.dynamicTest ("Multiply Test", () -> assertEquals (4, Math.multiplyExact (2, 2)))) .iterator (); } @TestFactory Stream dynamicTestsFromIntStream () {return IntStream.iterate (0, n -> n + 2) .limit (10) .mapToObj (n -> DynamicTest.dynamicTest ("test" + n, () -> assertTrue (n % 2 == 0))); }

Houd er rekening mee dat als het @TestFactory geeft een terug Stroom, dan wordt het automatisch gesloten zodra alle tests zijn uitgevoerd.

De uitvoer zal vrijwel hetzelfde zijn als in het eerste voorbeeld. Het bevat de weergavenaam die we doorgeven aan de dynamische test.

4. Een Stroom van DynamicTests

Overweeg voor de demonstratiedoeleinden een DomainNameResolver die een IP-adres retourneert wanneer we de domeinnaam als invoer doorgeven.

Laten we voor de eenvoud eens kijken naar het skelet op hoog niveau van onze fabrieksmethode:

@TestFactory Stream dynamicTestsFromStream () {// voorbeeld invoer- en uitvoerlijst inputList = Arrays.asList ("www.somedomain.com", "www.anotherdomain.com", "www.yetanotherdomain.com"); Lijst outputList = Arrays.asList ("154.174.10.56", "211.152.104.132", "178.144.120.156"); // inputgenerator die invoer genereert met inputList /*...code hier ... * / // een weergavenaamgenerator die een // andere naam maakt op basis van de invoer /*...code hier ... * / // de testuitvoerder, die eigenlijk de // logica heeft om de testcase /*...code hier uit te voeren ... * / // alles combineren en een Stream of DynamicTest /*...code hier teruggeven ... * /}

Er is niet veel code gerelateerd aan DynamicTest hier afgezien van de @TestFactory annotatie, waarmee we al bekend zijn.

De twee ArrayLists wordt gebruikt als invoer voor DomainNameResolver en verwachte output.

Laten we nu eens kijken naar de input-generator:

Iterator inputGenerator = inputList.iterator ();

De input-generator is niets anders dan een Iterator van Draad. Het gebruikt onze inputList en geeft de domeinnaam een ​​voor een terug.

De weergavenaamgenerator is vrij eenvoudig:

Function displayNameGenerator = (input) -> "Oplossen:" + input;

De taak van een weergavenaamgenerator is alleen om een ​​weergavenaam op te geven voor de testcase die zal worden gebruikt in JUnit-rapporten of het JUnit-tabblad van onze IDE.

Hier gebruiken we alleen de domeinnaam om voor elke test unieke namen te genereren. Het is niet vereist om unieke namen te maken, maar het zal helpen in het geval van een storing. Als we dit hebben, kunnen we de domeinnaam vertellen waarvoor de testcase is mislukt.

Laten we nu eens kijken naar het centrale deel van onze test - de testuitvoeringscode:

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

We hebben de ThrowingConsumer, wat een is @FunctioneleInterface voor het schrijven van de testcase. Voor elke invoer die door de gegevensgenerator wordt gegenereerd, halen we de verwachte uitvoer op van de outputList en de daadwerkelijke uitvoer van een exemplaar van DomainNameResolver.

Nu is het laatste deel gewoon om alle stukken in elkaar te zetten en terug te keren als een Stroom van DynamicTest:

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

Dat is het. Als u de test uitvoert, wordt het rapport weergegeven met de namen die zijn gedefinieerd door onze weergavenaamgenerator:

Oplossen: www.somedomain.com (dynamicTestsFromStream ()) Oplossen: www.anotherdomain.com (dynamicTestsFromStream ()) Oplossen: www.yetanotherdomain.com (dynamicTestsFromStream ())

5. Verbetering van het DynamicTest Java 8-functies gebruiken

De testfabriek die in de vorige sectie is geschreven, kan drastisch worden verbeterd door de functies van Java 8 te gebruiken. De resulterende code zal veel schoner zijn en kan in een kleiner aantal regels worden geschreven:

@TestFactory Stream dynamicTestsFromStreamInJava8 () {DomainNameResolver resolver = nieuwe DomainNameResolver (); List domainNames = Arrays.asList ("www.somedomain.com", "www.anotherdomain.com", "www.yetanotherdomain.com"); Lijst 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));})); }

De bovenstaande code heeft hetzelfde effect als degene die we in de vorige sectie hebben gezien. De inputList.stream (). map () levert de stroom van inputs (input generator). Het eerste argument om dynamicTest () is onze generator voor weergavenamen ("Resolving:" + dom) terwijl het tweede argument, a lambda, is onze testuitvoerder.

De uitvoer is hetzelfde als die uit de vorige sectie.

6. Bijkomend voorbeeld

In dit voorbeeld onderzoeken we de kracht van de dynamische tests om de invoer te filteren op basis van de testgevallen:

@TestFactory Stream dynamicTestsForEmployeeWorkflows () {List inputList = Arrays.asList (nieuwe werknemer (1, "Fred"), nieuwe werknemer (2), nieuwe werknemer (3, "John")); Employeebao dao = nieuwe Employeebao (); Stream saveEmployeeStream = inputList.stream () .map (emp -> DynamicTest.dynamicTest ("saveEmployee:" + emp.toString (), () -> {Medewerker geretourneerd = dao.save (emp.getId ()); assertEquals ( geretourneerd.getId (), emp.getId ());})); Stroom saveEmployeeWithFirstNameStream = inputList.stream () .filter (emp ->! Emp.getFirstName (). IsEmpty ()) .map (emp -> DynamicTest.dynamicTest ("saveEmployeeWithName" + emp.toString (), () -> { Werknemer geretourneerd = dao.save (emp.getId (), emp.getFirstName ()); assertEquals (geretourneerde.getId (), emp.getId ()); assertEquals (geretourneerde.getFirstName (), emp.getFirstName ()); })); retour Stream.concat (saveEmployeeStream, saveEmployeeWithFirstNameStream); }

De opslaan (lang) methode heeft alleen de medewerker-ID. Daarom gebruikt het alle Werknemer gevallen. De save (Long, String) methode behoeften Voornaam los van de medewerker-ID. Daarom filtert het de Werknemer instanties zonder Voornaam.

Ten slotte combineren we beide streams en retourneren we alle tests als één Stroom.

Laten we nu eens kijken naar de uitvoer:

saveEmployee: Werknemer [id = 1, firstName = Fred] (dynamicTestsForEmployeeWorkflows ()) saveEmployee: Werknemer [id = 2, firstName =] (dynamicTestsForEmployeeWorkflows ()) saveEmployee: Werknemer [id = 3, firstName = John] (dynamicTestsForEmployee )Workflows (dynamicTestsForEmployee) saveEmployeeWithNameEmployee [id = 1, firstName = Fred] (dynamicTestsForEmployeeWorkflows ()) saveEmployeeWithNameEmployee [id = 3, firstName = John] (dynamicTestsForEmployeeWorkflows ())

7. Conclusie

De geparametriseerde tests kunnen veel van de voorbeelden in dit artikel vervangen. De dynamische tests verschillen echter van de geparametriseerde tests omdat ze de volledige testlevenscyclus ondersteunen, terwijl geparametriseerde tests dat niet doen.

Bovendien bieden dynamische tests meer flexibiliteit met betrekking tot hoe de invoer wordt gegenereerd en hoe de tests worden uitgevoerd.

JUnit 5 geeft de voorkeur aan uitbreidingen boven het functieprincipe. Als gevolg hiervan is het belangrijkste doel van dynamische tests om een ​​uitbreidingspunt te bieden voor frameworks of uitbreidingen van derden.

U kunt meer lezen over andere functies van JUnit 5 in ons artikel over herhaalde tests in JUnit 5.

Vergeet niet de volledige broncode van dit artikel op GitHub te lezen.


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