JUnit 5-Tutorial, Teil 1: Unit-Tests mit JUnit 5, Mockito und Hamcrest
JUnit 5 ist der neue De-facto-Standard für die Entwicklung von Unit-Tests in Java. Diese neueste Version hat die Einschränkungen von Java 5 hinter sich gelassen und viele Funktionen von Java 8 integriert, insbesondere die Unterstützung von Lambda-Ausdrücken.
In dieser ersten Hälfte einer zweiteiligen Einführung in JUnit 5 beginnen Sie mit dem Testen mit JUnit 5. Ich zeige Ihnen, wie Sie ein Maven-Projekt für die Verwendung von JUnit 5 konfigurieren, wie Sie Tests mit den Anmerkungen @Test
und schreiben. @ParameterizedTest
und wie Sie mit den neuen Lebenszyklusanmerkungen in JUnit 5 arbeiten. Außerdem sehen Sie ein kurzes Beispiel für die Verwendung von Filter-Tags. Außerdem zeigen wir Ihnen, wie Sie JUnit 5 in eine Assertionsbibliothek eines Drittanbieters integrieren - in diesem Fall Hamcrest . Schließlich erhalten Sie eine kurze Einführung in die Integration von JUnit 5 in Mockito, damit Sie robustere Komponententests für komplexe, reale Systeme schreiben können.
Testgetriebene Entwicklung
Wenn Sie Java-Code für einen bestimmten Zeitraum entwickelt haben, sind Sie wahrscheinlich mit der testgetriebenen Entwicklung bestens vertraut. Daher werde ich diesen Abschnitt kurz halten. Es ist jedoch wichtig zu verstehen, warum wir Komponententests schreiben und welche Strategien Entwickler beim Entwerfen von Komponententests anwenden.
Testgetriebene Entwicklung (TDD) ist ein Softwareentwicklungsprozess, der Codierung, Test und Design miteinander verbindet. Es ist ein Test-First-Ansatz, der darauf abzielt, die Qualität Ihrer Anwendungen zu verbessern. Testgetriebene Entwicklung wird durch den folgenden Lebenszyklus definiert:
- Fügen Sie einen Test hinzu.
- Führen Sie alle Ihre Tests aus und beobachten Sie, dass der neue Test fehlschlägt.
- Implementieren Sie den Code.
- Führen Sie alle Ihre Tests aus und beobachten Sie, ob der neue Test erfolgreich ist.
- Refactor den Code.
Abbildung 1 zeigt diesen TDD-Lebenszyklus.

Das Schreiben von Tests vor dem Schreiben Ihres Codes hat zwei Gründe. Erstens zwingt es Sie, über das Geschäftsproblem nachzudenken, das Sie lösen möchten. Wie sollten sich beispielsweise erfolgreiche Szenarien verhalten? Welche Bedingungen sollten fehlschlagen? Wie sollen sie scheitern? Zweitens gibt Ihnen das Testen zuerst mehr Vertrauen in Ihre Tests. Wenn ich Tests schreibe, nachdem ich Code geschrieben habe, muss ich sie immer brechen, um sicherzustellen, dass sie tatsächlich Fehler abfangen. Durch das Schreiben von Tests wird dieser zusätzliche Schritt vermieden.
Das Schreiben von Tests für den glücklichen Pfad ist normalerweise einfach: Bei guter Eingabe sollte die Klasse eine deterministische Antwort zurückgeben. Das Schreiben negativer (oder fehlerhafter) Testfälle, insbesondere für komplexe Komponenten, kann jedoch komplizierter sein.
Schreiben Sie beispielsweise Tests für ein Datenbank-Repository. Auf dem Happy Path fügen wir einen Datensatz in die Datenbank ein und erhalten das erstellte Objekt einschließlich aller generierten Schlüssel zurück. In der Realität müssen wir auch die Möglichkeit eines Konflikts berücksichtigen, z. B. das Einfügen eines Datensatzes mit einem eindeutigen Spaltenwert, der bereits von einem anderen Datensatz gehalten wird. Was passiert außerdem, wenn das Repository keine Verbindung zur Datenbank herstellen kann, möglicherweise weil sich der Benutzername oder das Kennwort geändert haben? Was passiert, wenn während der Übertragung ein Netzwerkfehler auftritt? Was passiert, wenn die Anforderung nicht innerhalb Ihres festgelegten Zeitlimits abgeschlossen wird?
Um eine robuste Komponente zu erstellen, müssen Sie alle wahrscheinlichen und unwahrscheinlichen Szenarien berücksichtigen, Tests für sie entwickeln und Ihren Code schreiben, um diese Tests zu erfüllen. Später in diesem Artikel werden Strategien zum Erstellen verschiedener Fehlerszenarien sowie einige der neuen Funktionen in JUnit 5 vorgestellt, mit denen Sie diese Szenarien testen können.
JUnit übernehmen 5
Wenn Sie JUnit schon länger verwenden, werden einige Änderungen in JUnit 5 angepasst. Hier ist eine allgemeine Zusammenfassung der Unterschiede zwischen den beiden Versionen:
- JUnit 5 ist jetzt in der
org.junit.jupiter
Gruppe enthalten, wodurch geändert wird, wie Sie es in Ihre Maven- und Gradle-Projekte aufnehmen. - Für Einheit 4 ist ein Mindest-JDK von JDK 5 erforderlich. Für JUnit 5 ist mindestens JDK 8 erforderlich.
- JUnit 4 ist
@Before
,@BeforeClass
,@After
und@AfterClass
Anmerkungen wurden ersetzt durch@BeforeEach
,@BeforeAll
,@AfterEach
, und@AfterAll
, respectively. - Die
@Ignore
Annotation von JUnit 4 wurde durch die@Disabled
Annotation ersetzt. - Die
@Category
Anmerkung wurde durch die@Tag
Anmerkung ersetzt. - JUnit 5 fügt einen neuen Satz von Assertionsmethoden hinzu.
- Läufer wurden durch Erweiterungen ersetzt, mit einer neuen API für Erweiterungsimplementierer.
- JUnit 5 führt Annahmen ein, die die Ausführung eines Tests verhindern.
- JUnit 5 unterstützt verschachtelte und dynamische Testklassen.
Wir werden die meisten dieser neuen Funktionen in diesem Artikel untersuchen.
Unit-Test mit JUnit 5
Beginnen wir einfach mit einem End-to-End-Beispiel für die Konfiguration eines Projekts zur Verwendung von JUnit 5 für einen Komponententest. Listing 1 zeigt eine MathTools
Klasse, deren Methode einen Zähler und einen Nenner in a konvertiert double
.
Listing 1. Ein Beispiel für ein JUnit 5-Projekt (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; } }
Wir haben zwei primäre Szenarien zum Testen der MathTools
Klasse und ihrer Methode:
- Ein gültiger Test , bei dem wir Ganzzahlen ungleich Null für Zähler und Nenner übergeben.
- Ein Fehlerszenario , in dem wir einen Nullwert für den Nenner übergeben.
Listing 2 zeigt eine JUnit 5-Testklasse zum Testen dieser beiden Szenarien.
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
Aufrufs aus. Das erste Argument ist die erwartete Art 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 org.junit.jupiter.api.Test
Anmerkung bezeichnet eine Testmethode. Beachten Sie, dass die @Test
Anmerkung jetzt aus dem JUnit 5 Jupiter API-Paket anstelle des JUnit 4- org.junit
Pakets stammt. Die testConvertToDecimalSuccess
Methode führt die MathTools::convertToDecimal
Methode zuerst mit einem Zähler von 3 und einem Nenner von 4 aus und behauptet dann, dass das Ergebnis gleich 0,75 ist. Die org.junit.jupiter.api.Assertions
Klasse bietet eine Reihe von static
Methoden zum Vergleichen der tatsächlichen und erwarteten Ergebnisse. Die Assertions
Klasse verfügt über die folgenden Methoden, die die meisten primitiven Datentypen abdecken:
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 nicht gleich sind.assertTrue
Überprüft, ob der angegebene Wert wahr ist.assertFalse
Überprüft, ob der angegebene Wert falsch ist.assertLinesMatch
vergleicht zwei Listen vonString
s.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, dass zwei Werte nicht auf dasselbe Objekt verweisen.assertThrows
Überprüft, ob die Ausführung einer Methode eine erwartete Ausnahme auslöst (dies sehen Sie imtestConvertToDecimalInvalidDenominator
obigen Beispiel).assertTimeout
Überprüft, ob eine bereitgestellte Funktion innerhalb eines bestimmten Zeitlimits abgeschlossen ist.assertTimeoutPreemptively
Überprüft, ob eine bereitgestellte Funktion innerhalb eines bestimmten Zeitlimits abgeschlossen ist. Sobald das Zeitlimit erreicht ist, wird die Ausführung der Funktion abgebrochen.
Wenn eine dieser Assertionsmethoden fehlschlägt, wird der Komponententest als fehlgeschlagen markiert. Diese Fehlermeldung wird beim Ausführen des Tests auf den Bildschirm geschrieben und dann in einer Berichtsdatei gespeichert.
Verwenden von Delta mit assertEquals
Wenn Sie float
und double
Werte in a verwenden assertEquals
, können Sie auch a angeben delta
, das einen Differenzschwellenwert zwischen den beiden darstellt. In unserem Beispiel hätten wir ein Delta von 0,001 hinzufügen können, wenn 0,75 tatsächlich als 0,750001 zurückgegeben wurde.
Analysieren Sie Ihre Testergebnisse
Zusätzlich zur Überprüfung eines Werts oder Verhaltens können die assert
Methoden auch eine Textbeschreibung des Fehlers akzeptieren, mit deren Hilfe Sie Fehler diagnostizieren können. Zum Beispiel:
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");
Die Ausgabe zeigt den erwarteten Wert von 0,75 und den tatsächlichen Wert. Außerdem wird die angegebene Meldung angezeigt, die Ihnen helfen kann, den Kontext des Fehlers zu verstehen. Der Unterschied zwischen den beiden Varianten besteht darin, dass die erste die Nachricht immer erstellt, auch wenn sie nicht angezeigt wird, während die zweite die Nachricht nur erstellt, wenn die Zusicherung fehlschlägt. In diesem Fall ist die Konstruktion der Nachricht trivial, daher spielt es keine Rolle. Es ist jedoch nicht erforderlich, eine Fehlermeldung für einen bestandenen Test zu erstellen. Daher ist es normalerweise eine bewährte Methode, den zweiten Stil zu verwenden.
Wenn Sie zum Ausführen Ihrer Tests eine IDE wie IntelliJ verwenden, wird jede Testmethode anhand ihres Methodennamens angezeigt. Dies ist in Ordnung, wenn Ihre Methodennamen lesbar sind. Sie können @DisplayName
Ihren Testmethoden jedoch auch eine Anmerkung hinzufügen , um die Tests besser zu identifizieren:
@Test @DisplayName("Test successful decimal conversion") void testConvertToDecimalSuccess() { double result = MathTools.convertToDecimal(3, 4); Assertions.assertEquals(0.751, result); }
Ausführen Ihres Unit-Tests
Um JUnit 5-Tests in einem Maven-Projekt auszuführen, müssen Sie die maven-surefire-plugin
in die Maven- pom.xml
Datei aufnehmen und eine neue Abhängigkeit hinzufügen. Listing 3 zeigt die pom.xml
Datei für dieses Projekt.
Listing 3. Maven pom.xml für ein Beispielprojekt für JUnit 5
4.0.0 com.javaworld.geekcap junit5 jar 1.0-SNAPSHOT org.apache.maven.plugins maven-compiler-plugin 3.8.1 8 8 org.apache.maven.plugins maven-surefire-plugin 3.0.0-M4 junit5 //maven.apache.org org.junit.jupiter junit-jupiter 5.6.0 test
JUnit 5-Abhängigkeiten
JUnit 5 packt seine Komponenten in die org.junit.jupiter
Gruppe und wir müssen das junit-jupiter
Artefakt hinzufügen , ein Aggregator-Artefakt, das die folgenden Abhängigkeiten importiert:
junit-jupiter-api
Definiert die API zum Schreiben von Tests und Erweiterungen.junit-jupiter-engine
ist die Test-Engine-Implementierung, die die Unit-Tests ausführt.junit-jupiter-params
bietet Unterstützung für parametrisierte Tests.
Als nächstes müssen wir das maven-surefire-plugin
Build-Plug-In hinzufügen , um die Tests auszuführen.
Stellen Sie schließlich sicher, dass Sie das maven-compiler-plugin
in einer Version von Java 8 oder höher enthalten, damit Sie Java 8-Funktionen wie Lambdas verwenden können.
Starte es!
Verwenden Sie den folgenden Befehl, um die Testklasse von Ihrer IDE oder von Maven aus auszuführen:
mvn clean test
Wenn Sie erfolgreich sind, sollte eine Ausgabe ähnlich der folgenden angezeigt werden:
[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: 2020-02-16T08:21:15-05:00 [INFO] ------------------------------------------------------------------------