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 @Testund schreiben. @ParameterizedTestund 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.

download Holen Sie sich den Code Holen Sie sich den Quellcode für Beispiele in diesem Tutorial. Erstellt von Steven Haines für JavaWorld.

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:

  1. Fügen Sie einen Test hinzu.
  2. Führen Sie alle Ihre Tests aus und beobachten Sie, dass der neue Test fehlschlägt.
  3. Implementieren Sie den Code.
  4. Führen Sie alle Ihre Tests aus und beobachten Sie, ob der neue Test erfolgreich ist.
  5. Refactor den Code.

Abbildung 1 zeigt diesen TDD-Lebenszyklus.

Steven Haines

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.jupiterGruppe 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, @Afterund @AfterClassAnmerkungen wurden ersetzt durch @BeforeEach, @BeforeAll, @AfterEach, und @AfterAll, respectively.
  • Die @IgnoreAnnotation von JUnit 4 wurde durch die @DisabledAnnotation ersetzt.
  • Die @CategoryAnmerkung wurde durch die @TagAnmerkung 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 MathToolsKlasse, 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 MathToolsKlasse 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 testConvertToDecimalInvalidDenominatorMethode die MathTools::convertToDecimalMethode innerhalb eines assertThrowsAufrufs aus. Das erste Argument ist die erwartete Art der auszulösenden Ausnahme. Das zweite Argument ist eine Funktion, die diese Ausnahme auslöst. Die assertThrowsMethode 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.TestAnmerkung bezeichnet eine Testmethode. Beachten Sie, dass die @TestAnmerkung jetzt aus dem JUnit 5 Jupiter API-Paket anstelle des JUnit 4- org.junitPakets stammt. Die testConvertToDecimalSuccessMethode führt die MathTools::convertToDecimalMethode 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.AssertionsKlasse bietet eine Reihe von staticMethoden zum Vergleichen der tatsächlichen und erwarteten Ergebnisse. Die AssertionsKlasse 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.
  • assertLinesMatchvergleicht zwei Listen von Strings.
  • 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 im testConvertToDecimalInvalidDenominatorobigen 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 floatund doubleWerte 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 assertMethoden 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 @DisplayNameIhren 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-pluginin die Maven- pom.xmlDatei aufnehmen und eine neue Abhängigkeit hinzufügen. Listing 3 zeigt die pom.xmlDatei 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.jupiterGruppe und wir müssen das junit-jupiterArtefakt 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-pluginBuild-Plug-In hinzufügen , um die Tests auszuführen.

Stellen Sie schließlich sicher, dass Sie das maven-compiler-pluginin 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] ------------------------------------------------------------------------