Vodič za dinamičke testove u 5. lipnju

1. Pregled

Dinamičko testiranje novi je programski model uveden u JUnit 5. U ovom ćemo članku pogledati što su točno dinamički testovi i kako ih stvoriti.

Ako ste potpuno novi u JUnit 5, možda biste željeli provjeriti pregled JUnit 5 i naš primarni vodič.

2. Što je a DynamicTest?

Standardni testovi označeni s @Test anotacija su statički testovi koji su u potpunosti navedeni u vrijeme sastavljanja. A DynamicTest je test generiran tijekom izvođenja. Ti se testovi generiraju tvorničkom metodom označeni s @TestFactory bilješka.

A @TestFactory metoda mora vratiti a Stream, Kolekcija, Iterativ, ili Iterator od DynamicTest instance. Vraćanje bilo čega drugog rezultirat će a JUnitException budući da se neispravne vrste povratka ne mogu otkriti u vrijeme sastavljanja. Osim ovoga, a @TestFactory metoda ne može biti statičnac ili privatni.

The DynamicTestIzvode se drugačije od standardnih @Testi ne podržavaju povratne pozive u životnom ciklusu. Znači, @BeforeEach i @AfterEach metode neće biti pozvane za DynamicTests.

3. Stvaranje DynamicTests

Prvo, pogledajmo različite načine stvaranja DynamicTests.

Primjeri ovdje nisu dinamične prirode, ali pružit će dobro polazište za stvaranje uistinu dinamičnih.

Stvorit ćemo a Kolekcija od DynamicTest:

@TestFactory Collection dynamicTestsWithCollection () {return Arrays.asList (DynamicTest.dynamicTest ("Dodaj test", () -> assertEquals (2, Math.addExact (1, 1))), DynamicTest.dynamicTest ("Množenje testa", ( ) -> assertEquals (4, Math.multiplyExact (2, 2)))); }

The @TestFactory metoda govori JUnit-u da je ovo tvornica za izradu dinamičkih testova. Kao što vidimo, vraćamo samo a Kolekcija od DynamicTest. Svaki od DynamicTest sastoji se od dva dijela, naziva testa ili naziva za prikaz i an Izvršno.

Izlaz će sadržavati ime za prikaz koje smo proslijedili dinamičkim testovima:

Dodaj test (dynamicTestsWithCollection ()) Množi test (dynamicTestsWithCollection ())

Isti test može se izmijeniti da bi vratio Iterativ, Iterator, ili a Stream:

@TestFactory Iterable dynamicTestsWithIterable () {return Arrays.asList (DynamicTest.dynamicTest ("Dodaj test", () -> assertEquals (2, Math.addExact (1, 1))), DynamicTest.dynamicTest ("Množenje testa", ( ) -> assertEquals (4, Math.multiplyExact (2, 2)))); } @TestFactory Iterator dynamicTestsWithIterator () {return Arrays.asList (DynamicTest.dynamicTest ("Dodaj test", () -> assertEquals (2, Math.addExact (1, 1))), DynamicTest.dynamicTest ("Množenje testa", () -> 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))); }

Imajte na umu da ako @TestFactory vraća a Stream, tada će se automatski zatvoriti nakon što se izvrše svi testovi.

Izlaz će biti približno isti kao i prvi primjer. Sadržat će ime za prikaz koje prosljeđujemo na dinamički test.

4. Stvaranje a Stream od DynamicTests

U svrhu demonstracije razmotrite a DomainNameResolver koji vraća IP adresu kada naziv domene proslijedimo kao ulaz.

Radi jednostavnosti, pogledajmo kostur visoke razine naše tvorničke metode:

@TestFactory Stream dynamicTestsFromStream () {// uzorak popisa ulaza i izlaza inputList = Arrays.asList ("www.somedomain.com", "www.anotherdomain.com", "www.yetanotherdomain.com"); Popis outputList = Arrays.asList ("154.174.10.56", "211.152.104.132", "178.144.120.156"); // generator unosa koji generira ulaze pomoću inputList /*...code ovdje ... * / // generator imena za prikaz koji stvara // različito ime na temelju ulaza /*...code ovdje ... * / // izvršitelj testa, koji zapravo ima // logiku za izvršavanje testnog slučaja /*...code ovdje ... * / // kombinira sve i ovdje vraća stream DynamicTest /*...code ... * /}

Nema puno koda povezanog sa DynamicTest ovdje osim @TestFactory napomena, koja nam je već poznata.

Dva ArrayLists će se koristiti kao ulaz za DomainNameResolver odnosno očekivani izlaz.

Pogledajmo sada ulazni generator:

Iterator inputGenerator = inputList.iterator ();

Ulazni generator nije ništa drugo doli Iterator od Niz. Koristi naše inputList i vraća naziv domene jedan po jedan.

Generator imena za prikaz prilično je jednostavan:

Funkcija displayNameGenerator = (ulaz) -> "Rješavanje:" + ulaz;

Zadatak generatora imena za prikaz je samo pružiti ime za prikaz za testni slučaj koji će se koristiti u izvješćima JUnit ili kartici JUnit našeg IDE-a.

Ovdje samo koristimo ime domene za generiranje jedinstvenih imena za svaki test. Nije potrebno stvarati jedinstvena imena, ali pomoći će u slučaju neuspjeha. Nakon toga moći ćemo reći ime domene za koju test nije uspio.

Sada ćemo pogledati središnji dio našeg testa - kôd za izvršavanje testa:

Razrješivač DomainNameResolver = novi DomainNameResolver (); ThrowingConsumer testExecutor = (input) -> {int id = inputList.indexOf (input); assertEquals (outputList.get (id), resolver.resolveDomain (input)); };

Koristili smo ThrowingConsumer, što je a @FunctionalInterface za pisanje test slučaja. Za svaki ulaz koji generira generator podataka dohvaćamo očekivani izlaz iz outputList i stvarni izlaz iz instance DomainNameResolver.

Sada je posljednji dio jednostavno sastaviti sve dijelove i vratiti se kao a Stream od DynamicTest:

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

To je to. Pokretanje testa prikazat će izvještaj koji sadrži imena definirana od strane našeg generatora imena za prikaz:

Rješavanje: www.somedomain.com (dynamicTestsFromStream ()) Rješavanje: www.anotherdomain.com (dynamicTestsFromStream ()) Rješavanje: www.yetanotherdomain.com (dynamicTestsFromStream ())

5. Poboljšanje DynamicTest Korištenje značajki Java 8

Tvornica testova napisana u prethodnom odjeljku može se drastično poboljšati upotrebom značajki Java 8. Rezultirajući kôd bit će mnogo čišći i može se napisati u manji broj redaka:

@TestFactory Stream dynamicTestsFromStreamInJava8 () {Razriješivač DomainNameResolver = novi DomainNameResolver (); Popis domenaName = Arrays.asList ("www.somedomain.com", "www.anotherdomain.com", "www.yetanotherdomain.com"); Popis outputList = Arrays.asList ("154.174.10.56", "211.152.104.132", "178.144.120.156"); vrati inputList.stream () .map (dom -> DynamicTest.dynamicTest ("Rješavanje:" + dom, () -> {int id = inputList.indexOf (dom); assertEquals (outputList.get (id), resolver.resolveDomain (dom));})); }

Gornji kod ima isti učinak kao onaj koji smo vidjeli u prethodnom odjeljku. The inputList.stream (). map () osigurava tok ulaza (ulazni generator). Prvi argument za dynamicTest () je naš generator imena za prikaz ("Rješavanje:" + dom) dok je drugi argument, a lambda, je naš izvršitelj testa.

Izlaz će biti isti kao onaj iz prethodnog odjeljka.

6. Dodatni primjer

U ovom primjeru dalje istražujemo snagu dinamičkih testova za filtriranje ulaza na temelju test slučajeva:

@TestFactory Stream dynamicTestsForEfficieeWorkflows () {List inputList = Arrays.asList (novi zaposlenik (1, "Fred"), novi zaposlenik (2), novi zaposlenik (3, "John")); EmployeeDao dao = novi EmployeeDao (); Stream saveEfficieeStream = inputList.stream () .map (emp -> DynamicTest.dynamicTest ("saveEfficiee:" + emp.toString (), () -> {Zaposlenik se vratio = dao.save (emp.getId ()); assertEquals ( return.getId (), emp.getId ());})); Stream saveEfficieeWithFirstNameStream = inputList.stream () .filter (emp ->! Emp.getFirstName (). IsEmpty ()) .map (emp -> DynamicTest.dynamicTest ("saveEfficieeWithName" + emp.toString (), () -> {{ Zaposlenik se vratio = dao.save (emp.getId (), emp.getFirstName ()); assertEquals (return.getId (), emp.getId ()); assertEquals (return.getFirstName (), emp.getFirstName ()); })); vratiti Stream.concat (saveEfficieeStream, saveEfficieeWithFirstNameStream); }

The spremi (dugo) metoda treba samo id zaposlenika. Dakle, koristi sve Zaposlenik instance. The spremi (Long, String) metoda treba ime osim id zaposlenika. Stoga filtrira Zaposlenik instance bez ime.

Konačno, kombiniramo oba toka i vraćamo sve testove kao jedan Stream.

Pogledajmo sada izlaz:

saveEfficiee: Zaposlenik [id = 1, firstName = Fred] (dynamicTestsForEfficieeWorkflows ()) saveE Employee: Employee [id = 2, firstName =] (dynamicTestsForEfficieeWorkflows ()) saveEfficiee: Employee [id = 3, firstName = John] (dynamicTestsForbage (e) saveEfficieeWithNameEfficiee [id = 1, firstName = Fred] (dynamicTestsForEfficieeWorkflows ()) saveEfficieeWithNameEfficiee [id = 3, firstName = John] (dynamicTestsForEfficieeWorkflows ())

7. Zaključak

Parametrizirani testovi mogu zamijeniti mnoge primjere u ovom članku. Međutim, dinamički testovi razlikuju se od parametriziranih testova jer podržavaju puni životni ciklus ispitivanja, dok parametrizirani testovi ne.

Štoviše, dinamički testovi pružaju veću fleksibilnost u pogledu načina na koji se generira ulaz i kako se testovi izvršavaju.

JUnit 5 preferira proširenja u odnosu na princip značajki. Kao rezultat toga, glavni cilj dinamičkih testova je pružiti točku proširenja za okvire ili proširenja treće strane.

Više o ostalim značajkama JUnit 5 možete pročitati u našem članku o ponovljenim testovima u JUnit 5.

Ne zaboravite pogledati puni izvorni kod ovog članka na GitHubu.