srcset=”https://b2b-contenthub.com/wp-content/uploads/2025/06/dotshock_shutterstock_2311984335_16z9.jpg?quality=50&strip=all 8224w, https://b2b-contenthub.com/wp-content/uploads/2025/06/dotshock_shutterstock_2311984335_16z9.jpg?resize=300%2C168&quality=50&strip=all 300w, https://b2b-contenthub.com/wp-content/uploads/2025/06/dotshock_shutterstock_2311984335_16z9.jpg?resize=768%2C432&quality=50&strip=all 768w, https://b2b-contenthub.com/wp-content/uploads/2025/06/dotshock_shutterstock_2311984335_16z9.jpg?resize=1024%2C576&quality=50&strip=all 1024w, https://b2b-contenthub.com/wp-content/uploads/2025/06/dotshock_shutterstock_2311984335_16z9.jpg?resize=1536%2C864&quality=50&strip=all 1536w, https://b2b-contenthub.com/wp-content/uploads/2025/06/dotshock_shutterstock_2311984335_16z9.jpg?resize=2048%2C1152&quality=50&strip=all 2048w, https://b2b-contenthub.com/wp-content/uploads/2025/06/dotshock_shutterstock_2311984335_16z9.jpg?resize=1240%2C697&quality=50&strip=all 1240w, https://b2b-contenthub.com/wp-content/uploads/2025/06/dotshock_shutterstock_2311984335_16z9.jpg?resize=150%2C84&quality=50&strip=all 150w, https://b2b-contenthub.com/wp-content/uploads/2025/06/dotshock_shutterstock_2311984335_16z9.jpg?resize=854%2C480&quality=50&strip=all 854w, https://b2b-contenthub.com/wp-content/uploads/2025/06/dotshock_shutterstock_2311984335_16z9.jpg?resize=640%2C360&quality=50&strip=all 640w, https://b2b-contenthub.com/wp-content/uploads/2025/06/dotshock_shutterstock_2311984335_16z9.jpg?resize=444%2C250&quality=50&strip=all 444w” width=”1024″ height=”576″ sizes=”(max-width: 1024px) 100vw, 1024px”>Dieses Tutorial bringt Ihnen Testing mit JUnit 5 näher.dotshock | shutterstock.com JUnit 5 ist der De-facto-Standard, um Unit-Tests in Java zu entwickeln. Eine robuste Testing-Suite gibt Ihnen nicht nur die Sicherheit, dass sich Ihre Applikationen wie gewünscht verhalten, sondern kann auch verhindern, dass sich bei Änderungen unabsichtlich Fehler einschleichen. Dieser Artikel vermittelt Ihnen die Grundlagen, um Ihre Java-Anwendungen mit JUnit 5 zu testen. Ganz konkret erfahren Sie in diesem Tutorial, wie Sie: ein Maven-Projekt für JUnit konfigurieren, grundlegende und parametrisierte Unit-Tests schreiben und die in JUnit 5 integrierten Assertions, Annotationen und Tags nutzen. Den Quellcode für sämtliche Beispiele in diesem Artikel können Sie hier direkt herunterladen (Zip-Archiv). Unit-Testing mit JUnit 5 Sehen wir uns zum Start ein Beispiel dafür an, wie ein Projekt für Unit-Tests mit JUnit 5 konfiguriert wird. Das folgende Listing zeigt eine MathTools-Klasse, deren Methode einen Zähler und einen Nenner zu einem Double-Wert konvertiert. Listing 1: JUnit-5-Beispielprojekt (MathTools.java) package com.javaworld.geekcap.math; public class MathTools { public static double convertToDecimal(int numerator, int denominator) { if (denominator == 0) { throw new IllegalArgumentException(“Denominator must not be 0”); } return (double)numerator / (double)denominator; } } Um die MathTools-Klasse und ihre Methode zu testen, stehen zwei primäre Szenarien zur Verfügung: Ein validierter Test, bei dem eine Ganzzahl ungleich Null an den Nenner übergeben wird. Ein Fehlerszenario, bei dem ein Nullwert an den Nenner übergeben wird. Um diese beiden Szenarien zu testen, ist eine JUnit-5-Testklasse nötig. Listing 2: Eine JUnit 5-Testklasse (MathToolsTest.java) package com.javaworld.geekcap.math; import java.lang.IllegalArgumentException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class MathToolsTest { @Test void testConvertToDecimalSuccess() { double result = MathTools.convertToDecimal(3, 4); Assertions.assertEquals(0.75, result); } @Test void testConvertToDecimalInvalidDenominator() { Assertions.assertThrows(IllegalArgumentException.class, () -> MathTools.convertToDecimal(3, 0)); } } In Listing 2 führt die testConvertToDecimalInvalidDenominator-Methode die MathTools::convertToDecimal-Methode innerhalb eines assertThrows-Calls aus. Das erste Argument ist der erwartete Typ der auszulösenden Ausnahme. Das zweite Argument ist eine Funktion, die diese Ausnahme auslöst. Die assertThrows-Methode führt die Funktion aus und überprüft, ob der erwartete Ausnahmetyp ausgelöst wird. Die Assertions-Klasse und ihre Methoden Die Annotation org.junit.jupiter.api.Test kennzeichnet eine Testmethode. Zunächst führt testConvertToDecimalSuccess die MathTools::convertToDecimal-Methode mit einem Zähler von 3 und einem Nenner von 4 aus und überprüft dann, ob das Ergebnis 0,75 ist. Die org.junit.jupiter.api.Assertions-Klasse bietet eine Reihe statischer Methoden, um tatsächliche und erwartete Ergebnisse zu vergleichen. Die Methoden der Assertions-Klasse decken die meisten primitiven Datentypen ab: assertArrayEquals vergleicht den Inhalt eines tatsächlichen Arrays mit einem erwarteten Array. assertEquals vergleicht einen tatsächlichen Wert mit einem erwarteten Wert. assertNotEquals vergleicht zwei Werte, um zu überprüfen, ob sie ungleich sind. assertTrue überprüft, ob der angegebene Wert true ist. assertFalse überprüft, ob der angegebene Wert false ist. assertLinesMatch vergleicht zwei String-Listen. assertNull überprüft, ob der angegebene Wert null ist. assertNotNull überprüft, ob der angegebene Wert nicht null ist. assertSame überprüft, ob zwei Werte auf dasselbe Objekt verweisen. assertNotSame überprüft, ob zwei Werte nicht auf dasselbe Objekt verweisen. assertThrows überprüft, ob eine Methode eine erwartete Ausnahme auslöst, wenn sie ausgeführt wird. assertTimeout überprüft, ob eine angegebene Funktion innerhalb eines definierten Timout-Rahmens abgeschlossen wird. assertTimeoutPreemptively überprüft, ob eine angegebene Funktion innerhalb eines bestimmten Timout-Fensters abgeschlossen wird – führt diese jedoch nicht mehr aus, sobald die definierte Zeit verstrichen ist. Schlägt eine dieser Assertion-Methoden fehl, scheitert auch der Unit-Test und wird entsprechend markiert. Die Fehlermeldung wird bei der Ausführung des Tests auf dem Bildschirm angezeigt und anschließend in einer Berichtsdatei gespeichert. Delta mit assertEquals verwenden Wenn Sie in einem assertEquals Float- und Double-Werte verwenden, können Sie auch ein Delta angeben, das einen Schwellenwert für die Differenz zwischen den beiden verglichenen Werten darstellt. Beispielsweise wird 22/7 häufig als Annäherungswert für PI oder 3,14 verwendet. Allerdings erhalten wir nicht 3,14 wenn wir 22 durch 7 dividieren, sondern 3,14285. Das nachfolgende Listing demonstriert, wie Sie mit einem delta-Wert überprüfen können, ob 22/7 einen Wert zwischen 3,141 und 3,143 zurückgibt. Listing 3: assertEquals mit einem Delta testen @Test void testConvertToDecimalWithDeltaSuccess () { double result = MathTools.convertToDecimal(22, 7); Assertions.assertEquals(3.142, result, 0.001); } In diesem Beispiel erwarten wir 3,142 +/- 0,001, was allen Werten zwischen 3,141 und 3,143 entspricht. Sowohl 3,140 als auch 3,144 würden dazu führen, dass der Test fehlschlägt – mit 3,142857 würde er klappen. Testergebnisse analysieren Assert-Methoden können nicht nur Werte oder Verhalten validieren, sondern akzeptieren auch eine textuelle Beschreibung des Fehlers. Das kann Sie bei der Diagnose unterstützen. Vergegenwärtigen Sie sich anhand des folgenden Outputs zwei Varianten: Assertions.assertEquals(0.75, result, “The MathTools::convertToDecimal value did not return the correct value of 0.75 for 3/4”); Assertions.assertEquals(0.75, result, () -> “The MathTools::convertToDecimal value did not return the correct value of 0.75 for 3/4”); Der Output zeigt den erwarteten Wert 0,75 sowie den tatsächlichen Wert. Außerdem wird die spezifizierte Meldung angezeigt, die dabei helfen kann, den Fehlerkontext zu verstehen. Der Unterschied zwischen den beiden Varianten: Erstere erstellt immer die Meldung, auch wenn sie nicht angezeigt wird. Zweitere nur dann, wenn die Assertion fehlschlägt. In diesem Fall ist es trivial, die Meldung zu erstellen. Trotzdem ist es nicht erforderlich, eine Fehlermeldung für einen Test zu erstellen, der bestanden wurde. Es empfiehlt sich daher in der Regel, auf den zweitgenannten Ansatz zu setzen. Falls Sie für Ihre Tests eine Java-IDE wie IntelliJ einsetzen, wird jede Testmethode mit ihrem Methodennamen angezeigt. Das erfordert, dass Ihre Methodennamen auch lesbar sind. Ansonsten können Sie Ihre Testmethoden auch um @DisplayName-Annotations erweitern, um sie “identifizierbarer” zu gestalten: @Test @DisplayName(“Test successful decimal conversion”) void testConvertToDecimalSuccess() { double result = MathTools.convertToDecimal(3, 4); Assertions.assertEquals(0.751, result); } Unit-Tests mit Maven fahren Um JUnit-5-Tests aus einem Maven-Projekt heraus auszuführen, müssen Sie das maven-surefire-plugin in die pom.xml-Datei aufnehmen und eine neue Abhängigkeit hinzufügen. Das folgende Listing zeigt pom.xml für dieses Projekt. Listing 4: Maven pom.xml für ein Beispielprojekt mit JUnit 5 4.0.0 org.example JUnitExample 1.0-SNAPSHOT 24 24 UTF-8 org.apache.maven.plugins maven-surefire-plugin 3.5.3 org.junit.jupiter junit-jupiter 5.12.2 test JUnit 5 verpackt seine Komponenten in der org.junit.jupiter-Gruppe und nutzt das Aggregator-Artefakt junit-jupiter, um Abhängigkeiten zu importieren: junit-jupiter-api definiert die API, um Tests und Erweiterungen zu erstellen. junit-jupiter-engine ist die Test-Engine-Implementierung, die die Unit-Tests ausführt. junit-jupiter-params unterstützt parametrisierte Tests. Im nächsten Schritt fügen wir das Maven-Build-Plugin hinzu, um die Tests auszuführen. Schließlich richten wir unseren Build mit den Eigenschaften maven.compiler.source und maven.compiler.target auf Java 24 aus. Testklasse ausführen Jetzt können wir unsere Testklasse ausführen. Dazu nutzen Sie folgenden Befehl: mvn clean test Im Erfolgsfall sollten Sie (in etwa) folgenden Output zu sehen bekommen: [INFO] ——————————————————- [INFO] T E S T S [INFO] ——————————————————- [INFO] Running com.javaworld.geekcap.math.MathToolsTest [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.04 s – in com.javaworld.geekcap.math.MathToolsTest [INFO] [INFO] Results: [INFO] [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] ———————————————————————— [INFO] BUILD SUCCESS [INFO] ———————————————————————— [INFO] Total time: 3.832 s [INFO] Finished at: 2025-05-21T08:21:15-05:00 [INFO] ———————————————————————— Parametrisierte Tests mit JUnit 5 Da Sie nun wissen, wie man einen simplen Unit-Test mit JUnit 5 erstellt, gehen wir einen Schritt weiter: Die Testklasse in diesem Abschnitt basiert ebenfalls auf der MathTools-Klasse – allerdings nutzen wir nun parametrisierte Tests, um unseren Code gründlicher auf die Probe zu stellen. Dazu ergänzen wir MathTools zunächst um eine weitere Methode namens isEven: public static boolean isEven(int number) { return number % 2 == 0; } Wir könnten diesen Code auf dieselbe Weise testen wie im vorherigen Abschnitt, indem wir verschiedene Zahlen an die isEven-Methode übergeben und die Antwort validieren: @Test void testIsEvenSuccessful() { Assertions.assertTrue(MathTools.isEven(2)); Assertions.assertFalse(MathTools.isEven(1)); } Diese Methodik funktioniert zwar, wird jedoch schnell mühsam, wenn Sie eine große Anzahl von Werten testen möchten und diese manuell eingeben müssen. Um die Werte zu definieren, die wir testen möchten, nutzen wir einen parametrisierten Test: @ParameterizedTest @ValueSource(ints = {0, 2, 4, 6, 8, 10, 100, 1000}) void testIsEven(int number) { Assertions.assertTrue(MathTools.isEven(number)); } Anstelle der @Test-Annotation verwenden wir an dieser Stelle @ParameterizedTest. Außerdem ist es nötig, eine Quelle für die Parameter anzugeben: A note about the ValueSource annotation The ValueSource in the above example accepts an integer array by specifying an ints argument, but the ValueSource annotation also supports booleans, bytes, chars, classes, doubles, floats, longs, shorts, and strings. For example, you might supply a list of String literals: @ValueSource(strings = {“foo”, “bar”, “baz”}). Quellen in parametrisierten Tests nutzen Es gibt verschiedene Arten von Quellen. Mit der einfachsten – @ValueSource – lässt sich eine Integer- oder String-Liste angeben. Der Parameter wird dabei als Argument an die Testmethode übergeben und kann anschließend im Test genutzt werden. In unserem Beispiel übergeben wir acht gerade Zahlen und überprüfen, ob die Methode MathTools::isEven diese korrekt identifiziert. Bleibt das Problem, sämtliche Werte manuell eingeben zu müssen, die getestet werden sollen. Wollten Sie sämtliche Ganzzahlen zwischen 0 und 1.000 testen, könnten Sie @ValueSource durch @MethodSource ersetzen, um die Zahlenliste zu generieren. Ein Beispiel: @ParameterizedTest @MethodSource(“generateEvenNumbers”) void testIsEvenRange(int number) { Assertions.assertTrue(MathTools.isEven(number)); } static IntStream generateEvenNumbers() { return IntStream.iterate(0, i -> i + 2).limit(500); } Kommt @MethodSource zum Einsatz, definieren wir eine statische Methode, die einen Stream oder eine Collection zurückgibt. Dabei wird jeder Wert als Methodenargument an unsere Testmethode gesendet. In unserem Beispiel erstellen wir einen IntStream (Integer-Stream). Dieser beginnt bei 0, erhöht sich um jeweils zwei und begrenzt die Gesamtzahl der Elemente im Stream auf 500. Die isEven-Methode wird also 500 Mal aufgerufen, wobei alle geraden Zahlen zwischen 0 und 998 verwendet werden. Parametrisierte Tests unterstützen folgende Quelltypen: ValueSource spezifiziert eine hartkodierte Liste von Ganzzahlen oder Strings. MethodSource ruft eine statische Methode auf, die einen Stream oder eine Collection von Elementen generiert. EnumSource gibt eine Enumeration an, deren Werte an die Testmethode übergeben werden. Das ermöglicht, über sämtliche Enum-Werte zu iterieren oder bestimmte Werte ein- und auszuschließen. CsvSource gibt eine durch Kommas getrennte Liste von Werten an. CsvFileSource spezifiziert einen Pfad zu einer durch Kommas getrennten Value-Datei mit Testdaten. ArgumentsSource ermöglicht, eine Klasse anzugeben, die das ArgumentsProvider-Interface implementiert. Dieses generiert einen Stream von Argumenten, die an die Testmethode übergeben werden. NullSource übergibt null an Ihre Testmethode, wenn Sie mit Strings, Collections oder Arrays arbeiten. Diese Annotation lässt sich in andere (wie ValueSource) inkludieren, um eine Sammlung von Werten (und null) zu testen. EmptySource fügt einen leeren Wert ein, wenn Sie mit Strings, Collections oder Arrays arbeiten. NullAndEmptySource inkludiert sowohl null als auch einen leeren Wert, wenn Sie mit Strings, Collections oder Arrays arbeiten. FieldSource ermöglicht, auf ein oder mehrere Felder der Testklasse (oder externer Klassen) zu verweisen. Darüber hinaus können Sie mit JUnit mehrere “wiederholbare” Quellen nutzen, indem Sie mehrere Quell-Annotationen in Ihrer parametrisierten Testmethode spezifizieren. Zu diesen wiederholbaren Quellen gehören: ValueSource, EnumSource, MethodSource, FieldSource, CsvSource, CsvFileSource , sowie ArgumentsSource. Der Test-Lifecycle von JUnit 5 Bei den meisten Softwaretests empfiehlt es sich, vor und nach jedem Testlauf (beziehungsweise vor und nach allen Testläufen) bestimmte Dinge zu tun. Wollen Sie beispielsweise Datenbankabfragen testen, ist es möglicherweise sinnvoll: vor allen Testläufen eine Verbindung zu einer Datenbank herzustellen und ein Schema importieren, vor jedem einzelnen Test Testdaten einzufügen, nach jedem Test die Datenbank zu bereinigen, sowie nach allen Testläufen das Schema zu löschen und die Datenbankverbindung schließen. Zu diesem Zweck bietet JUnit 5 folgenden Annotationen, die Sie den Methoden Ihrer Testklasse hinzufügen können: @BeforeAll ist eine statische Methode in Ihrer Testklasse, die aufgerufen wird, bevor sie Tests durchführt. @AfterAll ist eine statische Methode in Ihrer Testklasse, die aufgerufen wird, nachdem alle Tests durchgeführt wurden. @BeforeEach ist eine Methode, die vor jedem einzelnen Test aufgerufen wird. @AfterEach ist eine Methode, die nach jedem einzelnen Test aufgerufen wird. Das nachfolgende Listing zeigt ein einfaches Beispiel, das die Aufrufe der verschiedenen Lebenszyklusmethoden protokolliert. Listing 5: JUnit-5-Lebenszyklusmethoden loggen (LifecycleDemoTest.java) package com.javaworld.geekcap.lifecycle; import org.junit.jupiter.api.*; public class LifecycleDemoTest { @BeforeAll static void beforeAll() { System.out.println(“Connect to the database”); } @BeforeEach void beforeEach() { System.out.println(“Load the schema”); } @AfterEach void afterEach() { System.out.println(“Drop the schema”); } @AfterAll static void afterAll() { System.out.println(“Disconnect from the database”); } @Test void testOne() { System.out.println(“Test One”); } @Test void testTwo() { System.out.println(“Test Two”); } } Wenn Sie diesen Test ausführen, erwartet Sie folgender Konsolen-Output: Connect to the database Load the schema Test One Drop the schema Load the schema Test Two Drop the schema Disconnect from the database Hier wird die beforeAll-Methode aufgerufen, die etwa eine Verbindung zu einer Datenbank herstellen oder eine große Datenstruktur im Arbeitsspeicher erstellen könnte. Im nächsten Schritt sorgt die beforeEach-Methode dafür, dass die Daten für jeden Test vorbereitet werden – etwa, indem sie eine Testdatenbank mit einem erwarteten Datensatz befüllt. Anschließend wird der erste Test ausgeführt, gefolgt von der afterEach-Methode. Dieser Prozess (beforeEach—> Test—>afterEach) wird solange fortgesetzt, bis alle Tests abgeschlossen sind. Abschließend bereinigt die afterAll-Methode die Testumgebung, beispielsweise, indem sie die Verbindung zur Datenbank trennt. Tags & Filtering in JUnit 5 Abschließend werfen wir nun noch einen Blick darauf, wie Sie Tags nutzen können, um verschiedene Testfälle selektiv auszuführen. Tags identifizieren und filtern bestimmte Tests, die Sie in verschiedenen Szenarien ausführen möchten. Die Bennenung und der Verwendungszweck von Tags sind frei wählbar. Nachfolgend erstellen wir drei neue Testklassen und kennzeichnen zwei davon als “Development” und eine als “Integration”: Listing 6: JUnit 5-Tags, Test 1 (TestOne.java) package com.javaworld.geekcap.tags; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @Tag(“Development”) class TestOne { @Test void testOne() { System.out.println(“Test 1”); } } Listing 7: JUnit 5 tags, test 2 (TestTwo.java) package com.javaworld.geekcap.tags; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @Tag(“Development”) class TestTwo { @Test void testTwo() { System.out.println(“Test 2”); } } Listing 8: JUnit 5 tags, test 3 (TestThree.java) package com.javaworld.geekcap.tags; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @Tag(“Integration”) class TestThree { @Test void testThree() { System.out.println(“Test 3”); } } Implementiert werden Tags durch Annotationen. Dabei können Sie entweder eine gesamte Test-Klasse oder einzelne Methoden innerhalb einer solchen mit Anmerkungen versehen. Darüber hinaus können Klassen und Methoden auch mehrere Tags aufweisen. In unseren Beispielen sind TestOne und TestTwo mit dem “Development”-, TestThree mit dem “Integration”-Tag versehen. Mit Hilfe der Tags können Sie Testläufe auf verschiedene Weisen filtern. Am einfachsten ist es, einen Test über Ihre Maven-Kommandozeile anzugeben. Im folgenden Beispiel werden beispielsweise nur Tests ausgeführt, die das “Development”-Tag aufweisen: mvn clean test -Dgroups=”Development” Die Property groups erlaubt Ihnen, eine durch Kommas getrennte Liste von Tag-Namen für die Tests zu spezifizieren, die JUnit 5 ausführen soll. Das resultiert in folgendem Output: [INFO] ——————————————————- [INFO] T E S T S [INFO] ——————————————————- [INFO] Running com.javaworld.geekcap.tags.TestOne Test 1 [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.029 s – in com.javaworld.geekcap.tags.TestOne [INFO] Running com.javaworld.geekcap.tags.TestTwo Test 2 [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s – in com.javaworld.geekcap.tags.TestTwo Folgendermaßen könnten Sie nur die Integrationstests ausführen: mvn clean test -Dgroups=”Integration” Oder sowohl Development- als auch Integrationstests: mvn clean test -Dgroups=”Development, Integration” Zusätzlich zur Eigenschaft groups können Sie in JUnit 5 auch excludedGroups verwenden, um alle Tests auszuführen, die nicht über das angegebene Tag verfügen. In einer Entwicklungsumgebung möchten wir beispielsweise auf Integrationstests verzichten. Wir könnten deshalb folgendermaßen verfahren: mvn clean test -DexcludedGroups=”Integration” Das ist besonders im Fall von großen Anwendungen hilfreich, die Tausende von Tests umfassen können: Wenn Sie später neue Produktionstests hinzufügen möchten, müssen Sie nicht einen Schritt zurückgehen und die anderen 10.000 Tests um ein “Development”-Tag ergänzen. Last, but not least ist es auch möglich, das surefire-Plugin sowohl um groups als auch um excludedGroups zu ergänzen und diese Felder über Maven-Profile zu steuern. Weitere Informationen zu Tags entnehmen Sie dem JUnit 5-Benutzerhandbuch. (fm) Sie wollen weitere interessante Beiträge zu diversen Themen aus der IT-Welt lesen? Unsere kostenlosen Newsletter liefern Ihnen alles, was IT-Profis wissen sollten – direkt in Ihre Inbox!
Tutorial: Java-Apps mit JUnit 5 testen
srcset="https://b2b-contenthub.com/wp-content/uploads/2025/06/dotshock_shutterstock_2311984335_16z9.jpg?quality=50&strip=all 8224w, https://b2b-contenthub.com/wp-content/uploads/2025/06/dotshock_shutterstock_2311984335_16z9.jpg?resize=300%2C168&quality=50&strip=all 300w, https://b2b-contenthub.com/wp-content/uploads/2025/06/dotshock_shutterstock_2311984335_16z9.jpg?resize=768%2C432&quality=50&strip=all 768w, https://b2b-contenthub.com/wp-content/uploads/2025/06/dotshock_shutterstock_2311984335_16z9.jpg?resize=1024%2C576&quality=50&strip=all 1024w, https://b2b-contenthub.com/wp-content/uploads/2025/06/dotshock_shutterstock_2311984335_16z9.jpg?resize=1536%2C864&quality=50&strip=all 1536w, https://b2b-contenthub.com/wp-content/uploads/2025/06/dotshock_shutterstock_2311984335_16z9.jpg?resize=2048%2C1152&quality=50&strip=all 2048w, https://b2b-contenthub.com/wp-content/uploads/2025/06/dotshock_shutterstock_2311984335_16z9.jpg?resize=1240%2C697&quality=50&strip=all 1240w, https://b2b-contenthub.com/wp-content/uploads/2025/06/dotshock_shutterstock_2311984335_16z9.jpg?resize=150%2C84&quality=50&strip=all 150w, https://b2b-contenthub.com/wp-content/uploads/2025/06/dotshock_shutterstock_2311984335_16z9.jpg?resize=854%2C480&quality=50&strip=all 854w, https://b2b-contenthub.com/wp-content/uploads/2025/06/dotshock_shutterstock_2311984335_16z9.jpg?resize=640%2C360&quality=50&strip=all 640w, https://b2b-contenthub.com/wp-content/uploads/2025/06/dotshock_shutterstock_2311984335_16z9.jpg?resize=444%2C250&quality=50&strip=all 444w" width="1024" height="576" sizes="(max-width: 1024px) 100vw, 1024px">Dieses Tutorial bringt Ihnen Testing mit JUnit 5 näher.dotshock | shutterstock.com JUnit 5 ist der De-facto-Standard, um Unit-Tests in Java zu entwickeln. Eine robuste Testing-Suite gibt Ihnen nicht nur die Sicherheit, dass sich Ihre Applikationen wie gewünscht verhalten, sondern kann auch verhindern, dass sich bei Änderungen unabsichtlich Fehler einschleichen. Dieser Artikel vermittelt Ihnen die Grundlagen, um Ihre Java-Anwendungen mit JUnit 5 zu testen. Ganz konkret erfahren Sie in diesem Tutorial, wie Sie: ein Maven-Projekt für JUnit konfigurieren, grundlegende und parametrisierte Unit-Tests schreiben und die in JUnit 5 integrierten Assertions, Annotationen und Tags nutzen. Den Quellcode für sämtliche Beispiele in diesem Artikel können Sie hier direkt herunterladen (Zip-Archiv). Unit-Testing mit JUnit 5 Sehen wir uns zum Start ein Beispiel dafür an, wie ein Projekt für Unit-Tests mit JUnit 5 konfiguriert wird. Das folgende Listing zeigt eine MathTools-Klasse, deren Methode einen Zähler und einen Nenner zu einem Double-Wert konvertiert. Listing 1: JUnit-5-Beispielprojekt (MathTools.java) package com.javaworld.geekcap.math; public class MathTools { public static double convertToDecimal(int numerator, int denominator) { if (denominator == 0) { throw new IllegalArgumentException("Denominator must not be 0"); } return (double)numerator / (double)denominator; } } Um die MathTools-Klasse und ihre Methode zu testen, stehen zwei primäre Szenarien zur Verfügung: Ein validierter Test, bei dem eine Ganzzahl ungleich Null an den Nenner übergeben wird. Ein Fehlerszenario, bei dem ein Nullwert an den Nenner übergeben wird. Um diese beiden Szenarien zu testen, ist eine JUnit-5-Testklasse nötig. Listing 2: Eine JUnit 5-Testklasse (MathToolsTest.java) package com.javaworld.geekcap.math; import java.lang.IllegalArgumentException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class MathToolsTest { @Test void testConvertToDecimalSuccess() { double result = MathTools.convertToDecimal(3, 4); Assertions.assertEquals(0.75, result); } @Test void testConvertToDecimalInvalidDenominator() { Assertions.assertThrows(IllegalArgumentException.class, () -> MathTools.convertToDecimal(3, 0)); } } In Listing 2 führt die testConvertToDecimalInvalidDenominator-Methode die MathTools::convertToDecimal-Methode innerhalb eines assertThrows-Calls aus. Das erste Argument ist der erwartete Typ der auszulösenden Ausnahme. Das zweite Argument ist eine Funktion, die diese Ausnahme auslöst. Die assertThrows-Methode führt die Funktion aus und überprüft, ob der erwartete Ausnahmetyp ausgelöst wird. Die Assertions-Klasse und ihre Methoden Die Annotation org.junit.jupiter.api.Test kennzeichnet eine Testmethode. Zunächst führt testConvertToDecimalSuccess die MathTools::convertToDecimal-Methode mit einem Zähler von 3 und einem Nenner von 4 aus und überprüft dann, ob das Ergebnis 0,75 ist. Die org.junit.jupiter.api.Assertions-Klasse bietet eine Reihe statischer Methoden, um tatsächliche und erwartete Ergebnisse zu vergleichen. Die Methoden der Assertions-Klasse decken die meisten primitiven Datentypen ab: assertArrayEquals vergleicht den Inhalt eines tatsächlichen Arrays mit einem erwarteten Array. assertEquals vergleicht einen tatsächlichen Wert mit einem erwarteten Wert. assertNotEquals vergleicht zwei Werte, um zu überprüfen, ob sie ungleich sind. assertTrue überprüft, ob der angegebene Wert true ist. assertFalse überprüft, ob der angegebene Wert false ist. assertLinesMatch vergleicht zwei String-Listen. assertNull überprüft, ob der angegebene Wert null ist. assertNotNull überprüft, ob der angegebene Wert nicht null ist. assertSame überprüft, ob zwei Werte auf dasselbe Objekt verweisen. assertNotSame überprüft, ob zwei Werte nicht auf dasselbe Objekt verweisen. assertThrows überprüft, ob eine Methode eine erwartete Ausnahme auslöst, wenn sie ausgeführt wird. assertTimeout überprüft, ob eine angegebene Funktion innerhalb eines definierten Timout-Rahmens abgeschlossen wird. assertTimeoutPreemptively überprüft, ob eine angegebene Funktion innerhalb eines bestimmten Timout-Fensters abgeschlossen wird – führt diese jedoch nicht mehr aus, sobald die definierte Zeit verstrichen ist. Schlägt eine dieser Assertion-Methoden fehl, scheitert auch der Unit-Test und wird entsprechend markiert. Die Fehlermeldung wird bei der Ausführung des Tests auf dem Bildschirm angezeigt und anschließend in einer Berichtsdatei gespeichert. Delta mit assertEquals verwenden Wenn Sie in einem assertEquals Float- und Double-Werte verwenden, können Sie auch ein Delta angeben, das einen Schwellenwert für die Differenz zwischen den beiden verglichenen Werten darstellt. Beispielsweise wird 22/7 häufig als Annäherungswert für PI oder 3,14 verwendet. Allerdings erhalten wir nicht 3,14 wenn wir 22 durch 7 dividieren, sondern 3,14285. Das nachfolgende Listing demonstriert, wie Sie mit einem delta-Wert überprüfen können, ob 22/7 einen Wert zwischen 3,141 und 3,143 zurückgibt. Listing 3: assertEquals mit einem Delta testen @Test void testConvertToDecimalWithDeltaSuccess () { double result = MathTools.convertToDecimal(22, 7); Assertions.assertEquals(3.142, result, 0.001); } In diesem Beispiel erwarten wir 3,142 +/- 0,001, was allen Werten zwischen 3,141 und 3,143 entspricht. Sowohl 3,140 als auch 3,144 würden dazu führen, dass der Test fehlschlägt – mit 3,142857 würde er klappen. Testergebnisse analysieren Assert-Methoden können nicht nur Werte oder Verhalten validieren, sondern akzeptieren auch eine textuelle Beschreibung des Fehlers. Das kann Sie bei der Diagnose unterstützen. Vergegenwärtigen Sie sich anhand des folgenden Outputs zwei Varianten: Assertions.assertEquals(0.75, result, "The MathTools::convertToDecimal value did not return the correct value of 0.75 for 3/4"); Assertions.assertEquals(0.75, result, () -> "The MathTools::convertToDecimal value did not return the correct value of 0.75 for 3/4"); Der Output zeigt den erwarteten Wert 0,75 sowie den tatsächlichen Wert. Außerdem wird die spezifizierte Meldung angezeigt, die dabei helfen kann, den Fehlerkontext zu verstehen. Der Unterschied zwischen den beiden Varianten: Erstere erstellt immer die Meldung, auch wenn sie nicht angezeigt wird. Zweitere nur dann, wenn die Assertion fehlschlägt. In diesem Fall ist es trivial, die Meldung zu erstellen. Trotzdem ist es nicht erforderlich, eine Fehlermeldung für einen Test zu erstellen, der bestanden wurde. Es empfiehlt sich daher in der Regel, auf den zweitgenannten Ansatz zu setzen. Falls Sie für Ihre Tests eine Java-IDE wie IntelliJ einsetzen, wird jede Testmethode mit ihrem Methodennamen angezeigt. Das erfordert, dass Ihre Methodennamen auch lesbar sind. Ansonsten können Sie Ihre Testmethoden auch um @DisplayName-Annotations erweitern, um sie “identifizierbarer” zu gestalten: @Test @DisplayName("Test successful decimal conversion") void testConvertToDecimalSuccess() { double result = MathTools.convertToDecimal(3, 4); Assertions.assertEquals(0.751, result); } Unit-Tests mit Maven fahren Um JUnit-5-Tests aus einem Maven-Projekt heraus auszuführen, müssen Sie das maven-surefire-plugin in die pom.xml-Datei aufnehmen und eine neue Abhängigkeit hinzufügen. Das folgende Listing zeigt pom.xml für dieses Projekt. Listing 4: Maven pom.xml für ein Beispielprojekt mit JUnit 5 4.0.0 org.example JUnitExample 1.0-SNAPSHOT 24 24 UTF-8 org.apache.maven.plugins maven-surefire-plugin 3.5.3 org.junit.jupiter junit-jupiter 5.12.2 test JUnit 5 verpackt seine Komponenten in der org.junit.jupiter-Gruppe und nutzt das Aggregator-Artefakt junit-jupiter, um Abhängigkeiten zu importieren: junit-jupiter-api definiert die API, um Tests und Erweiterungen zu erstellen. junit-jupiter-engine ist die Test-Engine-Implementierung, die die Unit-Tests ausführt. junit-jupiter-params unterstützt parametrisierte Tests. Im nächsten Schritt fügen wir das Maven-Build-Plugin hinzu, um die Tests auszuführen. Schließlich richten wir unseren Build mit den Eigenschaften maven.compiler.source und maven.compiler.target auf Java 24 aus. Testklasse ausführen Jetzt können wir unsere Testklasse ausführen. Dazu nutzen Sie folgenden Befehl: mvn clean test Im Erfolgsfall sollten Sie (in etwa) folgenden Output zu sehen bekommen: [INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running com.javaworld.geekcap.math.MathToolsTest [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.04 s - in com.javaworld.geekcap.math.MathToolsTest [INFO] [INFO] Results: [INFO] [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 3.832 s [INFO] Finished at: 2025-05-21T08:21:15-05:00 [INFO] ------------------------------------------------------------------------ Parametrisierte Tests mit JUnit 5 Da Sie nun wissen, wie man einen simplen Unit-Test mit JUnit 5 erstellt, gehen wir einen Schritt weiter: Die Testklasse in diesem Abschnitt basiert ebenfalls auf der MathTools-Klasse – allerdings nutzen wir nun parametrisierte Tests, um unseren Code gründlicher auf die Probe zu stellen. Dazu ergänzen wir MathTools zunächst um eine weitere Methode namens isEven: public static boolean isEven(int number) { return number % 2 == 0; } Wir könnten diesen Code auf dieselbe Weise testen wie im vorherigen Abschnitt, indem wir verschiedene Zahlen an die isEven-Methode übergeben und die Antwort validieren: @Test void testIsEvenSuccessful() { Assertions.assertTrue(MathTools.isEven(2)); Assertions.assertFalse(MathTools.isEven(1)); } Diese Methodik funktioniert zwar, wird jedoch schnell mühsam, wenn Sie eine große Anzahl von Werten testen möchten und diese manuell eingeben müssen. Um die Werte zu definieren, die wir testen möchten, nutzen wir einen parametrisierten Test: @ParameterizedTest @ValueSource(ints = {0, 2, 4, 6, 8, 10, 100, 1000}) void testIsEven(int number) { Assertions.assertTrue(MathTools.isEven(number)); } Anstelle der @Test-Annotation verwenden wir an dieser Stelle @ParameterizedTest. Außerdem ist es nötig, eine Quelle für die Parameter anzugeben: A note about the ValueSource annotation The ValueSource in the above example accepts an integer array by specifying an ints argument, but the ValueSource annotation also supports booleans, bytes, chars, classes, doubles, floats, longs, shorts, and strings. For example, you might supply a list of String literals: @ValueSource(strings = {"foo", "bar", "baz"}). Quellen in parametrisierten Tests nutzen Es gibt verschiedene Arten von Quellen. Mit der einfachsten – @ValueSource – lässt sich eine Integer- oder String-Liste angeben. Der Parameter wird dabei als Argument an die Testmethode übergeben und kann anschließend im Test genutzt werden. In unserem Beispiel übergeben wir acht gerade Zahlen und überprüfen, ob die Methode MathTools::isEven diese korrekt identifiziert. Bleibt das Problem, sämtliche Werte manuell eingeben zu müssen, die getestet werden sollen. Wollten Sie sämtliche Ganzzahlen zwischen 0 und 1.000 testen, könnten Sie @ValueSource durch @MethodSource ersetzen, um die Zahlenliste zu generieren. Ein Beispiel: @ParameterizedTest @MethodSource("generateEvenNumbers") void testIsEvenRange(int number) { Assertions.assertTrue(MathTools.isEven(number)); } static IntStream generateEvenNumbers() { return IntStream.iterate(0, i -> i + 2).limit(500); } Kommt @MethodSource zum Einsatz, definieren wir eine statische Methode, die einen Stream oder eine Collection zurückgibt. Dabei wird jeder Wert als Methodenargument an unsere Testmethode gesendet. In unserem Beispiel erstellen wir einen IntStream (Integer-Stream). Dieser beginnt bei 0, erhöht sich um jeweils zwei und begrenzt die Gesamtzahl der Elemente im Stream auf 500. Die isEven-Methode wird also 500 Mal aufgerufen, wobei alle geraden Zahlen zwischen 0 und 998 verwendet werden. Parametrisierte Tests unterstützen folgende Quelltypen: ValueSource spezifiziert eine hartkodierte Liste von Ganzzahlen oder Strings. MethodSource ruft eine statische Methode auf, die einen Stream oder eine Collection von Elementen generiert. EnumSource gibt eine Enumeration an, deren Werte an die Testmethode übergeben werden. Das ermöglicht, über sämtliche Enum-Werte zu iterieren oder bestimmte Werte ein- und auszuschließen. CsvSource gibt eine durch Kommas getrennte Liste von Werten an. CsvFileSource spezifiziert einen Pfad zu einer durch Kommas getrennten Value-Datei mit Testdaten. ArgumentsSource ermöglicht, eine Klasse anzugeben, die das ArgumentsProvider-Interface implementiert. Dieses generiert einen Stream von Argumenten, die an die Testmethode übergeben werden. NullSource übergibt null an Ihre Testmethode, wenn Sie mit Strings, Collections oder Arrays arbeiten. Diese Annotation lässt sich in andere (wie ValueSource) inkludieren, um eine Sammlung von Werten (und null) zu testen. EmptySource fügt einen leeren Wert ein, wenn Sie mit Strings, Collections oder Arrays arbeiten. NullAndEmptySource inkludiert sowohl null als auch einen leeren Wert, wenn Sie mit Strings, Collections oder Arrays arbeiten. FieldSource ermöglicht, auf ein oder mehrere Felder der Testklasse (oder externer Klassen) zu verweisen. Darüber hinaus können Sie mit JUnit mehrere “wiederholbare” Quellen nutzen, indem Sie mehrere Quell-Annotationen in Ihrer parametrisierten Testmethode spezifizieren. Zu diesen wiederholbaren Quellen gehören: ValueSource, EnumSource, MethodSource, FieldSource, CsvSource, CsvFileSource , sowie ArgumentsSource. Der Test-Lifecycle von JUnit 5 Bei den meisten Softwaretests empfiehlt es sich, vor und nach jedem Testlauf (beziehungsweise vor und nach allen Testläufen) bestimmte Dinge zu tun. Wollen Sie beispielsweise Datenbankabfragen testen, ist es möglicherweise sinnvoll: vor allen Testläufen eine Verbindung zu einer Datenbank herzustellen und ein Schema importieren, vor jedem einzelnen Test Testdaten einzufügen, nach jedem Test die Datenbank zu bereinigen, sowie nach allen Testläufen das Schema zu löschen und die Datenbankverbindung schließen. Zu diesem Zweck bietet JUnit 5 folgenden Annotationen, die Sie den Methoden Ihrer Testklasse hinzufügen können: @BeforeAll ist eine statische Methode in Ihrer Testklasse, die aufgerufen wird, bevor sie Tests durchführt. @AfterAll ist eine statische Methode in Ihrer Testklasse, die aufgerufen wird, nachdem alle Tests durchgeführt wurden. @BeforeEach ist eine Methode, die vor jedem einzelnen Test aufgerufen wird. @AfterEach ist eine Methode, die nach jedem einzelnen Test aufgerufen wird. Das nachfolgende Listing zeigt ein einfaches Beispiel, das die Aufrufe der verschiedenen Lebenszyklusmethoden protokolliert. Listing 5: JUnit-5-Lebenszyklusmethoden loggen (LifecycleDemoTest.java) package com.javaworld.geekcap.lifecycle; import org.junit.jupiter.api.*; public class LifecycleDemoTest { @BeforeAll static void beforeAll() { System.out.println("Connect to the database"); } @BeforeEach void beforeEach() { System.out.println("Load the schema"); } @AfterEach void afterEach() { System.out.println("Drop the schema"); } @AfterAll static void afterAll() { System.out.println("Disconnect from the database"); } @Test void testOne() { System.out.println("Test One"); } @Test void testTwo() { System.out.println("Test Two"); } } Wenn Sie diesen Test ausführen, erwartet Sie folgender Konsolen-Output: Connect to the database Load the schema Test One Drop the schema Load the schema Test Two Drop the schema Disconnect from the database Hier wird die beforeAll-Methode aufgerufen, die etwa eine Verbindung zu einer Datenbank herstellen oder eine große Datenstruktur im Arbeitsspeicher erstellen könnte. Im nächsten Schritt sorgt die beforeEach-Methode dafür, dass die Daten für jeden Test vorbereitet werden – etwa, indem sie eine Testdatenbank mit einem erwarteten Datensatz befüllt. Anschließend wird der erste Test ausgeführt, gefolgt von der afterEach-Methode. Dieser Prozess (beforeEach—> Test—>afterEach) wird solange fortgesetzt, bis alle Tests abgeschlossen sind. Abschließend bereinigt die afterAll-Methode die Testumgebung, beispielsweise, indem sie die Verbindung zur Datenbank trennt. Tags & Filtering in JUnit 5 Abschließend werfen wir nun noch einen Blick darauf, wie Sie Tags nutzen können, um verschiedene Testfälle selektiv auszuführen. Tags identifizieren und filtern bestimmte Tests, die Sie in verschiedenen Szenarien ausführen möchten. Die Bennenung und der Verwendungszweck von Tags sind frei wählbar. Nachfolgend erstellen wir drei neue Testklassen und kennzeichnen zwei davon als “Development” und eine als “Integration”: Listing 6: JUnit 5-Tags, Test 1 (TestOne.java) package com.javaworld.geekcap.tags; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @Tag("Development") class TestOne { @Test void testOne() { System.out.println("Test 1"); } } Listing 7: JUnit 5 tags, test 2 (TestTwo.java) package com.javaworld.geekcap.tags; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @Tag("Development") class TestTwo { @Test void testTwo() { System.out.println("Test 2"); } } Listing 8: JUnit 5 tags, test 3 (TestThree.java) package com.javaworld.geekcap.tags; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @Tag("Integration") class TestThree { @Test void testThree() { System.out.println("Test 3"); } } Implementiert werden Tags durch Annotationen. Dabei können Sie entweder eine gesamte Test-Klasse oder einzelne Methoden innerhalb einer solchen mit Anmerkungen versehen. Darüber hinaus können Klassen und Methoden auch mehrere Tags aufweisen. In unseren Beispielen sind TestOne und TestTwo mit dem "Development"-, TestThree mit dem "Integration"-Tag versehen. Mit Hilfe der Tags können Sie Testläufe auf verschiedene Weisen filtern. Am einfachsten ist es, einen Test über Ihre Maven-Kommandozeile anzugeben. Im folgenden Beispiel werden beispielsweise nur Tests ausgeführt, die das "Development"-Tag aufweisen: mvn clean test -Dgroups="Development" Die Property groups erlaubt Ihnen, eine durch Kommas getrennte Liste von Tag-Namen für die Tests zu spezifizieren, die JUnit 5 ausführen soll. Das resultiert in folgendem Output: [INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running com.javaworld.geekcap.tags.TestOne Test 1 [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.029 s - in com.javaworld.geekcap.tags.TestOne [INFO] Running com.javaworld.geekcap.tags.TestTwo Test 2 [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s - in com.javaworld.geekcap.tags.TestTwo Folgendermaßen könnten Sie nur die Integrationstests ausführen: mvn clean test -Dgroups="Integration" Oder sowohl Development- als auch Integrationstests: mvn clean test -Dgroups="Development, Integration" Zusätzlich zur Eigenschaft groups können Sie in JUnit 5 auch excludedGroups verwenden, um alle Tests auszuführen, die nicht über das angegebene Tag verfügen. In einer Entwicklungsumgebung möchten wir beispielsweise auf Integrationstests verzichten. Wir könnten deshalb folgendermaßen verfahren: mvn clean test -DexcludedGroups="Integration" Das ist besonders im Fall von großen Anwendungen hilfreich, die Tausende von Tests umfassen können: Wenn Sie später neue Produktionstests hinzufügen möchten, müssen Sie nicht einen Schritt zurückgehen und die anderen 10.000 Tests um ein "Development"-Tag ergänzen. Last, but not least ist es auch möglich, das surefire-Plugin sowohl um groups als auch um excludedGroups zu ergänzen und diese Felder über Maven-Profile zu steuern. Weitere Informationen zu Tags entnehmen Sie dem JUnit 5-Benutzerhandbuch. (fm) Sie wollen weitere interessante Beiträge zu diversen Themen aus der IT-Welt lesen? Unsere kostenlosen Newsletter liefern Ihnen alles, was IT-Profis wissen sollten – direkt in Ihre Inbox!
Tutorial: Java-Apps mit JUnit 5 testen srcset="https://b2b-contenthub.com/wp-content/uploads/2025/06/dotshock_shutterstock_2311984335_16z9.jpg?quality=50&strip=all 8224w, https://b2b-contenthub.com/wp-content/uploads/2025/06/dotshock_shutterstock_2311984335_16z9.jpg?resize=300%2C168&quality=50&strip=all 300w, https://b2b-contenthub.com/wp-content/uploads/2025/06/dotshock_shutterstock_2311984335_16z9.jpg?resize=768%2C432&quality=50&strip=all 768w, https://b2b-contenthub.com/wp-content/uploads/2025/06/dotshock_shutterstock_2311984335_16z9.jpg?resize=1024%2C576&quality=50&strip=all 1024w, https://b2b-contenthub.com/wp-content/uploads/2025/06/dotshock_shutterstock_2311984335_16z9.jpg?resize=1536%2C864&quality=50&strip=all 1536w, https://b2b-contenthub.com/wp-content/uploads/2025/06/dotshock_shutterstock_2311984335_16z9.jpg?resize=2048%2C1152&quality=50&strip=all 2048w, https://b2b-contenthub.com/wp-content/uploads/2025/06/dotshock_shutterstock_2311984335_16z9.jpg?resize=1240%2C697&quality=50&strip=all 1240w, https://b2b-contenthub.com/wp-content/uploads/2025/06/dotshock_shutterstock_2311984335_16z9.jpg?resize=150%2C84&quality=50&strip=all 150w, https://b2b-contenthub.com/wp-content/uploads/2025/06/dotshock_shutterstock_2311984335_16z9.jpg?resize=854%2C480&quality=50&strip=all 854w, https://b2b-contenthub.com/wp-content/uploads/2025/06/dotshock_shutterstock_2311984335_16z9.jpg?resize=640%2C360&quality=50&strip=all 640w, https://b2b-contenthub.com/wp-content/uploads/2025/06/dotshock_shutterstock_2311984335_16z9.jpg?resize=444%2C250&quality=50&strip=all 444w" width="1024" height="576" sizes="(max-width: 1024px) 100vw, 1024px">Dieses Tutorial bringt Ihnen Testing mit JUnit 5 näher.dotshock | shutterstock.com JUnit 5 ist der De-facto-Standard, um Unit-Tests in Java zu entwickeln. Eine robuste Testing-Suite gibt Ihnen nicht nur die Sicherheit, dass sich Ihre Applikationen wie gewünscht verhalten, sondern kann auch verhindern, dass sich bei Änderungen unabsichtlich Fehler einschleichen. Dieser Artikel vermittelt Ihnen die Grundlagen, um Ihre Java-Anwendungen mit JUnit 5 zu testen. Ganz konkret erfahren Sie in diesem Tutorial, wie Sie: ein Maven-Projekt für JUnit konfigurieren, grundlegende und parametrisierte Unit-Tests schreiben und die in JUnit 5 integrierten Assertions, Annotationen und Tags nutzen. Den Quellcode für sämtliche Beispiele in diesem Artikel können Sie hier direkt herunterladen (Zip-Archiv). Unit-Testing mit JUnit 5 Sehen wir uns zum Start ein Beispiel dafür an, wie ein Projekt für Unit-Tests mit JUnit 5 konfiguriert wird. Das folgende Listing zeigt eine MathTools-Klasse, deren Methode einen Zähler und einen Nenner zu einem Double-Wert konvertiert. Listing 1: JUnit-5-Beispielprojekt (MathTools.java) package com.javaworld.geekcap.math; public class MathTools { public static double convertToDecimal(int numerator, int denominator) { if (denominator == 0) { throw new IllegalArgumentException("Denominator must not be 0"); } return (double)numerator / (double)denominator; } } Um die MathTools-Klasse und ihre Methode zu testen, stehen zwei primäre Szenarien zur Verfügung: Ein validierter Test, bei dem eine Ganzzahl ungleich Null an den Nenner übergeben wird. Ein Fehlerszenario, bei dem ein Nullwert an den Nenner übergeben wird. Um diese beiden Szenarien zu testen, ist eine JUnit-5-Testklasse nötig. Listing 2: Eine JUnit 5-Testklasse (MathToolsTest.java) package com.javaworld.geekcap.math; import java.lang.IllegalArgumentException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class MathToolsTest { @Test void testConvertToDecimalSuccess() { double result = MathTools.convertToDecimal(3, 4); Assertions.assertEquals(0.75, result); } @Test void testConvertToDecimalInvalidDenominator() { Assertions.assertThrows(IllegalArgumentException.class, () -> MathTools.convertToDecimal(3, 0)); } } In Listing 2 führt die testConvertToDecimalInvalidDenominator-Methode die MathTools::convertToDecimal-Methode innerhalb eines assertThrows-Calls aus. Das erste Argument ist der erwartete Typ der auszulösenden Ausnahme. Das zweite Argument ist eine Funktion, die diese Ausnahme auslöst. Die assertThrows-Methode führt die Funktion aus und überprüft, ob der erwartete Ausnahmetyp ausgelöst wird. Die Assertions-Klasse und ihre Methoden Die Annotation org.junit.jupiter.api.Test kennzeichnet eine Testmethode. Zunächst führt testConvertToDecimalSuccess die MathTools::convertToDecimal-Methode mit einem Zähler von 3 und einem Nenner von 4 aus und überprüft dann, ob das Ergebnis 0,75 ist. Die org.junit.jupiter.api.Assertions-Klasse bietet eine Reihe statischer Methoden, um tatsächliche und erwartete Ergebnisse zu vergleichen. Die Methoden der Assertions-Klasse decken die meisten primitiven Datentypen ab: assertArrayEquals vergleicht den Inhalt eines tatsächlichen Arrays mit einem erwarteten Array. assertEquals vergleicht einen tatsächlichen Wert mit einem erwarteten Wert. assertNotEquals vergleicht zwei Werte, um zu überprüfen, ob sie ungleich sind. assertTrue überprüft, ob der angegebene Wert true ist. assertFalse überprüft, ob der angegebene Wert false ist. assertLinesMatch vergleicht zwei String-Listen. assertNull überprüft, ob der angegebene Wert null ist. assertNotNull überprüft, ob der angegebene Wert nicht null ist. assertSame überprüft, ob zwei Werte auf dasselbe Objekt verweisen. assertNotSame überprüft, ob zwei Werte nicht auf dasselbe Objekt verweisen. assertThrows überprüft, ob eine Methode eine erwartete Ausnahme auslöst, wenn sie ausgeführt wird. assertTimeout überprüft, ob eine angegebene Funktion innerhalb eines definierten Timout-Rahmens abgeschlossen wird. assertTimeoutPreemptively überprüft, ob eine angegebene Funktion innerhalb eines bestimmten Timout-Fensters abgeschlossen wird – führt diese jedoch nicht mehr aus, sobald die definierte Zeit verstrichen ist. Schlägt eine dieser Assertion-Methoden fehl, scheitert auch der Unit-Test und wird entsprechend markiert. Die Fehlermeldung wird bei der Ausführung des Tests auf dem Bildschirm angezeigt und anschließend in einer Berichtsdatei gespeichert. Delta mit assertEquals verwenden Wenn Sie in einem assertEquals Float- und Double-Werte verwenden, können Sie auch ein Delta angeben, das einen Schwellenwert für die Differenz zwischen den beiden verglichenen Werten darstellt. Beispielsweise wird 22/7 häufig als Annäherungswert für PI oder 3,14 verwendet. Allerdings erhalten wir nicht 3,14 wenn wir 22 durch 7 dividieren, sondern 3,14285. Das nachfolgende Listing demonstriert, wie Sie mit einem delta-Wert überprüfen können, ob 22/7 einen Wert zwischen 3,141 und 3,143 zurückgibt. Listing 3: assertEquals mit einem Delta testen @Test void testConvertToDecimalWithDeltaSuccess () { double result = MathTools.convertToDecimal(22, 7); Assertions.assertEquals(3.142, result, 0.001); } In diesem Beispiel erwarten wir 3,142 +/- 0,001, was allen Werten zwischen 3,141 und 3,143 entspricht. Sowohl 3,140 als auch 3,144 würden dazu führen, dass der Test fehlschlägt – mit 3,142857 würde er klappen. Testergebnisse analysieren Assert-Methoden können nicht nur Werte oder Verhalten validieren, sondern akzeptieren auch eine textuelle Beschreibung des Fehlers. Das kann Sie bei der Diagnose unterstützen. Vergegenwärtigen Sie sich anhand des folgenden Outputs zwei Varianten: Assertions.assertEquals(0.75, result, "The MathTools::convertToDecimal value did not return the correct value of 0.75 for 3/4"); Assertions.assertEquals(0.75, result, () -> "The MathTools::convertToDecimal value did not return the correct value of 0.75 for 3/4"); Der Output zeigt den erwarteten Wert 0,75 sowie den tatsächlichen Wert. Außerdem wird die spezifizierte Meldung angezeigt, die dabei helfen kann, den Fehlerkontext zu verstehen. Der Unterschied zwischen den beiden Varianten: Erstere erstellt immer die Meldung, auch wenn sie nicht angezeigt wird. Zweitere nur dann, wenn die Assertion fehlschlägt. In diesem Fall ist es trivial, die Meldung zu erstellen. Trotzdem ist es nicht erforderlich, eine Fehlermeldung für einen Test zu erstellen, der bestanden wurde. Es empfiehlt sich daher in der Regel, auf den zweitgenannten Ansatz zu setzen. Falls Sie für Ihre Tests eine Java-IDE wie IntelliJ einsetzen, wird jede Testmethode mit ihrem Methodennamen angezeigt. Das erfordert, dass Ihre Methodennamen auch lesbar sind. Ansonsten können Sie Ihre Testmethoden auch um @DisplayName-Annotations erweitern, um sie “identifizierbarer” zu gestalten: @Test @DisplayName("Test successful decimal conversion") void testConvertToDecimalSuccess() { double result = MathTools.convertToDecimal(3, 4); Assertions.assertEquals(0.751, result); } Unit-Tests mit Maven fahren Um JUnit-5-Tests aus einem Maven-Projekt heraus auszuführen, müssen Sie das maven-surefire-plugin in die pom.xml-Datei aufnehmen und eine neue Abhängigkeit hinzufügen. Das folgende Listing zeigt pom.xml für dieses Projekt. Listing 4: Maven pom.xml für ein Beispielprojekt mit JUnit 5 4.0.0 org.example JUnitExample 1.0-SNAPSHOT 24 24 UTF-8 org.apache.maven.plugins maven-surefire-plugin 3.5.3 org.junit.jupiter junit-jupiter 5.12.2 test JUnit 5 verpackt seine Komponenten in der org.junit.jupiter-Gruppe und nutzt das Aggregator-Artefakt junit-jupiter, um Abhängigkeiten zu importieren: junit-jupiter-api definiert die API, um Tests und Erweiterungen zu erstellen. junit-jupiter-engine ist die Test-Engine-Implementierung, die die Unit-Tests ausführt. junit-jupiter-params unterstützt parametrisierte Tests. Im nächsten Schritt fügen wir das Maven-Build-Plugin hinzu, um die Tests auszuführen. Schließlich richten wir unseren Build mit den Eigenschaften maven.compiler.source und maven.compiler.target auf Java 24 aus. Testklasse ausführen Jetzt können wir unsere Testklasse ausführen. Dazu nutzen Sie folgenden Befehl: mvn clean test Im Erfolgsfall sollten Sie (in etwa) folgenden Output zu sehen bekommen: [INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running com.javaworld.geekcap.math.MathToolsTest [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.04 s - in com.javaworld.geekcap.math.MathToolsTest [INFO] [INFO] Results: [INFO] [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 3.832 s [INFO] Finished at: 2025-05-21T08:21:15-05:00 [INFO] ------------------------------------------------------------------------ Parametrisierte Tests mit JUnit 5 Da Sie nun wissen, wie man einen simplen Unit-Test mit JUnit 5 erstellt, gehen wir einen Schritt weiter: Die Testklasse in diesem Abschnitt basiert ebenfalls auf der MathTools-Klasse – allerdings nutzen wir nun parametrisierte Tests, um unseren Code gründlicher auf die Probe zu stellen. Dazu ergänzen wir MathTools zunächst um eine weitere Methode namens isEven: public static boolean isEven(int number) { return number % 2 == 0; } Wir könnten diesen Code auf dieselbe Weise testen wie im vorherigen Abschnitt, indem wir verschiedene Zahlen an die isEven-Methode übergeben und die Antwort validieren: @Test void testIsEvenSuccessful() { Assertions.assertTrue(MathTools.isEven(2)); Assertions.assertFalse(MathTools.isEven(1)); } Diese Methodik funktioniert zwar, wird jedoch schnell mühsam, wenn Sie eine große Anzahl von Werten testen möchten und diese manuell eingeben müssen. Um die Werte zu definieren, die wir testen möchten, nutzen wir einen parametrisierten Test: @ParameterizedTest @ValueSource(ints = {0, 2, 4, 6, 8, 10, 100, 1000}) void testIsEven(int number) { Assertions.assertTrue(MathTools.isEven(number)); } Anstelle der @Test-Annotation verwenden wir an dieser Stelle @ParameterizedTest. Außerdem ist es nötig, eine Quelle für die Parameter anzugeben: A note about the ValueSource annotation The ValueSource in the above example accepts an integer array by specifying an ints argument, but the ValueSource annotation also supports booleans, bytes, chars, classes, doubles, floats, longs, shorts, and strings. For example, you might supply a list of String literals: @ValueSource(strings = {"foo", "bar", "baz"}). Quellen in parametrisierten Tests nutzen Es gibt verschiedene Arten von Quellen. Mit der einfachsten – @ValueSource – lässt sich eine Integer- oder String-Liste angeben. Der Parameter wird dabei als Argument an die Testmethode übergeben und kann anschließend im Test genutzt werden. In unserem Beispiel übergeben wir acht gerade Zahlen und überprüfen, ob die Methode MathTools::isEven diese korrekt identifiziert. Bleibt das Problem, sämtliche Werte manuell eingeben zu müssen, die getestet werden sollen. Wollten Sie sämtliche Ganzzahlen zwischen 0 und 1.000 testen, könnten Sie @ValueSource durch @MethodSource ersetzen, um die Zahlenliste zu generieren. Ein Beispiel: @ParameterizedTest @MethodSource("generateEvenNumbers") void testIsEvenRange(int number) { Assertions.assertTrue(MathTools.isEven(number)); } static IntStream generateEvenNumbers() { return IntStream.iterate(0, i -> i + 2).limit(500); } Kommt @MethodSource zum Einsatz, definieren wir eine statische Methode, die einen Stream oder eine Collection zurückgibt. Dabei wird jeder Wert als Methodenargument an unsere Testmethode gesendet. In unserem Beispiel erstellen wir einen IntStream (Integer-Stream). Dieser beginnt bei 0, erhöht sich um jeweils zwei und begrenzt die Gesamtzahl der Elemente im Stream auf 500. Die isEven-Methode wird also 500 Mal aufgerufen, wobei alle geraden Zahlen zwischen 0 und 998 verwendet werden. Parametrisierte Tests unterstützen folgende Quelltypen: ValueSource spezifiziert eine hartkodierte Liste von Ganzzahlen oder Strings. MethodSource ruft eine statische Methode auf, die einen Stream oder eine Collection von Elementen generiert. EnumSource gibt eine Enumeration an, deren Werte an die Testmethode übergeben werden. Das ermöglicht, über sämtliche Enum-Werte zu iterieren oder bestimmte Werte ein- und auszuschließen. CsvSource gibt eine durch Kommas getrennte Liste von Werten an. CsvFileSource spezifiziert einen Pfad zu einer durch Kommas getrennten Value-Datei mit Testdaten. ArgumentsSource ermöglicht, eine Klasse anzugeben, die das ArgumentsProvider-Interface implementiert. Dieses generiert einen Stream von Argumenten, die an die Testmethode übergeben werden. NullSource übergibt null an Ihre Testmethode, wenn Sie mit Strings, Collections oder Arrays arbeiten. Diese Annotation lässt sich in andere (wie ValueSource) inkludieren, um eine Sammlung von Werten (und null) zu testen. EmptySource fügt einen leeren Wert ein, wenn Sie mit Strings, Collections oder Arrays arbeiten. NullAndEmptySource inkludiert sowohl null als auch einen leeren Wert, wenn Sie mit Strings, Collections oder Arrays arbeiten. FieldSource ermöglicht, auf ein oder mehrere Felder der Testklasse (oder externer Klassen) zu verweisen. Darüber hinaus können Sie mit JUnit mehrere “wiederholbare” Quellen nutzen, indem Sie mehrere Quell-Annotationen in Ihrer parametrisierten Testmethode spezifizieren. Zu diesen wiederholbaren Quellen gehören: ValueSource, EnumSource, MethodSource, FieldSource, CsvSource, CsvFileSource , sowie ArgumentsSource. Der Test-Lifecycle von JUnit 5 Bei den meisten Softwaretests empfiehlt es sich, vor und nach jedem Testlauf (beziehungsweise vor und nach allen Testläufen) bestimmte Dinge zu tun. Wollen Sie beispielsweise Datenbankabfragen testen, ist es möglicherweise sinnvoll: vor allen Testläufen eine Verbindung zu einer Datenbank herzustellen und ein Schema importieren, vor jedem einzelnen Test Testdaten einzufügen, nach jedem Test die Datenbank zu bereinigen, sowie nach allen Testläufen das Schema zu löschen und die Datenbankverbindung schließen. Zu diesem Zweck bietet JUnit 5 folgenden Annotationen, die Sie den Methoden Ihrer Testklasse hinzufügen können: @BeforeAll ist eine statische Methode in Ihrer Testklasse, die aufgerufen wird, bevor sie Tests durchführt. @AfterAll ist eine statische Methode in Ihrer Testklasse, die aufgerufen wird, nachdem alle Tests durchgeführt wurden. @BeforeEach ist eine Methode, die vor jedem einzelnen Test aufgerufen wird. @AfterEach ist eine Methode, die nach jedem einzelnen Test aufgerufen wird. Das nachfolgende Listing zeigt ein einfaches Beispiel, das die Aufrufe der verschiedenen Lebenszyklusmethoden protokolliert. Listing 5: JUnit-5-Lebenszyklusmethoden loggen (LifecycleDemoTest.java) package com.javaworld.geekcap.lifecycle; import org.junit.jupiter.api.*; public class LifecycleDemoTest { @BeforeAll static void beforeAll() { System.out.println("Connect to the database"); } @BeforeEach void beforeEach() { System.out.println("Load the schema"); } @AfterEach void afterEach() { System.out.println("Drop the schema"); } @AfterAll static void afterAll() { System.out.println("Disconnect from the database"); } @Test void testOne() { System.out.println("Test One"); } @Test void testTwo() { System.out.println("Test Two"); } } Wenn Sie diesen Test ausführen, erwartet Sie folgender Konsolen-Output: Connect to the database Load the schema Test One Drop the schema Load the schema Test Two Drop the schema Disconnect from the database Hier wird die beforeAll-Methode aufgerufen, die etwa eine Verbindung zu einer Datenbank herstellen oder eine große Datenstruktur im Arbeitsspeicher erstellen könnte. Im nächsten Schritt sorgt die beforeEach-Methode dafür, dass die Daten für jeden Test vorbereitet werden – etwa, indem sie eine Testdatenbank mit einem erwarteten Datensatz befüllt. Anschließend wird der erste Test ausgeführt, gefolgt von der afterEach-Methode. Dieser Prozess (beforeEach—> Test—>afterEach) wird solange fortgesetzt, bis alle Tests abgeschlossen sind. Abschließend bereinigt die afterAll-Methode die Testumgebung, beispielsweise, indem sie die Verbindung zur Datenbank trennt. Tags & Filtering in JUnit 5 Abschließend werfen wir nun noch einen Blick darauf, wie Sie Tags nutzen können, um verschiedene Testfälle selektiv auszuführen. Tags identifizieren und filtern bestimmte Tests, die Sie in verschiedenen Szenarien ausführen möchten. Die Bennenung und der Verwendungszweck von Tags sind frei wählbar. Nachfolgend erstellen wir drei neue Testklassen und kennzeichnen zwei davon als “Development” und eine als “Integration”: Listing 6: JUnit 5-Tags, Test 1 (TestOne.java) package com.javaworld.geekcap.tags; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @Tag("Development") class TestOne { @Test void testOne() { System.out.println("Test 1"); } } Listing 7: JUnit 5 tags, test 2 (TestTwo.java) package com.javaworld.geekcap.tags; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @Tag("Development") class TestTwo { @Test void testTwo() { System.out.println("Test 2"); } } Listing 8: JUnit 5 tags, test 3 (TestThree.java) package com.javaworld.geekcap.tags; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @Tag("Integration") class TestThree { @Test void testThree() { System.out.println("Test 3"); } } Implementiert werden Tags durch Annotationen. Dabei können Sie entweder eine gesamte Test-Klasse oder einzelne Methoden innerhalb einer solchen mit Anmerkungen versehen. Darüber hinaus können Klassen und Methoden auch mehrere Tags aufweisen. In unseren Beispielen sind TestOne und TestTwo mit dem "Development"-, TestThree mit dem "Integration"-Tag versehen. Mit Hilfe der Tags können Sie Testläufe auf verschiedene Weisen filtern. Am einfachsten ist es, einen Test über Ihre Maven-Kommandozeile anzugeben. Im folgenden Beispiel werden beispielsweise nur Tests ausgeführt, die das "Development"-Tag aufweisen: mvn clean test -Dgroups="Development" Die Property groups erlaubt Ihnen, eine durch Kommas getrennte Liste von Tag-Namen für die Tests zu spezifizieren, die JUnit 5 ausführen soll. Das resultiert in folgendem Output: [INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running com.javaworld.geekcap.tags.TestOne Test 1 [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.029 s - in com.javaworld.geekcap.tags.TestOne [INFO] Running com.javaworld.geekcap.tags.TestTwo Test 2 [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s - in com.javaworld.geekcap.tags.TestTwo Folgendermaßen könnten Sie nur die Integrationstests ausführen: mvn clean test -Dgroups="Integration" Oder sowohl Development- als auch Integrationstests: mvn clean test -Dgroups="Development, Integration" Zusätzlich zur Eigenschaft groups können Sie in JUnit 5 auch excludedGroups verwenden, um alle Tests auszuführen, die nicht über das angegebene Tag verfügen. In einer Entwicklungsumgebung möchten wir beispielsweise auf Integrationstests verzichten. Wir könnten deshalb folgendermaßen verfahren: mvn clean test -DexcludedGroups="Integration" Das ist besonders im Fall von großen Anwendungen hilfreich, die Tausende von Tests umfassen können: Wenn Sie später neue Produktionstests hinzufügen möchten, müssen Sie nicht einen Schritt zurückgehen und die anderen 10.000 Tests um ein "Development"-Tag ergänzen. Last, but not least ist es auch möglich, das surefire-Plugin sowohl um groups als auch um excludedGroups zu ergänzen und diese Felder über Maven-Profile zu steuern. Weitere Informationen zu Tags entnehmen Sie dem JUnit 5-Benutzerhandbuch. (fm) Sie wollen weitere interessante Beiträge zu diversen Themen aus der IT-Welt lesen? Unsere kostenlosen Newsletter liefern Ihnen alles, was IT-Profis wissen sollten – direkt in Ihre Inbox!