Test it!

JUnit 5 and beyond: Das gibt es Neues beim Testing-Framework für Java

Marc Philipp

© Shutterstock / rawf8

Im September 2017 wurde die Version 5.0 von JUnit nach fast zweijähriger Entwicklung endlich fertiggestellt. Das JUnit-Team hat die Arbeit danach jedoch keineswegs eingestellt, sondern neben einigen Bugfix-Releases mittlerweile drei neue Featurereleases veröffentlicht.

Nach einer kurzen Einführung in das neue Programmiermodell für Tests konzentrieren wir uns in diesem Artikel auf die Neuerungen in den Featurereleases (zum Beispiel Kotlin-Support, Tag Expressions und parallele Testausführung) und lernen, dass JUnit 5 viel mehr ist, als ein Testing-Framework für Java.

Das neue Programmiermodell

Das mit JUnit 5.0 vorgestellte neue Programmiermodell zum Schreiben von Tests und Erweiterungen trägt den Namen JUnit Jupiter API. Da das JUnit-Team bezweckt, dieses API über mehrere Major-Versionen hinweg weiterzuentwickeln, enthält der Name bewusst keine Versionsnummer. Eine kleine Referenz zur Zahl Fünf gibt es dennoch, denn Jupiter ist, von der Sonne aus gezählt, der fünfte Planet in unserem Sonnensystem.

Am besten lässt sich das neue Programmiermodell anhand eines Beispiels erklären. Dazu betrachten wir Unit -Tests für eine Calculator-Klasse, die einen einfachen Taschenrechner repräsentiert (Listing 1). Eine Calculator-Instanz hat jederzeit einen aktuellen Wert, der durch Anwendung einer Rechenoperation, d. h. den Aufruf einer Methode, modifiziert wird. Alle Codebeispiele in diesem Artikel sind auf GitHub verfügbar.

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

class CalculatorTests {

  private Calculator calculator;

  @BeforeEach
  void createCalculator() {
    calculator = new Calculator();
  }

  @Test
  @DisplayName("1 + 1 = 2")
  @Tag("addition")
  void onePlusOneIsTwo() {
    long newValue = calculator.set(1).add(1).longValue();

    assertEquals(2, newValue);
  }

  @Test
  @DisplayName("(2 * 3) / 4 = 6/4 = 3/2 = 1.5")
  @Tag("multiplication")
  @Tag("division")
  void divideResultOfMultiplication() {
    BigDecimal newValue = calculator.set(2).multiply(3).divide(4).get();

    assertEquals(new BigDecimal("1.5"), newValue);
  }

  @Test
  @Tag("input-validation")
  void cannotSetValueToNull() {
    IllegalArgumentException exception
      = assertThrows(IllegalArgumentException.class, () -> calculator.set(null));

    assertEquals("cannot set value to null", exception.getMessage());
  }

}

Auf den ersten Blick sieht die Testklasse sehr ähnlich aus wie eine, die auf JUnit 4 basiert. So werden Testmethoden weiterhin mit @Test annotiert. Auf den zweiten Blick fällt allerdings auf, dass die Annotationen nun aus dem org.junit.jupiter.api-Package und nicht aus org.junit importiert werden. Des Weiteren müssen Testklassen und -methoden in Jupiter nicht mehr public sein, die Standardsichtbarkeit genügt.

Wie man anhand der createCalculator()-Methode sehen kann, gibt es weiterhin eigene Annotationen für Methoden der Testklasse, die am Lebenszyklus eines Tests teilnehmen möchten. Allerdings haben sie leicht andere Namen als in JUnit 4 (zum Beispiel @BeforeEach statt @Before).

Wie die onePlusOneIsTwo()-Methode zeigt, ist man beim Benennen der Tests nun nicht mehr auf die Syntax von Methodennamen in Java beschränkt, sondern kann mit Hilfe der @DisplayName-Annotation beliebige Zeichenketten verwenden. Dennoch kann man in IDEs direkt von dem Eintrag im Testbaum (Abb. 1) zur Methode navigieren.

Abb. 1: Ausführung von „CalculatorTests“ in IntelliJ IDEA

Abb. 1: Ausführung von „CalculatorTests“ in IntelliJ IDEA

Das Jupiter API bietet mit der Assertions-Klasse eine Basismenge an Assertions an, die so manchem (wie etwa assertEquals()) ebenfalls von JUnit 4 bekannt vorkommen dürfte. Wie man in der cannotSetValueToNull()-Methode sieht, gibt es darüber hinaus aber auch neue Assertions. Mit assertThrows() lässt sich zusichern, dass ein bestimmter Codeabschnitt (hier calculator.set(null)) eine gewisse Art von Exception wirft (hier IllegalArgumentException). Der Rückgabewert ist die tatsächlich aufgetretene Exception und kann verwendet werden, um weitere Assertions an Eigenschaften (hier die erwartete Message) zu formulieren. Das JUnit-Team empfiehlt Entwicklern, denen die in der Assertions-Klasse enthaltenen Methoden nicht ausreichen, ausdrücklich, Bibliotheken wie AssertJ oder Hamcrest zu verwenden.

Anstelle von Categories bietet Jupiter @Tags, die nun ebenfalls auf Strings basieren. Wie wir später sehen werden, sind Tags nützlich zur Filterung der auszuführenden Tests.

W-JAX 2019 Java-Dossier für Software-Architekten

Kostenlos: 30+ Seiten Java-Wissen von Experten

Sie finden Artikel zu EnterpriseTales, Microservices, Req4Arcs, Java Core und Angular-Abenteuer von Experten wie Uwe Friedrichsen (codecentric AG), Arne Limburg (Open Knowledge), Manfred Steyer (SOFTWAREarchitekt.at) und vielen weiteren.

API Summit 2019
Thilo Frotscher

API-Design – Tipps und Tricks aus der Praxis

mit Thilo Frotscher (Freiberufler)

Golo Roden

Skalierbare Web-APIs mit Node.js entwickeln

mit Golo Roden (the native web)

Parametrisierte Tests

Neben klassischen Testmethoden, die üblicherweise das Resultat einer beispielhaften Eingabe gegen eine erwartete Ausgabe prüfen, bietet das JUnit Jupiter API weitere Möglichkeiten, Tests zu formulieren. Diese sind insbesondere dann hilfreich, wenn man denselben Code mit vielen verschiedenen Eingaben testen möchte. Eine solche Variante stellen parametrisierte Tests dar.

Um einen parametrisierten Test zu schreiben, annotiert man die Testmethode mit @ParameterizedTest anstelle von @Test, wie etwa an der sqrt()-Methode im Beispiel (Listing 2). Zusätzlich muss man mindestens eine Source-Annotation deklarieren, die angibt, woher die Werte für die Parameter gelesen werden sollen. Im Beispiel verwenden wir @CsvSource und definieren die Parameter als kommagetrennte Liste. JUnit konvertiert die Strings automatisch in die deklarierten Parametertypen. Die Konvertierung lässt sich über eine zusätzliche @ConvertWith-Annotation anpassen. Details dazu findet man im JUnit 5 User Guide.

@ParameterizedTest(name = "sqrt({0}) = {1}")
@CsvSource({
  "1, 1.0000000000000000",
  "2, 1.4142135623730951",
  "3, 1.7320508075688772",
  "4, 2.0000000000000000"
})
@Tag("sqrt")
void sqrt(long input, double expectedResult) {
  double actualResult = calculator.set(input).sqrt().doubleValue();

  assertEquals(expectedResult, actualResult, 1e-16);
}

Die Testmethode wird einmal pro Parameterliste ausgeführt. Jede Ausführung weist den gleichen Lebenszyklus wie eine normale Testmethode auf, die mit @Test annotiert ist. So werden etwa @BeforeEach/@ AfterEach-Methoden vor bzw. nach jeder Ausführung aufgerufen.

Ein Vorteil von @CsvSource ist direkte Sichtbarkeit der Testdaten an der Testmethode. Allerdings kann die Definition der Daten unübersichtlich werden, je mehr Zeilen und Spalten man definiert. Manchmal möchte man außerdem dieselben Daten als Eingabeparameter für mehrere Tests verwenden. Ersetzt man die @CsvSource-Annotation durch @CsvFileSource(resources = „/sqrt.csv“) und fügt sqrt.csv (Listing 3) zu src/test/resources hinzu, lässt sich genau das erreichen.

1, 1.0000000000000000
2, 1.4142135623730951
3, 1.7320508075688772
4, 2.0000000000000000

Neben @CsvSource und @CsvFileSource gibt es noch viele weitere Möglichkeiten, die Quelle der Testdaten anzugeben. Am einfachsten ist mit Sicherheit die @ValueSource-Annotation, die es erlaubt, ein eindimensionales Array von primitiven Typen, Strings oder Class-Literalen zu verwenden. Des Weiteren unterstützt @EnumSource die Verwendung von Enum-Konstanten und @MethodSource die Definition der Testdaten in einer statischen Methode der Testklasse oder – zwecks Wiederverwendung in verschiedenen Testklassen – einer beliebigen anderen Klasse. Darüber hinaus kann man eigene ArgumentsProvider implementieren und per @ArgumentsSource oder über eine eigene Annotation verwenden.

Dynamische Tests

Testklassen und -methoden sind insofern statisch, als die darin enthaltenen Methoden bekannt und unveränderlich sind, sobald die Klasse kompiliert wurde. Im Gegensatz dazu bieten dynamische Tests die Möglichkeit, Testfälle zur Laufzeit programmatisch zu erzeugen.

Wie die powersOfTwo()-Methode (Listing 4) beispielhaft zeigt, annotiert man dazu eine Methode einer Testklasse mit @TestFactory anstelle von @Test und definiert den Rückgabewert als Stream<DynamicTest> anstelle von void (siehe JUnit User Guide für weitere unterstützte Rückgabetypen). Bei der Implementierung der Methode stehen nun alle Sprachmittel von Java zur Verfügung. Im Beispiel nehmen wir alle Zahlen von 1 bis 99 und erzeugen daraus jeweils eine Instanz von DynamicTest mittels der statisch importierten dynamicTest()-Methode. Der erste Parameter ist dabei der Anzeigename des Tests, der zweite der auszuführende Codeblock (entspricht dem Body einer regulären Testmethode). Führt man die Methode aus, wird pro DynamicTest ein Eintrag im Testbaum angezeigt (Abb. 2). So lassen sich schnell feingranulare Assertions definieren, deren Ergebnisse detailliert angezeigt werden.

@TestFactory
@Tag("multiplication")
@Tag("power")
Stream<DynamicTest> powersOfTwo() {
  return IntStream.range(1, 100)
    .mapToObj(value -> dynamicTest(MessageFormat.format("{0}^2 = {0} * {0}", value), () -> {
      var expectedValue = new Calculator(value).multiply(value).get();
      var actualValue = calculator.set(value).power(2).get();
      assertEquals(expectedValue, actualValue);
    }));
}
Abb. 2: Ausführung von „CalculatorTests.powerOfTwo()“ in IntelliJ IDEA

Abb. 2: Ausführung von „CalculatorTests.powerOfTwo()“ in IntelliJ IDEA

Repeated Tests

Zu guter Letzt bietet das neue Programmiermodell die Möglichkeit, Tests mehrfach auszuführen. Das ist etwa dann hilfreich, wenn ein Test flaky ist, d. h. manchmal erfolgreich durchläuft und andere Male, ohne Änderungen an Test oder Implementierung, fehlschlägt. Ersetzt man die @Test-Annotation durch @RepeatedTest, wird der Test entsprechend der angegebenen Anzahl wiederholt (Listing 5). So kann man schnell validieren, ob ein Test flaky ist, ohne dass die verwendete IDE-Mehrfachausführung als eigenes Feature unterstützen muss.

@RepeatedTest(10)
@Tag("power")
void flakyTest() {
  double actualResult = calculator.set(Math.random()).power(2).doubleValue();

  assertEquals(0.0, actualResult, 0.5);
}

Extensions

Automatisierte Tests sind heutzutage glücklicherweise essenzieller Bestandteil moderner Softwareentwicklung. Auch beim Schreiben von Testcode wollen wir uns nicht ständig wiederholen und wartbar soll das Resultat schlussendlich auch noch sein. Damit die Tests möglichst prägnant beschreiben, was wir eigentlich testen wollen, ist es daher eminent wichtig, wie gut ein Testing-Framework uns dabei unterstützt, häufig verwendetes Set-up/Teardown wiederzuverwenden und unnötigen Code zu vermeiden. Das JUnit-Team hat sich beim Design des Jupiter APIs sehr viele Gedanken über diese Art von Erweiterbarkeit gemacht. Während es in JUnit 4 mit @RunWith und @Rule zwei konkurrierende Erweiterungsmechanismen gibt, enthält das Jupiter API mit dem Extension API ein einziges durchgängiges Konzept.

Das Extension-Interface ist ein Marker-Interface und wird von mittlerweile elf konkreten Interfaces erweitert. Eine konkrete Extension-Implementierung kann dabei so viele dieser Interfaces implementieren wie gewünscht. Leider fehlt uns in diesem Artikel der Raum, auf jeden der verfügbaren Extension Points einzugehen. Stattdessen sehen wir uns am Beispiel der mittlerweile offiziellen Extension von Mockito, einer Bibliothek zum Erzeugen von Mock-Objekten,an, wie eine Extension registriert wird und was sie leisten kann (Listing 6). Die Registrierung der MockitoExtension erfolgt mittels der @ExtendWith-Annotation an einer Testklasse oder -methode. Da die MockitoExtension das ParameterResolver-Interface implementiert, das eine Erweiterung von Extension ist, wird sie von JUnit zum Ermitteln der Parameter der Testmethode verwendet. Mockito erzeugt Mock-Objekte für alle mit @Mock annotierten Parameter. In unserem Beispiel wird das Mock-Objekt dann im Test verwendet, um die Klasse CurrencyConverter zu testen, die einen ExchangeRateService benötigt.

@ExtendWith(MockitoExtension.class)
class CurrencyConverterTests {

  private static final Currency EUR = Currency.getInstance("EUR");
  private static final Currency USD = Currency.getInstance("USD");

  @Test
  void convertsEurToUsd(@Mock ExchangeRateService exchangeRateService) {
    var originalAmount = new MonetaryAmount("100.00", EUR);
    when(exchangeRateService.getRate("EUR", "USD")).thenReturn(1.139157);

    var currencyConverter = new CurrencyConverter(exchangeRateService);
    var convertedAmount = currencyConverter.convert(originalAmount, USD);

    assertEquals(new BigDecimal("113.92"), convertedAmount.getValue());
    assertEquals(USD, convertedAmount.getCurrency());
  }
}

Neben der deklarativen Registrierung von Extensions mittels @ExtendWith bietet JUnit außerdem noch die Möglichkeit der programmatischen Registrierung über ein mit @RegisterExtension annotiertes Feld der Testklasse. Alternativ können Extensions sogar global für alle Tests registriert werden. Details dazu findet man im JUnit User Guide.

Neben Mockito gibt es mittlerweile eine ganze Reihe an fertigen Extensions, die man zum Schreiben von Tests verwenden kann, zum Beispiel für Spring, Testcontainers, Wiremock, Kafka und viele mehr. Das JUnit-Team pflegt eine Liste von Extensions im Wiki auf GitHub.

Unterstützung für Kotlin

Die Programmiersprache Kotlin ist zurzeit in aller Munde und selbstverständlich gibt es Testframeworks, die speziell dafür geschrieben wurden. Aber auch das Jupiter API lässt sich sehr gut in Kotlin verwenden. Ein wesentlicher Vorteil besteht darin, dass sich bestehende Extensions, wie etwa für Spring oder Testcontainers, so weiterhin verwenden lassen.

Speziell für das Schreiben von Tests mit Kotlin hat das JUnit-Team in Version 5.1 begonnen, eigene Extension Functions zum Schreiben von Assertions mitzuliefern, die den Code idiomatischer aussehen lassen. Darin enthalten sind beispielsweise Varianten von assertAll() und assertThrows(), die erlauben, die erwartete Exception-Klasse als Reified Type Parameter zu übergeben bzw. Kotlin Closures direkt zu verwenden (Listing 7).

@TestInstance(Lifecycle.PER_CLASS)
class KotlinCalculatorTests {

  private lateinit var calculator: Calculator

  @BeforeAll
  fun createCalculator() {
    calculator = Calculator()
  }

  @Test
  @Tag("input-validation")
  fun `cannot set value to null`() {
    val exception = assertThrows<IllegalArgumentException> { calculator.set(null) }

    assertAll(
      { assertEquals("cannot set value to null", exception.message) },
      { assertNull(exception.cause) }
    )
  }
}

Da Kotlin nahezu beliebige Methodennamen erlaubt, kann man seinen Tests häufig besser lesbare Namen geben, ohne die @DisplayName-Annotation zu verwenden.

Des Weiteren kann man mit @TestInstance(Lifecycle.PER_CLASS) den Standardlebenszyklus von Testklassen so verändern, dass sich alle Tests eine Testinstanz teilen. Normalerweise erzeugt JUnit für jeden Test eine neue Instanz. Das zieht nach sich, dass Methoden, die mit @BeforeAll oder @AfterAll annotiert sind, normalerweise static sein müssen. Für letzteres benötigt man in Kotlin allerdings ein Companion Object, d. h. zusätzliche Syntax, die vom eigentlichen Test ablenken würde. Im Beispiel in Listing 7 ist es eigentlich unnötig, @BeforeAll zu verwenden, da der Aufwand zum Erzeugen einer Calculator-Klasse vernachlässigbar ist.

Parallele Testausführung

Eins der spannendsten neuesten Features ist mit Sicherheit die in Version 5.3 eingeführte Möglichkeit zur parallelen Testausführung. Standardmäßig werden Tests nach wie vor sequenziell ausgeführt. Mit Hilfe des junit.jupiter.execution.parallel.enabled-Configuration-Parameters, den man zum Beispiel als System Property angeben kann, lässt sich das Feature aktivieren. So geschehen führt die Jupiter Engine alle Tests in gegebenenfalls nebenläufigen Threads aus. Die Anzahl der Threads lässt sich ebenfalls über Configuration-Parameter einstellen (siehe JUnit User Guide). Ohne explizite Konfiguration verwendet die Jupiter Engine die Anzahl der Prozessorkerne als Richtwert.

Nun wäre es sicherlich unrealistisch, davon auszugehen, alle Testklassen seien robust gegenüber paralleler Ausführung. Thread Safety und der Zustand externer Ressourcen stehen dem oft im Weg. Glücklicherweise können wir mit Hilfe des Jupiter APIs beeinflussen, welche Tests nebenläufig ausgeführt werden. Die Annotation @Execution(SAME_THREAD) stellt die Ausführung einer Testklasse oder -methode auf den gleichen Thread um, der für das umschließende Element, also etwa die Testklasse, verwendet wird. Des Weiteren erlaubt die @ResourceLock-Annotation, deklarativ anzugeben, welche Ressourcen –beispielsweise eine Datei, Datenbank oder global gehaltener Zustand – ein Test lesend bzw. schreibend verwendet. Die Jupiter Engine stellt sicher, dass kein Test, der eine Ressource benötigt, gleichzeitig ausgeführt wird mit einem anderen, der dieselbe Ressource gerade potenziell verändert.

Das Beispiel in Listing 8 veranschaulicht, wie @ResourceLock funktioniert. Bei der geteilten Ressource handelt es sich hier um die System Properties von Java. Die erste Testmethode greift lediglich lesend darauf zu, kann also parallel mit anderen Tests ausgeführt werden, die ebenso nur lesenden Zugriff benötigen. Die anderen beiden Testmethoden greifen schreibend und lesend auf die System Properties zu und verwenden daher den READ_WRITE-Modus. Ohne @ResourceLock-Deklaration würden alle drei Tests nebenläufig ausgeführt werden und – aufgrund der Race Condition beim Zugriff auf die my.prop-Property – gelegentlich fehlschlagen.

@Execution(CONCURRENT)
class SharedResourcesDemo {
  @Test
  @ResourceLock(value = "system.properties", mode = READ)
  void customPropertyIsNotSetByDefault() {
    assertNull(System.getProperty("my.prop"));
  }

  @Test
  @ResourceLock(value = "system.properties", mode = READ_WRITE)
  void canSetCustomPropertyToFoo() {
    System.setProperty("my.prop", "foo");
    assertEquals("foo", System.getProperty("my.prop"));
  }

  @Test
  @ResourceLock(value = "system.properties", mode = READ_WRITE)
  void canSetCustomPropertyToBar() {
    System.setProperty("my.prop", "bar");
    assertEquals("bar", System.getProperty("my.prop"));
  }
}

JUnit als Plattform

JUnit ist das meistgenutzte Testing-Framework für Java und eine der weitverbreitetsten Open-Source-Bibliotheken für Java überhaupt. Es wird dabei nicht nur von Entwicklern verwendet, die Tests schreiben. Jede IDE und jedes Build-Tool im Java-Ökosystem unterstützt das Ausführen von JUnit-Tests. Dafür haben sie bisher nicht nur das öffentliche API verwendet, sondern teilweise auch auf interne APIs oder Reflection zurückgegriffen, um auf Daten zuzugreifen, die anders nicht zugänglich waren. Des Weiteren hängen andere Testing-Frameworks wie etwa Spock von JUnit ab, da sie auf dessen Integration in Build-Tools und IDEs aufbauen.

Die Belange von Testautoren, IDEs/Build-Tools sowie anderen Testing-Frameworks sind dabei nur bedingt kompatibel. Während erstere sich Flexibilität und neue Features wünschen, brauchen die anderen Stabilität, weil sie viele verschiedene Versionen unterstützen wollen. Daher hat sich das JUnit-Team beim Entwurf von JUnit 5 entschlossen, die verschiedenen Belange durch klar separierte APIs und Komponenten voneinander zu trennen. Für Testautoren gibt es das Jupiter API, für IDEs und Build-Tools stellt die neue JUnit Platform das Launcher API und für Testing-Frameworks das Test Engine SPI zur Verfügung.

JUnit 5 selbst enthält zwei Test-Engine-Implementierungen: Die Jupiter-Engine sowie die Vintage Engine. Letztere dient dazu, mit JUnit 3 oder 4 geschriebene Tests auf der neuen Platform auszuführen. So kann man sowohl neue Jupiter-Tests als auch alte Tests in einem Projekt gemeinsam ausführen. Auch eine schrittweise Migration ist so möglich. Darüber hinaus steht die neue Plattform anderen Testing-Frameworks zur Verfügung (Abb. 3). Wie im Wiki auf GitHub zu sehen ist, gibt es bereits einige solcher Test-Engine-Implementierungen. Im JUnit 5 Samples Repository findet man ein Beispiel für Ausführung verschiedener Test-Engines in einem Build.

Abb. 3: Die Architektur von JUnit 5

Die neue Platform enthielt anfangs auch Plug-ins für Gradle und Maven. Mittlerweile bieten die meisten Build-Tools aber native Unterstützung für JUnit 5: Gradle seit Version 4.6, Maven Sure re seit 2.22.0 und Ant seit 1.10.3. Über den in der Platform enthaltenen ConsoleLauncher lassen sich andere Tools problemlos anbinden.

Auch der IDE-Support für JUnit 5 ist mittlerweile sehr gut. IntelliJ IDEA unterstützt die Ausführung von Tests auf der JUnit Platform bereits seit Version 2016.2, Eclipse seit 4.7.1a, der Java Test Runner von Visual Studio Code sei 0.4.0. Wer Netbeans verwendet, muss noch auf Version 10.0 warten oder in der Zwischenzeit @RunWith(JUnitPlatform) verwenden, um Jupiter-Tests mit JUnit 4 auszuführen.

Filtern mit Tags

Ein Platform-Feature, das somit allen Test-Engines offensteht, ist das Filtern der auszuführenden Tests über Tag-Expressions. Eine Tag-Expression ist ein Boolescher Ausdruck, der die Operatoren !, & und | sowie Klammern enthalten darf. Im einfachsten Fall ist eine Tag-Expression einfach nur der Name eines Tags. Einen solchen Ausdruck kann man verwenden, um gewisse Tests zu akzeptieren oder sie herauszufiltern.

Im Beispiel aus Listing 1 können wir mit der Tag-Expression addition den Test onePlusOneIsTwo() miteinbeziehen bzw. ausschließen. Die Tag-Expression addition & multiplication selektiert alle Tests, die sowohl mit addition als auch mit multiplication gekennzeichnet sind. In unserem Beispiel ist das lediglich die divideResultOfMultiplication()-Testmethode. Das funktioniert sowohl in IntelliJ IDEA (Abb. 4) und Eclipse als auch in Build-Tools wie Maven und Gradle. Wie Listing 9 demonstriert, kann man damit etwa in Gradle eigene Tasks definieren, die nur eine bestimmte Art von Tests ausführen, zum Beispiel Integrationstests.

tasks.register("integrationTest") {
  useJUnitPlatform {
    includeTags("integration")
  }
}
Abb. 4: Tag-Expressions in IntelliJ IDEA

Abb. 4: Tag-Expressions in IntelliJ IDEA

Fazit

An JUnit 5 wurde vor der Veröffentlichung von Version 5.0 fast zwei Jahre entwickelt. Der aktuelle Stand erfüllt die meisten Anforderungen, die wir heutzutage an ein modernes Testing-Framework haben. Die neue JUnit Platform bietet sehr gute Integration in IDEs und Build-Tools und erlaubt uns gleichzeitig sowohl JUnit 3/4, Jupiter als auch andere Testing-Frameworks zu verwenden. Zum Einstieg gibt es neben dem User Guide eine ganze Reihe von Beispielprojekten für alle gängigen Build-Tools.

Das JUnit-Team arbeitet kontinuierlich daran, weitere Verbesserungen und neue Features zu veröffentlichen. So wird etwa JUnit 5.4 die Beeinflussung der Ausführungsreihenfolge von Tests ermöglichen, zum Beispiel um diese zu randomisieren. Ein neues Reporting-Format, das die neuen Platform-Features besser unterstützt, deklarative Testsuiten, parametrisierte Testklassen und Szenariotests stehen außerdem auf der Agenda. Als Teil des JUnit-Teams kann ich versichern, dass wir uns stets über Feedback, Anregungen und sogar Bug Reports auf GitHub freuen. Happy Testing!

Geschrieben von
Marc Philipp
Marc Philipp
Marc Philipp hat mehr als zehn Jahre Erfahrung in der Entwicklung von Software für Unternehmen und Endkunden sowie als Trainer und Coach für andere Entwickler. In seiner Arbeit bei Gradle widmet er sich der Verbesserung des Entwicklungsalltags von Millionen von Entwicklerinnen und Entwicklern und kann seiner Leidenschaft für Open-Source-Software nachgehen. Er ist seit langem aktiver Committer und Maintainer von JUnit. Außerdem war er Mitinitiator der Crowdfunding-Kampagne JUnit Lambda, die der Anfang von dem war, was nun JUnit 5 ist.
Kommentare

Hinterlasse einen Kommentar

1 Kommentar auf "JUnit 5 and beyond: Das gibt es Neues beim Testing-Framework für Java"

avatar
4000
  Subscribe  
Benachrichtige mich zu:
molocko
Gast

Danke an den Autor für diese sehr ausführliche Darstellung der neuen Funktionen. Hat auch mir als Laie verständlich erklärt, was neu oder anders im Vergleich zur Vorgängerversion von jUnit ist.