JUnit 5

Aus MimiPedia

Das Test-Framework bietet den Rahmen in dem die Unit-Tests geschrieben werden. Es führt die Unit-Tests aus und stellt die Ergebnisse bereit, die dann von Eclipse, Jenkins oder Sonar ausgewertet und dargestellt werden.

Neben JUnit – in der damaligen Version 4 – begann sich TestNG zunächst als Alternative zu etablieren. Doch während JUnit kontinuierlich weiterentwickelt wurde und mit JUnit 5 eine bemerkenswerte Erweiterung erfuhr, scheinen sich die Aktivitäten um TestNG mehrheitlich im Sande verlaufen zu haben. Das muß nicht so bleiben, TestNG kann – wie auch Junit 4 – weiterhin als Test-Framework eingesetzt werden, aktuell wird jedoch die Nutzung von JUnit 5 empfohlen. Vor allem die Möglichkeiten, gleichartige Tests als parametrisierte Tests zusammenzufassen oder über eine Test-Factory erzeugen zu lassen kann eine Menge Tipp-Arbeit ersparen.

Diese Einführung beschreibt zunächst die grundlegenden Komponenten von Unit-Tests für tiefergehende Details sei auf die JUnit5-Dokumentation verwiesen.

Test-Aufbau

Die Test-Klasse

Die Test-Klasse wird durch rechts-Klick auf die zu testende Klasse und anschließende Auswahl von "New JUnit Test Case" erzeugt. Das funktioniert bei allen Frameworks im Prinzip genauso. Das Ergebnis ist eine ganz gewöhnliche Klasse; daß es sich um eine Test-Klasse handelt, erkennt JUnit daran, daß sie mindestens eine Test-Methode enthält. Bei Bedarf kann der Klasse per @DisplayName eine Beschreibung mitgegeben werden, die in der Auswertung anstelle des Klassennamen auftaucht.

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

@DisplayName("Tests für Datumskonverierung")
public class ConvertTest {
    // ...
}

Der Name der kann zwar völlig beliebig gewählt werden, doch gehen Maven-Plugins wie surefire in der Regel von bestimmten Namenskonventionen aus. Unit-Test-Klassen enden bei uns daher immer mit dem Suffix Test.

Die Test-Methode

Eine Test-Methode hat keine Parameter, liefert void als Ergebnis und ist mit @Test annotiert. Auch ihr kann per @DisplayName eine Beschreibung mitgegeben werden, die in der Auswertung anstelle des Methodennamen auftaucht

@Test
@DisplayName("localDate wird nach Date konvertiert")
void test4711() {
    // ...
}

Mit Ausnahme von private ist jede Sichtbarkeit erlaubt, "package visible" bedeutet dabei den wenigsten Schreibaufwand. Was die Frameworks unmittelbar von einander unterscheidet ist das Import-Statement. Für JUnit 5 muß es lauten

import org.junit.jupiter.api.Test;

Methoden ohne @Test-Annotation werden vom Test-Runner ignoriert – sie sind ganz gewöhnliche Methoden. Neben den einfachen Tests gibt es

  • parametrisierte Test, hier werden unterschiedliche Daten mit der gleichen Test-Methode ausgeführt
  • dynamische Tests, hier wird eine Factory definiert, die Test-Fälle generiert
  • wiederholte Tests, hier werden einfache Tests mehrmals ausgeführt

Eine Einführung in parametrisierte Tests gibt es hier(see page 10). Für die anderen Test-Arten sei auf die JUnit5-Doku2 verwiesen. Dynamische Tests sind aktuell noch als "experimentell" markiert.

viele, viel Tests

Clean-Code-Regeln verlangen, für jeden Test -- und Test ist hier wirklich "so klein wie möglich" zu verstehen -- eine eigene Test-Methode anzulegen, mit genau einem Test also einer Assertion. Das führt in der Realität zu viel Code und viel Wiederholung, was sich mit Clean Code auch wieder nicht verträgt. Um das zu vermeiden bietet JUnit5 sehr mächtige Parametrisierte Tests.

Vorbereitung – Nachbereitung

Ist es nötig vor bzw. nach den Tests Aktionen durchzuführen, werden dafür Methoden definiert und entsprechend annotiert. Dabei gibt es zwei Situationen die im Folgenden beschrieben werden. Alle vier Annotationen werden vererbt und können überschrieben werden. Sie können auch bei default-Methoden von Interfaces verwendet werden, was die Möglichkeit eröffnet, mehrere inhaltlich getrennte Aktionen an eine Testklasse zu heften.

Einmal für alle Tests

Sind die Aktionen nur einmal für die gesamte Test-Klasse durchzuführen verwendet man diese Annotationen:

@BeforeAll wird ausgeführt, bevor der erste Test der Klasse ausgeführt wird
@AfterAll wird ausgeführt, nachdem der letzte Test der Klasse ausgeführt wurde

Die so annotierte Methode muß statisch sein und void liefern, in der Regel haben sie auch keine Parameter.

Für jeden Tests

Sind die Aktionen für jeden einzelnen Test der durchzuführen verwendet man diese Annotationen:

@BeforeEach wird ausgeführt, bevor ein Test der Klasse ausgeführt wird
@AfterEach wird ausgeführt, nachdem ein Test der Klasse ausgeführt wurde

Die so annotierte Methode muß statisch sein und void liefern, in der Regel haben sie auch keine Parameter. Die Methoden werden nicht nur vor jeder Test-Methode ausgeführt, sondern vor jedem Tests. Das macht bei Einzel-Tests keinen Unterschied, bedeutet aber, daß die Methoden jeden Einzeltests eines parameterisierten Tests und jeden Einzeltest aus einer Test-Factory umrahmen können.

Tests überspringen

Um einen Test zu überspringen, setzt man die Annotation @Disabled vor die @Test-Annotation. Mit @Disabled kann auch die Test-Klasse annotiert werden, dann werden alle Tests der Klasse ignoriert.

Assertions

Junit5 bietet – wie Junit4 – die Klasse Assertions mit einer ganzen Reihe von Assertion-Methoden an. Die Features wurden aber nicht weiter ausgebaut. Daher werden die JUnit-Assertions hier nicht beschrieben. Stattdessen wird der Einsatz von AssertJ empfohlen und auf die entsprechende Wiki-Einführung(see page 14) verwiesen. AssertJ ist wesentlich mächtiger und die Notation ist wesentlich leichter und schneller zu lesen, was für die unerläßliche Pflege der Unit-Tests unabdingbar ist.

Migration

Vorhandene JUnit4-Tests lassen sich mit dem Legacy-Runner der JUnit5-Engine ausgeführt werden ohne daß Änderungen erforderlich sind. Das bietet die Möglichkeit, mit den alten Tests zu starten und diese dann schrittweise umzustellen. Für TestNG-Tests gilt das leider nicht. Um einen JUnit4-Test in eine JUint5-Test umzuwandeln sind jedoch einige Änderungen nötig:

  • Die Annotationen, Assertions- und Assumptions-Klasse liegen nun im package
org.junit.jupiter.api
  • die Before/After[Class]-Annotationen müssen auf die oben beschriebenen Annotationen umgestellt werden
  • JUnit4-Rules werden nicht mehr unterstützt und müssen in das neue Extension-Modell überführt werden.
  • @Ignore muß durch @Disabled ersetzt werden
  • @Category muß duch @Tag ersetzt werden

Weitere Details finden sich in der JUnit5-Doku4.