Eine Anleitung zu JUnit 5

1. Übersicht

JUnit ist eines der beliebtesten Unit-Testing-Frameworks im Java-Ökosystem. Die JUnit 5-Version enthält eine Reihe aufregender Innovationen mit dem Ziel, neue Funktionen in Java 8 und höher zu unterstützen und viele verschiedene Teststile zu ermöglichen.

2. Maven-Abhängigkeiten

Das Einrichten von JUnit 5.x.0 ist ziemlich einfach. Wir müssen unserer pom.xml die folgende Abhängigkeit hinzufügen :

 org.junit.jupiter junit-jupiter-engine 5.1.0 test 

Es ist wichtig zu beachten, dass für diese Version Java 8 erforderlich ist .

Darüber hinaus gibt es jetzt direkte Unterstützung für die Ausführung von Unit-Tests auf der JUnit-Plattform in Eclipse sowie in IntelliJ. Sie können natürlich auch Tests mit dem Ziel Maven Test ausführen.

Auf der anderen Seite unterstützt IntelliJ standardmäßig JUnit 5. Daher ist das Ausführen von JUnit 5 unter IntelliJ ziemlich einfach. Klicken Sie einfach mit der rechten Maustaste -> Ausführen oder Strg-Umschalt-F10.

3. Architektur

JUnit 5 besteht aus mehreren verschiedenen Modulen aus drei verschiedenen Teilprojekten:

3.1. JUnit-Plattform

Die Plattform ist für das Starten von Test-Frameworks auf der JVM verantwortlich. Es definiert eine stabile und leistungsstarke Schnittstelle zwischen JUnit und seinem Client, z. B. Build-Tools.

Das endgültige Ziel besteht darin, wie die Kunden bei der Ermittlung und Durchführung der Tests problemlos in JUnit integriert werden können.

Außerdem wird die TestEngine-API zum Entwickeln eines Testframeworks definiert, das auf der JUnit-Plattform ausgeführt wird. Auf diese Weise können Sie Testbibliotheken von Drittanbietern direkt in JUnit einbinden, indem Sie eine benutzerdefinierte TestEngine implementieren.

3.2. JUnit Jupiter

Dieses Modul enthält neue Programmier- und Erweiterungsmodelle zum Schreiben von Tests in JUnit 5. Neue Anmerkungen im Vergleich zu JUnit 4 sind:

  • @TestFactory - bezeichnet eine Methode, die eine Testfactory für dynamische Tests ist
  • @DisplayName - Definiert einen benutzerdefinierten Anzeigenamen für eine Testklasse oder eine Testmethode
  • @Nested - Gibt an, dass die mit Anmerkungen versehene Klasse eine verschachtelte, nicht statische Testklasse ist
  • @Tag - deklariert Tags für Filtertests
  • @ExtendWith - wird verwendet, um benutzerdefinierte Erweiterungen zu registrieren
  • @BeforeEach - gibt an, dass die mit Anmerkungen versehene Methode vor jeder Testmethode ausgeführt wird (zuvor @Before ).
  • @AfterEach - gibt an, dass die mit Anmerkungen versehene Methode nach jeder Testmethode ausgeführt wird (zuvor @After )
  • @BeforeAll - gibt an, dass die mit Anmerkungen versehene Methode vor allen Testmethoden in der aktuellen Klasse ausgeführt wird (zuvor @BeforeClass ).
  • @AfterAll - gibt an, dass die mit Anmerkungen versehene Methode nach allen Testmethoden in der aktuellen Klasse ausgeführt wird (zuvor @AfterClass ).
  • @Disable - wird verwendet, um eine Testklasse oder -methode zu deaktivieren (zuvor @Ignore )

3.3. JUnit Vintage

Unterstützt das Ausführen von JUnit 3- und JUnit 4-basierten Tests auf der JUnit 5-Plattform.

4. Grundlegende Anmerkungen

Um neue Anmerkungen zu diskutieren, haben wir den Abschnitt in die folgenden Gruppen unterteilt, die für die Ausführung verantwortlich sind: vor den Tests, während der Tests (optional) und nach den Tests:

4.1. @BeforeAll und @BeforeEach

Unten finden Sie ein Beispiel für den einfachen Code, der vor den Haupttestfällen ausgeführt werden soll:

@BeforeAll static void setup() { log.info("@BeforeAll - executes once before all test methods in this class"); } @BeforeEach void init() { log.info("@BeforeEach - executes before each test method in this class"); }

Es ist wichtig zu beachten, dass die Methode mit der Annotation @BeforeAll statisch sein muss, da sonst der Code nicht kompiliert wird.

4.2. @DisplayName und @Disabled

Gehen wir zu neuen testoptionalen Methoden über:

@DisplayName("Single test successful") @Test void testSingleSuccessTest() { log.info("Success"); } @Test @Disabled("Not implemented yet") void testShowSomething() { }

Wie wir sehen können, können wir den Anzeigenamen ändern oder die Methode mit einem Kommentar mithilfe neuer Anmerkungen deaktivieren.

4.3. @AfterEach und @AfterAll

Lassen Sie uns abschließend Methoden diskutieren, die nach der Testausführung mit Operationen verbunden sind:

@AfterEach void tearDown() { log.info("@AfterEach - executed after each test method."); } @AfterAll static void done() { log.info("@AfterAll - executed after all test methods."); }

Bitte beachten Sie, dass die Methode mit @AfterAll auch eine statische Methode sein muss.

5. Behauptungen und Annahmen

JUnit 5 versucht, die neuen Funktionen von Java 8, insbesondere Lambda-Ausdrücke, voll auszunutzen.

5.1. Behauptungen

Zusicherungen wurden in org.junit.jupiter.api.Assertions verschoben und erheblich verbessert. Wie bereits erwähnt, können Sie Lambdas jetzt in Behauptungen verwenden:

@Test void lambdaExpressions() { assertTrue(Stream.of(1, 2, 3) .stream() .mapToInt(i -> i) .sum() > 5, () -> "Sum should be greater than 5"); }

Obwohl das obige Beispiel trivial ist, besteht ein Vorteil der Verwendung des Lambda-Ausdrucks für die Assertionsnachricht darin, dass er träge ausgewertet wird, was Zeit und Ressourcen sparen kann, wenn die Nachrichtenkonstruktion teuer ist.

It is also now possible to group assertions with assertAll() which will report any failed assertions within the group with a MultipleFailuresError:

 @Test void groupAssertions() { int[] numbers = {0, 1, 2, 3, 4}; assertAll("numbers", () -> assertEquals(numbers[0], 1), () -> assertEquals(numbers[3], 3), () -> assertEquals(numbers[4], 1) ); }

This means it is now safer to make more complex assertions, as you will be able to pinpoint the exact location of any failure.

5.2. Assumptions

Assumptions are used to run tests only if certain conditions are met. This is typically used for external conditions that are required for the test to run properly, but which are not directly related to whatever is being tested.

You can declare an assumption with assumeTrue(), assumeFalse(), and assumingThat().

@Test void trueAssumption() { assumeTrue(5 > 1); assertEquals(5 + 2, 7); } @Test void falseAssumption() { assumeFalse(5  assertEquals(2 + 2, 4) ); }

If an assumption fails, a TestAbortedException is thrown and the test is simply skipped.

Assumptions also understand lambda expressions.

6. Exception Testing

There are two ways of exception testing in JUnit 5. Both of them can be implemented by using assertThrows() method:

@Test void shouldThrowException() { Throwable exception = assertThrows(UnsupportedOperationException.class, () -> { throw new UnsupportedOperationException("Not supported"); }); assertEquals(exception.getMessage(), "Not supported"); } @Test void assertThrowsException() { String str = null; assertThrows(IllegalArgumentException.class, () -> { Integer.valueOf(str); }); }

The first example is used to verify more detail of the thrown exception and the second one just validates the type of exception.

7. Test Suites

To continue the new features of JUnit 5, we will try to get to know the concept of aggregating multiple test classes in a test suite so that we can run those together. JUnit 5 provides two annotations: @SelectPackages and @SelectClasses to create test suites.

Keep in mind that at this early stage most IDEs do not support those features.

Let's have a look at the first one:

@RunWith(JUnitPlatform.class) @SelectPackages("com.baeldung") public class AllUnitTest {}

@SelectPackage is used to specify the names of packages to be selected when running a test suite. In our example, it will run all test. The second annotation, @SelectClasses, is used to specify the classes to be selected when running a test suite:

@RunWith(JUnitPlatform.class) @SelectClasses({AssertionTest.class, AssumptionTest.class, ExceptionTest.class}) public class AllUnitTest {}

For example, above class will create a suite contains three test classes. Please note that the classes don't have to be in one single package.

8. Dynamic Tests

The last topic that we want to introduce is JUnit 5 Dynamic Tests feature, which allows to declare and run test cases generated at run-time. In contrary to the Static Tests which defines fixed number of test cases at the compile time, the Dynamic Tests allow us to define the tests case dynamically in the runtime.

Dynamic tests can be generated by a factory method annotated with @TestFactory. Let's have a look at the code example:

@TestFactory public Stream translateDynamicTestsFromStream() { return in.stream() .map(word -> DynamicTest.dynamicTest("Test translate " + word, () -> { int id = in.indexOf(word); assertEquals(out.get(id), translate(word)); }) ); }

This example is very straightforward and easy to understand. We want to translate words using two ArrayList, named in and out, respectively. The factory method must return a Stream, Collection, Iterable, or Iterator. In our case, we choose Java 8 Stream.

Please note that @TestFactory methods must not be private or static. The number of tests is dynamic, and it depends on the ArrayList size.

9. Conclusion

Das Schreiben war ein kurzer Überblick über die Änderungen, die mit JUnit 5 kommen.

Wir können sehen, dass JUnit 5 eine große Änderung in seiner Architektur aufweist, die sich auf den Plattformstarter, die Integration mit Build-Tool, IDE, anderen Unit-Test-Frameworks usw. bezieht. Darüber hinaus ist JUnit 5 stärker in Java 8 integriert, insbesondere in Lambdas- und Stream-Konzepte .

Die in diesem Artikel verwendeten Beispiele finden Sie im GitHub-Projekt.