En hurtig JUnit vs TestNG-sammenligning

1. Oversigt

JUnit og TestNG er utvivlsomt de to mest populære rammer for enhedstest i Java-økosystemet. Mens JUnit inspirerer TestNG selv, giver det dets karakteristiske træk, og i modsætning til JUnit fungerer det til funktionelle og højere testniveauer.

I dette indlæg, Vi diskuterer og sammenligner disse rammer ved at dække deres funktioner og almindelige brugssager.

2. Testopsætning

Mens vi skriver testtilfælde, er vi ofte nødt til at udføre nogle konfigurations- eller initialiseringsinstruktioner inden testudførelser og også nogle oprydninger efter afslutningen af ​​testene. Lad os evaluere disse i begge rammer.

JUnit tilbyder initialisering og oprydning på to niveauer før og efter hver metode og klasse. Vi har @BeforeEach, @AfterEach bemærkninger på metodeniveau og @BeforeAll og @Trods alt på klasseniveau:

offentlig klasse SummationServiceTest {private statiske listenumre; @BeforeAll offentlig statisk ugyldighed initialiseres () {numbers = new ArrayList (); } @AfterAll offentlig statisk ugyldighed tearDown () {numbers = null; } @BeforeEach public void runBeforeEachTest () {numbers.add (1); numbers.add (2); numbers.add (3); } @AfterEach public void runAfterEachTest () {numbers.clear (); } @Test offentlig ugyldighed givenNumbers_sumEquals_thenCorrect () {int sum = numbers.stream (). Reducer (0, Integer :: sum); assertEquals (6, sum); }}

Bemærk, at dette eksempel bruger JUnit 5. I den forrige JUnit 4-version skal vi bruge @Før og @Efter kommentarer, der svarer til @BeforeEach og @AfterEach. Ligeledes, @BeforeAll og @Trods alt er erstatninger for JUnit 4 @BeforeClass og @Efter skole.

Svarende til JUnit, TestNG giver også initialisering og oprydning på metode- og klasseniveau. Mens @BeforeClass og @Efter skole forblive de samme på klasseniveau, metodeniveau-kommentarerne er @Før metode og @AfterMethod:

@BeforeClass public void initialize () {numbers = new ArrayList (); } @AfterClass public void tearDown () {numbers = null; } @BeforeMethod offentlig ugyldig runBeforeEachTest () {numbers.add (1); numbers.add (2); numbers.add (3); } @AfterMethod offentlig ugyldig runAfterEachTest () {numbers.clear (); }

TestNG tilbyder også, @BeforeSuite, @AfterSuite, @BeforeGroup og @AfterGroup kommentarer til konfigurationer på suite- og gruppeniveau:

@BeforeGroups ("positive_tests") offentlige ugyldige runBeforeEachGroup () {numbers.add (1); numbers.add (2); numbers.add (3); } @AfterGroups ("negative_tests") offentlig ugyldig runAfterEachGroup () {numbers.clear (); }

Vi kan også bruge @BeforeTest og @AfterTest hvis vi har brug for nogen konfiguration før eller efter testsager inkluderet i tag i TestNG XML-konfigurationsfil:

Bemærk, at erklæringen om @BeforeClass og @Efter skole metoden skal være statisk i JUnit. Til sammenligning har TestNG-metodedeklaration ikke disse begrænsninger.

3. Ignorer test

Begge rammer understøtter ignorering af testsager, selvom de gør det helt anderledes. JUnit tilbyder @Ignorere kommentar:

@Ignorer @ Test offentlig ugyldighed givenNumbers_sumEquals_thenCorrect () {int sum = numbers.stream (). Reducer (0, Integer :: sum); Assert.assertEquals (6, sum); }

mens TestNG bruger @Prøve med en parameter "aktiveret" med en boolsk værdi rigtigt eller falsk:

@Test (aktiveret = falsk) offentlig ugyldighed givenNumbers_sumEquals_thenCorrect () {int sum = numbers.stream.reduce (0, Integer :: sum); Assert.assertEquals (6, sum); }

4. Køre tests sammen

At køre tests sammen som en samling er mulig i begge JUnit og TestNG, men de gør det på forskellige måder.

Vi kan bruge @RunWith,@SelectPackagesog @SelectClasses kommentarer til gruppering af testsager og køre dem som en suite i JUnit 5. En suite er en samling af testsager, som vi kan gruppere og køre som en enkelt test.

Hvis vi ønsker at gruppere test tilfælde af forskellige pakker til at køre sammen inden for en Suite vi har brug for @SelectPackages kommentar:

@RunWith (JUnitPlatform.class) @SelectPackages ({"org.baeldung.java.suite.childpackage1", "org.baeldung.java.suite.childpackage2"}) offentlig klasse SelectPackagesSuiteUnitTest {}

Hvis vi ønsker, at specifikke testklasser skal køre sammen, JUnit 5 giver fleksibilitet igennem @SelectClasses:

@RunWith (JUnitPlatform.class) @SelectClasses ({Class1UnitTest.class, Class2UnitTest.class}) offentlig klasse SelectClassesSuiteUnitTest {}

Tidligere brugt JUnit 4, vi opnåede gruppering og kørsel af flere tests sammen ved hjælp af @Suite kommentar:

@RunWith (Suite.class) @ Suite.SuiteClasses ({RegistrationTest.class, SignInTest.class}) offentlig klasse SuiteTest {}

I TestNG kan vi gruppere tests ved hjælp af en XML-fil:

Dette indikerer Registreringstest og SignInTest vil løbe sammen.

Bortset fra grupperingsklasser kan TestNG også gruppere metoder ved hjælp af @Test (grupper = ”groupName”) kommentar:

@Test (grupper = "regression") offentlig ugyldighed givenNegativeNumber_sumLessthanZero_thenCorrect () {int sum = numbers.stream (). Reducer (0, Integer :: sum); Assert.assertTrue (sum <0); }

Lad os bruge en XML til at udføre grupperne:

Dette udfører testmetoden mærket med gruppen regression.

5. Test af undtagelser

Funktionen til test af undtagelser ved hjælp af annoteringer er tilgængelig i både JUnit og TestNG.

Lad os først oprette en klasse med en metode, der giver en undtagelse:

public class Calculator {public double divide (double a, double b) {if (b == 0) {throw new DivideByZeroException ("Divider kan ikke være lig med nul!"); } returnere a / b; }}

I JUnit 5 vi kan bruge hævder kaster API til at teste undtagelser:

@Test offentligt ugyldigt nårDividerIsZero_thenDivideByZeroExceptionIsThrown () {Calculator calculator = new Calculator (); assertThrows (DivideByZeroException.class, () -> calculator.divide (10, 0)); }

I JUnit 4, vi kan opnå dette ved at bruge @Test (forventet = DivideByZeroException.class) over test-API'et.

Og med TestNG kan vi også implementere det samme:

@Test (forventetExceptions = ArithmeticException.class) offentlig ugyldighed givenNumber_whenThrowsException_thenCorrect () {int i = 1/0; }

Denne funktion indebærer, hvilken undtagelse der kastes fra et stykke kode, det er en del af en test.

6. Parameteriserede tests

Parameteriserede enhedstest er nyttige til test af den samme kode under flere forhold. Ved hjælp af parametriserede enhedstest kan vi oprette en testmetode, der henter data fra en datakilde. Hovedideen er at gøre enhedstestmetoden genanvendelig og at teste med et andet sæt input.

I JUnit 5, har vi fordelen ved, at testmetoder bruger dataargumenter direkte fra den konfigurerede kilde. Som standard giver JUnit 5 et par stykker kilde kommentarer som:

  • @ValueSource: vi kan bruge dette med en række værdier af typen Kort, Byte, Int, Lang, Flydende, Dobbelt, Char, og Snor:
@ParameterizedTest @ValueSource (strings = {"Hej", "Verden"}) ugyldigt givenString_TestNullOrNot (strengord) {assertNotNull (ord); }
  • @EnumSource - passerer Enum konstanter som parametre til testmetoden:
@ParameterizedTest @EnumSource (værdi = PizzaDeliveryStrategy.class, names = {"EXPRESS", "NORMAL"}) ugyldig givenEnum_TestContainsOrNot (PizzaDeliveryStrategy timeUnit) {assertTrue (EnumSet.of (PizzaDeliveryStrategy.trategy. ; }
  • @MethodSource - svurderer eksterne metoder, der genererer streams:
statisk stream wordDataProvider () {return Stream.of ("foo", "bar"); } @ParameterizedTest @MethodSource ("wordDataProvider") ugyldigt givenMethodSource_TestInputStream (String argument) {assertNotNull (argument); }
  • @CsvSource - bruger CSV-værdier som kilde til parametrene:
@ParameterizedTest @CsvSource ({"1, Car", "2, House", "3, Train"}) ugyldigt givetCSVSource_TestContent (int id, String word) {assertNotNull (id); assertNotNull (ord); }

På samme måde har vi andre kilder som @CsvFileSource hvis vi har brug for at læse en CSV-fil fra classpath og @ArgumentSource for at angive en brugerdefineret, genanvendelig Argumenter Udbyder.

I JUnit 4, skal testklassen kommenteres med @RunWith for at gøre det til en parametreret klasse og @Parameter for at bruge den betegne parameterværdierne for enhedstest.

I TestNG kan vi parametrere tests ved hjælp af @Parameter eller @DataProvider kommentarer. Når du bruger XML-filen, skal du kommentere testmetoden med @Parameter:

@Test @Parameters ({"værdi", "isEven"}) offentlig ugyldighed givenNumberFromXML_ifEvenCheckOK_thenCorrect (int værdi, boolsk isEven) {Assert.assertEquals (isEven, værdi% 2 == 0); }

og give dataene i XML-filen:

Mens brug af oplysninger i XML-filen er enkel og nyttig, i nogle tilfælde skal du muligvis give mere komplekse data.

Til dette kan vi bruge @DataProvider kommentar, som giver os mulighed for at kortlægge komplekse parametertyper til testmetoder.

Her er et eksempel på brug @DataProvider til primitive datatyper:

@DataProvider (name = "numbers") offentligt statisk objekt [] [] evenNumbers () {returner nyt objekt [] [] {{1, falsk}, {2, sandt}, {4, sandt}}; } @Test (dataProvider = "numbers") offentlig ugyldighed givenNumberFromDataProvider_ifEvenCheckOK_thenCorrect (heltal, forventet boolsk) {Assert.assertEquals (forventet, antal% 2 == 0); }

Og @DataProvider til genstande:

@Test (dataProvider = "numbersObject") offentlig ugyldighed givenNumberObjectFromDataProvider_ifEvenCheckOK_thenCorrect (EvenNumber number) {Assert.assertEquals (number.isEven (), number.getValue ()% 2 == 0); } @DataProvider (name = "numbersObject") public Object [] [] parameterProvider () {return new Object [] [] {{new EvenNumber (1, false)}, {new EvenNumber (2, true)}, {new EvenNumber (4, sandt)}}; }

På samme måde kan alle bestemte objekter, der skal testes, oprettes og returneres ved hjælp af dataudbyder. Det er nyttigt, når du integrerer med rammer som Spring.

Bemærk, at i TestNG siden @DataProvider metode behøver ikke at være statisk, vi kan bruge flere dataudbydermetoder i samme testklasse.

7. Test timeout

Tidsindstillet test betyder, at en testsag mislykkes, hvis udførelsen ikke er afsluttet inden for en bestemt specificeret periode. Både JUnit og TestNG support timeout-tests. I JUnit 5 vi kan skrive en timeout test som:

@Test offentlig ugyldighed givenExecution_takeMoreTime_thenFail () kaster InterruptedException {Assertions.assertTimeout (Duration.ofMillis (1000), () -> Thread.sleep (10000)); }

I JUnit 4 og TestNG kan vi den samme test ved hjælp af @Test (timeout = 1000)

@Test (timeOut = 1000) offentlig ugyldighed givenExecution_takeMoreTime_thenFail () {while (true); }

8. Afhængige tests

TestNG understøtter afhængighedstest. Dette betyder i et sæt testmetoder, at hvis den indledende test mislykkes, springes alle efterfølgende afhængige tests over og ikke markeres som mislykkede som i tilfældet med JUnit.

Lad os se på et scenario, hvor vi skal validere e-mail, og hvis det lykkes, fortsætter vi med at logge ind:

@Test offentlig ugyldighed givenEmail_ifValid_thenTrue () {boolean valid = email.contains ("@"); Assert.assertEquals (gyldig, sand); } @Test (afhængerOnMethods = {"givenEmail_ifValid_thenTrue"}) offentlig ugyldighed givenValidEmail_whenLoggedIn_thenTrue () {LOGGER.info ("E-mail {} gyldig >> logger ind", e-mail) }

9. Bestilling af testudførelse

Der er ingen defineret implicit rækkefølge, hvor testmetoder bliver udført i JUnit 4 eller TestNG. Metoderne påberåbes lige som returneret af Java Reflection API. Siden JUnit 4 bruger den en mere deterministisk, men ikke forudsigelig rækkefølge.

For at få mere kontrol vil vi kommentere testklassen med @FixMethodOrder kommentar og nævne en metodesorterer:

@FixMethodOrder (MethodSorters.NAME_ASCENDING) public class SortedTests {@Test public void a_givenString_whenChangedtoInt_thenTrue () {assertTrue (Integer.valueOf ("10") instance of Integer); } @Test offentligt ugyldigt b_givenInt_whenChangedtoString_thenTrue () {assertTrue (String.valueOf (10) instance of String); }}

Det MethodSorters.NAME_ASCENDING parameter sorterer metoderne efter metode navn er leksikografisk rækkefølge. Bortset fra denne sorterer har vi MethodSorter.DEFAULT og MethodSorter.JVM også.

Mens TestNG også giver et par måder at have kontrol i rækkefølgen på udførelse af testmetode. Vi leverer prioritet parameter i @Prøve kommentar:

@ Test (prioritet = 1) offentlig ugyldighed givenString_whenChangedToInt_thenCorrect () {Assert.assertTrue (Integer.valueOf ("10") instance of Integer); } @ Test (prioritet = 2) offentlig ugyldighed givenInt_whenChangedToString_thenCorrect () {Assert.assertTrue (String.valueOf (23) instance of String); }

Bemærk, at prioritet påberåber testmetoder baseret på prioritet, men garanterer ikke, at test i et niveau er afsluttet, før det næste prioritetsniveau påberåbes.

Undervejs når vi skriver funktionelle testsager i TestNG, kan vi have en indbyrdes afhængig test, hvor rækkefølgen af ​​udførelse skal være den samme for hver testkørsel. For at opnå det skal vi bruge afhænger af metoder parameter til @Prøve kommentar som vi så i det tidligere afsnit.

10. Navn på brugerdefineret test

Når vi kører en test, udskrives testklassen og navnet på testmetoden som standard i konsol eller IDE. JUnit 5 giver en unik funktion, hvor vi kan nævne brugerdefinerede beskrivende navne til klasse- og testmetoder ved hjælp af @DisplayName kommentar.

Denne kommentar giver ingen testfordele, men det giver også let at læse og forstå testresultater for en ikke-teknisk person:

@ParameterizedTest @ValueSource (strings = {"Hej", "Verden"}) @DisplayName ("Testmetode for at kontrollere, at input ikke er ugyldige") ugyldig givenString_TestNullOrNot (String word) {assertNotNull (word); }

Når vi kører testen, viser output displaynavnet i stedet for metodens navn.

Lige nu, i TestNG der er ingen måde at angive et brugerdefineret navn på.

11. Konklusion

Både JUnit og TestNG er moderne værktøjer til test i Java-økosystemet.

I denne artikel havde vi et hurtigt kig på forskellige måder at skrive test på med hver af disse to testrammer.

Implementeringen af ​​alle kodestykker kan findes i TestNG og junit-5 Github-projektet.