Een snelle vergelijking tussen JUnit en TestNG

1. Overzicht

JUnit en TestNG zijn ongetwijfeld de twee meest populaire frameworks voor unit-testing in het Java-ecosysteem. Hoewel JUnit TestNG zelf inspireert, biedt het zijn onderscheidende kenmerken, en in tegenstelling tot JUnit werkt het voor functionele en hogere testniveaus.

In dit bericht, we zullen deze frameworks bespreken en vergelijken door hun kenmerken en algemene use cases te bespreken.

2. Testopstelling

Tijdens het schrijven van testcases moeten we vaak enkele configuratie- of initialisatie-instructies uitvoeren voordat de test wordt uitgevoerd, en ook wat opschoning na voltooiing van tests. Laten we deze in beide kaders evalueren.

JUnit biedt initialisatie en opschoning op twee niveaus, voor en na elke methode en klasse. We hebben @BeforeEach, @Na elke annotaties op methodeniveau en @Voor alles en @Ten slotte op klasniveau:

openbare klasse SummationServiceTest {privé statische lijstnummers; @BeforeAll public static void initialize () {numbers = new ArrayList (); } @AfterAll openbare statische leegte tearDown () {nummers = null; } @BeforeEach openbare leegte runBeforeEachTest () {numbers.add (1); numbers.add (2); numbers.add (3); } @AfterEach openbare leegte runAfterEachTest () {numbers.clear (); } @Test openbare void givenNumbers_sumEquals_thenCorrect () {int sum = numbers.stream (). Reduceren (0, geheel getal :: som); assertEquals (6, som); }}

Merk op dat dit voorbeeld JUnit 5 gebruikt. In de vorige JUnit 4-versie zouden we de @Voordat en @Na annotaties die gelijk zijn aan @BeforeEach en @Na elke. Hetzelfde, @Voor alles en @Ten slotte zijn vervangingen voor JUnit 4's @Voor klas en @Na de les.

Net als bij JUnit, TestNG biedt ook initialisatie en opschoning op methode- en klassenniveau. Terwijl @Voor klas en @Na de les hetzelfde blijven op het klassenniveau, de annotaties op het methode-niveau zijn @BeforeMethod en @AfterMethod:

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

TestNG biedt ook, @BeforeSuite, @AfterSuite, @BeforeGroup en @AfterGroup annotaties, voor configuraties op suite- en groepsniveau:

@BeforeGroups ("positive_tests") openbare ongeldige runBeforeEachGroup () {numbers.add (1); numbers.add (2); numbers.add (3); } @AfterGroups ("negatieve_tests") openbare leegte runAfterEachGroup () {numbers.clear (); }

We kunnen ook de @BeforeTest en @AfterTest als we een configuratie nodig hebben voor of na testcases die zijn opgenomen in de tag in TestNG XML-configuratiebestand:

Merk op dat de verklaring van @Voor klas en @Na de les methode moet statisch zijn in JUnit. Ter vergelijking: de TestNG-methodedeclaratie heeft deze beperkingen niet.

3. Negeren van tests

Beide frameworks ondersteunen het negeren van testgevallen, hoewel ze het heel anders doen. JUnit biedt de @Negeren annotatie:

@Ignore @Test openbare ongeldige gegevenNumbers_sumEquals_thenCorrect () {int sum = numbers.stream (). Reduceren (0, geheel getal :: som); Assert.assertEquals (6, som); }

terwijl TestNG gebruikt @Test met een parameter “enabled” met een booleaanse waarde waar of false:

@Test (enabled = false) public void givenNumbers_sumEquals_thenCorrect () {int sum = numbers.stream.reduce (0, geheel getal :: som); Assert.assertEquals (6, som); }

4. Samen testen

Samen testen uitvoeren als collectie is in beide mogelijk JUnit en TestNG, maar ze doen het op verschillende manieren.

We kunnen de @Rennen met,@SelectPackages, en @SelectClasses annotaties om testcases te groeperen en ze als een suite uit te voeren in J Eenheid 5. Een suite is een verzameling testcases die we kunnen groeperen en als een enkele test kunnen uitvoeren.

Als we testgevallen van verschillende pakketten willen groeperen om samen te draaien binnen een Suite we hebben de @SelectPackages annotatie:

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

Als we willen dat specifieke testklassen samen worden uitgevoerd, J Eenheid 5 biedt de flexibiliteit door @SelectClasses:

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

Eerder gebruikend JUnit 4hebben we het groeperen bereikt en meerdere tests samen uitgevoerd met behulp van @Suite annotatie:

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

In TestNG kunnen we tests groeperen door een XML-bestand te gebruiken:

Dit wijst op RegistratieTest en SignInTest zullen samen rennen.

Behalve het groeperen van klassen, kan TestNG ook methoden groeperen met behulp van de @Test (groups = "groupName") annotatie:

@Test (groups = "regressie") public void givenNegativeNumber_sumLessthanZero_thenCorrect () {int sum = numbers.stream (). Reduce (0, geheel getal :: som); Assert.assertTrue (som <0); }

Laten we een XML gebruiken om de groepen uit te voeren:

Hiermee wordt de testmethode uitgevoerd die is getagd met group regressie.

5. Uitzonderingen testen

De functie voor het testen van uitzonderingen met behulp van annotaties is beschikbaar in zowel JUnit als TestNG.

Laten we eerst een klasse maken met een methode die een uitzondering genereert:

public class Calculator {public double divide (double a, double b) {if (b == 0) {throw new DivideByZeroException ("Divider kan niet gelijk zijn aan nul!"); } retourneer a / b; }}

In J Eenheid 5 we kunnen de assertThrows API om uitzonderingen te testen:

@Test openbare leegte whenDividerIsZero_thenDivideByZeroExceptionIsThrown () {Rekenmachine calculator = nieuwe rekenmachine (); assertThrows (DivideByZeroException.class, () -> calculator.divide (10, 0)); }

In JUnit 4, we kunnen dit bereiken door gebruik te maken van @Test (verwacht = DivideByZeroException.class) over de test-API.

En met TestNG kunnen we hetzelfde ook implementeren:

@Test (verwachteExceptions = ArithmeticException.class) openbare ongeldige gegevenNumber_whenThrowsException_thenCorrect () {int i = 1/0; }

Deze functie geeft aan welke uitzondering wordt gegenereerd door een stuk code, dat is onderdeel van een test.

6. Geparametriseerde tests

Geparametriseerde unit-tests zijn handig om dezelfde code onder verschillende omstandigheden te testen. Met behulp van geparametriseerde unit-tests kunnen we een testmethode opzetten die gegevens uit een gegevensbron haalt. Het belangrijkste idee is om de unit-testmethode herbruikbaar te maken en te testen met een andere set inputs.

In J Eenheid 5hebben we het voordeel dat testmethoden gegevensargumenten rechtstreeks uit de geconfigureerde bron verbruiken. Standaard biedt JUnit 5 er een paar bron annotaties zoals:

  • @ValueBron: we kunnen dit gebruiken met een reeks waarden van het type Short, Byte, Int, Long, Float, Double, Char, en Draad:
@ParameterizedTest @ValueSource (strings = {"Hallo", "Wereld"}) ongeldig gegevenString_TestNullOrNot (String woord) {assertNotNull (woord); }
  • @EnumSource - passeert Enum constanten als parameters voor de testmethode:
@ParameterizedTest @EnumSource (waarde = PizzaDeliveryStrategy.class, namen = {"EXPRESS", "NORMAL"}) ongeldig gegevenEnum_TestContainsOrNot (PizzaDeliveryStrategy timeUnit) {assertTrue (EnumSet.of (PizzaDeliveryStrategy) tijd) PizzaStrategy. ; }
  • @MethodSource - pbeoordeelt externe methoden die streams genereren:
statische Stream wordDataProvider () {return Stream.of ("foo", "bar"); } @ParameterizedTest @MethodSource ("wordDataProvider") ongeldig gegevenMethodSource_TestInputStream (String-argument) {assertNotNull (argument); }
  • @CsvSource - gebruikt CSV-waarden als bron voor de parameters:
@ParameterizedTest @CsvSource ({"1, Car", "2, House", "3, Train"}) ongeldig gegeven CSVSource_TestContent (int id, String-woord) {assertNotNull (id); assertNotNull (woord); }

Evenzo hebben we andere bronnen zoals @CsvFileSource als we een CSV-bestand moeten lezen van classpath en @ArgumentSource om een ​​aangepast, herbruikbaar ArgumentsProvider.

In JUnit 4moet de testklasse worden geannoteerd met @Rennen met om er een geparametriseerde klasse van te maken en @Parameter om de parameterwaarden voor de eenheidstest te gebruiken.

In TestNG kunnen we tests parametriseren met @Parameter of @Data provider annotaties. Terwijl u het XML-bestand gebruikt, annoteert u de testmethode met @Parameter:

@Test @Parameters ({"value", "isEven"}) public void givenNumberFromXML_ifEvenCheckOK_thenCorrect (int waarde, boolean isEven) {Assert.assertEquals (isEven, waarde% 2 == 0); }

en verstrek de gegevens in het XML-bestand:

Hoewel het gebruik van informatie in het XML-bestand eenvoudig en nuttig is, moet u in sommige gevallen mogelijk complexere gegevens opgeven.

Hiervoor kunnen we de @Data provider annotatie waarmee we complexe parametertypes voor testmethoden in kaart kunnen brengen.

Hier is een voorbeeld van gebruik @Data provider voor primitieve gegevenstypen:

@DataProvider (name = "numbers") openbaar statisch object [] [] evenNumbers () {retourneer nieuw object [] [] {{1, false}, {2, true}, {4, true}}; } @Test (dataProvider = "numbers") public void givenNumberFromDataProvider_ifEvenCheckOK_thenCorrect (geheel getal, boolean verwacht) {Assert.assertEquals (verwacht, getal% 2 == 0); }

En @Data provider voor objecten:

@Test (dataProvider = "numbersObject") public void givenNumberObjectFromDataProvider_ifEvenCheckOK_thenCorrect (EvenNumber nummer) {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, true)}}; }

Op dezelfde manier kunnen bepaalde objecten die moeten worden getest, worden gemaakt en geretourneerd met behulp van de gegevensprovider. Het is handig bij integratie met frameworks zoals Spring.

Merk op dat, in TestNG, sinds @Data provider methode hoeft niet statisch te zijn, we kunnen meerdere dataprovider-methoden gebruiken in dezelfde testklasse.

7. Time-out van test

Time-out tests betekent dat een testcase moet mislukken als de uitvoering niet binnen een bepaalde gespecificeerde periode is voltooid. Zowel JUnit als TestNG ondersteunen time-outtests. In J Eenheid 5 we kunnen een time-outtest schrijven als:

@Test openbare leegte gegevenExecution_takeMoreTime_thenFail () gooit InterruptedException {Assertions.assertTimeout (Duration.ofMillis (1000), () -> Thread.sleep (10000)); }

In JUnit 4 en TestNG kunnen we dezelfde test gebruiken met @Test (time-out = 1000)

@Test (timeOut = 1000) openbare leegte gegevenExecution_takeMoreTime_thenFail () {while (true); }

8. Afhankelijke tests

TestNG ondersteunt het testen van afhankelijkheden. Dit betekent dat in een reeks testmethoden, als de eerste test mislukt, alle daaropvolgende afhankelijke tests worden overgeslagen en niet als mislukt worden gemarkeerd zoals in het geval van JUnit.

Laten we eens kijken naar een scenario, waarin we e-mail moeten valideren, en als het lukt, gaan we door met inloggen:

@Test openbare ongeldige gegevenEmail_ifValid_thenTrue () {boolean valid = email.contains ("@"); Assert.assertEquals (geldig, waar); } @Test (afhankelijkOnMethods = {"givenEmail_ifValid_thenTrue"}) public void givenValidEmail_whenLoggedIn_thenTrue () {LOGGER.info ("E-mail {} geldig >> inloggen", e-mail); }

9. Volgorde van testuitvoering

Er is geen gedefinieerde impliciete volgorde waarin testmethoden worden uitgevoerd in JUnit 4 of TestNG. De methoden worden zojuist aangeroepen zoals geretourneerd door de Java Reflection API. Sinds JUnit 4 gebruikt het een meer deterministische maar niet voorspelbare volgorde.

Om meer controle te hebben, zullen we de testklasse annoteren met @FixMethodOrder annotatie en noem een ​​methodesorteerder:

@FixMethodOrder (MethodSorters.NAME_ASCENDING) openbare klasse SortedTests {@Test openbare ongeldige a_givenString_whenChangedtoInt_thenTrue () {assertTrue (Integer.valueOf ("10") instanceof Integer); } @Test openbare ongeldige b_givenInt_whenChangedtoString_thenTrue () {assertTrue (String.valueOf (10) instanceof String); }}

De MethodSorters.NAME_ASCENDING parameter sorteert de methoden op methode naam in lexicografische volgorde. Afgezien van deze sorteerder hebben we MethodSorter.DEFAULT en MethodSorter.JVM ook.

Hoewel TestNG ook een aantal manieren biedt om controle te hebben in de volgorde van uitvoering van de testmethode. Wij bieden de prioriteit parameter in het @Test annotatie:

@Test (prioriteit = 1) openbare ongeldige gegevenString_whenChangedToInt_thenCorrect () {Assert.assertTrue (Integer.valueOf ("10") instantie van Integer); } @Test (prioriteit = 2) openbare leegte gegevenInt_whenChangedToString_thenCorrect () {Assert.assertTrue (String.valueOf (23) instantie van String); }

Merk op dat prioriteit testmethoden oproept op basis van prioriteit, maar niet garandeert dat tests op het ene niveau worden voltooid voordat het volgende prioriteitsniveau wordt aangeroepen.

Soms kunnen we tijdens het schrijven van functionele testcases in TestNG een onderling afhankelijke test hebben waarbij de volgorde van uitvoering voor elke testrun hetzelfde moet zijn. Om dat te bereiken, moeten we de hangtOnMethods parameter naar @Test annotatie zoals we in de vorige sectie hebben gezien.

10. Aangepaste testnaam

Wanneer we een test uitvoeren, worden standaard de testklasse en de naam van de testmethode afgedrukt in console of IDE. J Eenheid 5 biedt een unieke functie waar we aangepaste beschrijvende namen kunnen noemen voor klasse- en testmethoden die gebruiken @Weergavenaam annotatie.

Deze annotatie biedt geen testvoordelen, maar het zorgt ook voor gemakkelijk leesbare en begrijpelijke testresultaten voor een niet-technisch persoon:

@ParameterizedTest @ValueSource (strings = {"Hallo", "Wereld"}) @DisplayName ("Testmethode om te controleren of de invoer niet nullabel is") void givenString_TestNullOrNot (String-woord) {assertNotNull (woord); }

Elke keer dat we de test uitvoeren, toont de uitvoer de weergavenaam in plaats van de naam van de methode.

Op dit moment, in TestNG er is geen manier om een ​​aangepaste naam op te geven.

11. Conclusie

Zowel JUnit als TestNG zijn moderne tools voor testen in het Java-ecosysteem.

In dit artikel hebben we kort gekeken naar verschillende manieren om tests te schrijven met elk van deze twee testframeworks.

De implementatie van alle codefragmenten is te vinden in TestNG en junit-5 Github-project.