Ein schneller Vergleich zwischen JUnit und TestNG

Ein Vergleich zwischen Quick JUnit und TestNG

1. Überblick

JUnit und TestNG sind zweifellos die beiden beliebtesten Unit-Testing-Frameworks im Java-Ökosystem. Während JUnit TestNG selbst inspiriert, bietet es seine besonderen Merkmale und arbeitet im Gegensatz zu JUnit für funktionale und höhere Testebenen.

In diesem Beitrag werdenwe’ll discuss and compare these frameworks by covering their features and common use cases.

2. Versuchsaufbau

Während wir Testfälle schreiben, müssen wir häufig einige Konfigurations- oder Initialisierungsanweisungen ausführen, bevor die Tests ausgeführt werden, und auch einige Aufräumarbeiten nach Abschluss der Tests. Lassen Sie uns diese in beiden Frameworks bewerten.

JUnit offers initialization and cleanup at two levels, before and after each method and class. Wir haben@BeforeEach,@AfterEach Anmerkungen auf Methodenebene und@BeforeAll und@AfterAll auf Klassenebene:

public class SummationServiceTest {

    private static List numbers;

    @BeforeAll
    public static void initialize() {
        numbers = new ArrayList<>();
    }

    @AfterAll
    public static void tearDown() {
        numbers = null;
    }

    @BeforeEach
    public void runBeforeEachTest() {
        numbers.add(1);
        numbers.add(2);
        numbers.add(3);
    }

    @AfterEach
    public void runAfterEachTest() {
        numbers.clear();
    }

    @Test
    public void givenNumbers_sumEquals_thenCorrect() {
        int sum = numbers.stream().reduce(0, Integer::sum);
        assertEquals(6, sum);
    }
}

Beachten Sie, dass in diesem Beispiel JUnit 5 verwendet wird. In der vorherigen JUnit 4-Version müssten wir die Annotationen@Before und@After verwenden, die@BeforeEach und @AfterEach.  entsprechen. Ebenso@BeforeAll und@AfterAll) s sind Ersatz für@BeforeClass und@AfterClass. von JUnit 4

Ähnlich wie bei JUnitTestNG also provides initialization and cleanup at the method and class level. Während@BeforeClass und@AfterClass auf Klassenebene gleich bleiben, lauten die Annotationen auf Methodenebene @BeforeMethod und@AfterMethod:

@BeforeClass
public void initialize() {
    numbers = new ArrayList<>();
}

@AfterClass
public void tearDown() {
    numbers = null;
}

@BeforeMethod
public void runBeforeEachTest() {
    numbers.add(1);
    numbers.add(2);
    numbers.add(3);
}

@AfterMethod
public void runAfterEachTest() {
    numbers.clear();
}

TestNG bietet auch@BeforeSuite, @AfterSuite, @BeforeGroup and @AfterGroup Anmerkungen für Konfigurationen auf Suite- und Gruppenebene an:

@BeforeGroups("positive_tests")
public void runBeforeEachGroup() {
    numbers.add(1);
    numbers.add(2);
    numbers.add(3);
}

@AfterGroups("negative_tests")
public void runAfterEachGroup() {
    numbers.clear();
}

Wir können auch@BeforeTest und @AfterTest verwenden, wenn wir vor oder nach Testfällen, die im Tag<test> in der TestNG-XML-Konfigurationsdatei enthalten sind, eine Konfiguration benötigen:


    
        
            
                
            
        
    

Beachten Sie, dass die Deklaration der Methoden@BeforeClass und@AfterClass in JUnit statisch sein muss. Im Vergleich dazu weist die TestNG-Methodendeklaration diese Einschränkungen nicht auf.

3. Tests ignorieren

Both frameworks support ignoring test cases, obwohl sie es ganz anders machen. JUnit bietet die Annotation@Ignorean:

@Ignore
@Test
public void givenNumbers_sumEquals_thenCorrect() {
    int sum = numbers.stream().reduce(0, Integer::sum);
    Assert.assertEquals(6, sum);
}

während TestNG@Test mit einem Parameter "enabled" mit einem booleschen Werttrue oderfalse verwendet:

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

4. Tests zusammen ausführen

Das gemeinsame Ausführen von Tests als Sammlung ist sowohl inJUnit als auch in TestNG möglich, jedoch auf unterschiedliche Weise.

We can use the @RunWith, @SelectPackages, and @SelectClasses annotations to group test cases and run them as a suite in JUnit 5. Eine Suite ist eine Sammlung von Testfällen, die wir gruppieren und als einzelnen Test ausführen können.

Wenn wir Testfälle verschiedener Pakete gruppieren möchten, um sie innerhalb einesSuite we zusammen auszuführen, benötigen Sie die Annotation@SelectPackages:

@RunWith(JUnitPlatform.class)
@SelectPackages({ "org.example.java.suite.childpackage1", "org.example.java.suite.childpackage2" })
public class SelectPackagesSuiteUnitTest {

}

Wenn bestimmte Testklassen zusammen ausgeführt werden sollen, bietetJUnit 5 die Flexibilität durch@SelectClasses:

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

}

Zuvor haben wirJUnit 4 verwendet und mit@Suite annotation mehrere Tests zusammen gruppiert und ausgeführt:

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

}

In TestNG können wir Tests mithilfe einer XML-Datei gruppieren:


    
        
            
            
        
    

Dies zeigt an, dassRegistrationTest undSignInTest zusammen ausgeführt werden.

Neben der Gruppierung von Klassen kann TestNG auch Methoden mithilfe der Annotation @Test(groups=”groupName”)gruppieren:

@Test(groups = "regression")
public void givenNegativeNumber_sumLessthanZero_thenCorrect() {
    int sum = numbers.stream().reduce(0, Integer::sum);
    Assert.assertTrue(sum < 0);
}

Verwenden wir ein XML, um die Gruppen auszuführen:


    
        
            
        
    
    
        
    

Dadurch wird die mit der Grupperegression gekennzeichnete Testmethode ausgeführt.

5. Ausnahmen testen

Die Funktion zum Testen auf Ausnahmen mithilfe von Anmerkungen ist sowohl in JUnit als auch in TestNG verfügbar.

Erstellen wir zunächst eine Klasse mit einer Methode, die eine Ausnahme auslöst:

public class Calculator {
    public double divide(double a, double b) {
        if (b == 0) {
            throw new DivideByZeroException("Divider cannot be equal to zero!");
        }
        return a/b;
    }
}

InJUnit 5 können wir dieassertThrows API verwenden, um Ausnahmen zu testen:

@Test
public void whenDividerIsZero_thenDivideByZeroExceptionIsThrown() {
    Calculator calculator = new Calculator();
    assertThrows(DivideByZeroException.class, () -> calculator.divide(10, 0));
}

InJUnit 4, kann swe dies erreichen, indem@Test(expected = DivideByZeroException.class) für die Test-API verwendet wird.

Und mit TestNG können wir das auch umsetzen:

@Test(expectedExceptions = ArithmeticException.class)
public void givenNumber_whenThrowsException_thenCorrect() {
    int i = 1 / 0;
}

Diese Funktion impliziert, welche Ausnahme von einem Code ausgelöst wird, der Teil eines Tests ist.

6. Parametrisierte Tests

Parametrisierte Unit-Tests sind hilfreich, um denselben Code unter verschiedenen Bedingungen zu testen. Mit Hilfe von parametrisierten Unit-Tests können wir eine Testmethode einrichten, die Daten aus einer Datenquelle bezieht. Die Hauptidee besteht darin, die Einheitentestmethode wiederverwendbar zu machen und mit einem anderen Satz von Eingängen zu testen.

In JUnit 5, we have the advantage of test methods consuming data arguments directly from the configured source. Standardmäßig bietet JUnit 5 einigesource-Anmerkungen wie:

  • @ValueSource: können wir mit einem Array von Werten vom TypShort, Byte, Int, Long, Float, Double, Char, undString: verwenden

@ParameterizedTest
@ValueSource(strings = { "Hello", "World" })
void givenString_TestNullOrNot(String word) {
    assertNotNull(word);
}
  • @EnumSource – passesEnum constants als Parameter für die Testmethode:

@ParameterizedTest
@EnumSource(value = PizzaDeliveryStrategy.class, names = {"EXPRESS", "NORMAL"})
void givenEnum_TestContainsOrNot(PizzaDeliveryStrategy timeUnit) {
    assertTrue(EnumSet.of(PizzaDeliveryStrategy.EXPRESS, PizzaDeliveryStrategy.NORMAL).contains(timeUnit));
}
  • @MethodSource – pverwendet externe Methoden, um Streams zu generieren:

static Stream wordDataProvider() {
    return Stream.of("foo", "bar");
}

@ParameterizedTest
@MethodSource("wordDataProvider")
void givenMethodSource_TestInputStream(String argument) {
    assertNotNull(argument);
}
  • @CsvSource – verwendet CSV-Werte als Quelle für die Parameter:

@ParameterizedTest
@CsvSource({ "1, Car", "2, House", "3, Train" })
void givenCSVSource_TestContent(int id, String word) {
    assertNotNull(id);
    assertNotNull(word);
}

In ähnlicher Weise haben wir andere Quellen wie@CsvFileSource , wenn wir eine CSV-Datei aus dem Klassenpfad lesen müssen, und@ArgumentSource, um benutzerdefinierte, wiederverwendbareArgumentsProvider. anzugeben

InJUnit 4 muss die Testklasse mit@RunWith kommentiert werden, um sie zu einer parametrisierten Klasse zu machen, und@Parameter, um die Parameterwerte für den Komponententest zu verwenden.

In TestNG, we can parametrize tests using @Parameter or @DataProvider annotations. Kommentieren Sie bei Verwendung der XML-Datei die Testmethode mit @Parameter:

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

und geben Sie die Daten in der XML-Datei an:


    
        
        
        
            
        
    

Während die Verwendung von Informationen in der XML-Datei einfach und nützlich ist, müssen Sie in einigen Fällen möglicherweise komplexere Daten bereitstellen.

Hierzu können wir die Annotation@DataProvider verwenden, mit der wir komplexe Parametertypen für Testmethoden abbilden können.

Hier ist ein Beispiel für die Verwendung von@DataProvider für primitive Datentypen:

@DataProvider(name = "numbers")
public static Object[][] evenNumbers() {
    return new Object[][]{{1, false}, {2, true}, {4, true}};
}

@Test(dataProvider = "numbers")
public void givenNumberFromDataProvider_ifEvenCheckOK_thenCorrect
  (Integer number, boolean expected) {
    Assert.assertEquals(expected, number % 2 == 0);
}

Und@DataProvider für Objekte:

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

Auf die gleiche Weise können bestimmte zu testende Objekte mit dem Datenprovider erstellt und zurückgegeben werden. Dies ist nützlich bei der Integration in Frameworks wie Spring.

Beachten Sie, dass in TestNG, da die Methode von@DataProvidernicht statisch sein muss, mehrere Datenprovider-Methoden in derselben Testklasse verwendet werden können.

7. Test-Timeout

Timeout-Tests bedeuten, dass ein Testfall fehlschlagen sollte, wenn die Ausführung nicht innerhalb eines bestimmten festgelegten Zeitraums abgeschlossen wurde. Sowohl JUnit als auch TestNG unterstützen Timeout-Tests. InJUnit 5 können wir einen Timeout-Test als: schreiben

@Test
public void givenExecution_takeMoreTime_thenFail() throws InterruptedException {
    Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(10000));
}

InJUnit 4 und TestNG können wir denselben Test mit @Test(timeout=1000) durchführen

@Test(timeOut = 1000)
public void givenExecution_takeMoreTime_thenFail() {
    while (true);
}

8. Abhängige Tests

TestNG unterstützt Abhängigkeitstests. Dies bedeutet, dass in einer Reihe von Testmethoden, wenn der erste Test fehlschlägt, alle nachfolgenden abhängigen Tests übersprungen werden und nicht wie im Fall von JUnit als fehlgeschlagen markiert werden.

Schauen wir uns ein Szenario an, in dem wir E-Mails validieren müssen. Wenn dies erfolgreich ist, melden wir uns an:

@Test
public void givenEmail_ifValid_thenTrue() {
    boolean valid = email.contains("@");
    Assert.assertEquals(valid, true);
}

@Test(dependsOnMethods = {"givenEmail_ifValid_thenTrue"})
public void givenValidEmail_whenLoggedIn_thenTrue() {
    LOGGER.info("Email {} valid >> logging in", email);
}

9. Reihenfolge der Testausführung

There is no defined implicit order in which test methods will get executed in JUnit 4 or TestNG. Die Methoden werden nur so aufgerufen, wie sie von der Java Reflection-API zurückgegeben werden. Seit JUnit 4 wird eine deterministischere, aber nicht vorhersehbare Reihenfolge verwendet.

Um mehr Kontrolle zu haben, werden wir die Testklasse mit@FixMethodOrder Annotation versehen und einen Methodensortierer erwähnen:

@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class SortedTests {

    @Test
    public void a_givenString_whenChangedtoInt_thenTrue() {
        assertTrue(
          Integer.valueOf("10") instanceof Integer);
    }

    @Test
    public void b_givenInt_whenChangedtoString_thenTrue() {
        assertTrue(
          String.valueOf(10) instanceof String);
    }

}

Der ParameterMethodSorters.NAME_ASCENDING sortiert die Methoden nach Methodennamen in lexikografischer Reihenfolge. Abgesehen von diesem Sortierer haben wirMethodSorter.DEFAULT and MethodSorter.JVM as well.

Zwar bietet TestNG auch einige Möglichkeiten, die Reihenfolge der Testmethodenausführung zu steuern. Wir geben den Parameterpriority in der Annotation@Test an:

@Test(priority = 1)
public void givenString_whenChangedToInt_thenCorrect() {
    Assert.assertTrue(
      Integer.valueOf("10") instanceof Integer);
}

@Test(priority = 2)
public void givenInt_whenChangedToString_thenCorrect() {
    Assert.assertTrue(
      String.valueOf(23) instanceof String);
}

Beachten Sie, dass die Priorität Testmethoden basierend auf der Priorität aufruft, jedoch nicht garantiert, dass die Tests in einer Ebene abgeschlossen sind, bevor die nächste Prioritätsstufe aufgerufen wird.

Manchmal kann es beim Schreiben von Funktionstestfällen in TestNG zu einem voneinander abhängigen Test kommen, bei dem die Ausführungsreihenfolge für jeden Testlauf gleich sein muss. Um dies zu erreichen, sollten wir den ParameterdependsOnMethods für die Annotation @Testverwenden, wie wir im vorherigen Abschnitt gesehen haben.

10. Benutzerdefinierter Testname

Wenn wir einen Test ausführen, werden die Testklasse und der Name der Testmethode standardmäßig in der Konsole oder in der IDE gedruckt. JUnit 5 bietet eine einzigartige Funktion, bei der benutzerdefinierte beschreibende Namen für Klassen- und Testmethoden mithilfe der Annotation von@DisplayNameerwähnt werden können.

Diese Anmerkung bietet keine Testvorteile, bietet jedoch auch für eine nicht technische Person leicht lesbare und verständliche Testergebnisse:

@ParameterizedTest
@ValueSource(strings = { "Hello", "World" })
@DisplayName("Test Method to check that the inputs are not nullable")
void givenString_TestNullOrNot(String word) {
    assertNotNull(word);
}

Immer wenn wir den Test ausführen, wird in der Ausgabe der Anzeigename anstelle des Methodennamens angezeigt.

Derzeit gibt es inTestNG keine Möglichkeit, einen benutzerdefinierten Namen anzugeben.

11. Fazit

Sowohl JUnit als auch TestNG sind moderne Tools zum Testen im Java-Ökosystem.

In diesem Artikel haben wir uns kurz mit den verschiedenen Methoden zum Schreiben von Tests für jedes dieser beiden Test-Frameworks befasst.

Die Implementierung aller Codefragmente finden Sie inTestNG undjunit-5 Github project.