Headerbild für JUnit 5 – Parametrisierte Tests

JUnit 5 – Software-Qualität jetzt noch besser sichern

Teil 2: JUnit 5 – Parametrisierte Tests

Von: Sven Ruppert

Die fortschreitende Entwicklung von Softwarearchitekturen und die zunehmende Komplexität von Anwendungen erfordern eine konsistente und nachhaltige Qualitätssicherung. In diesem Zusammenhang nimmt das automatisierte Testen eine essenzielle Rolle ein. Insbesondere in Java-Ökosystemen hat sich das Test-Framework JUnit über viele Jahre hinweg als De-Facto-Standard etabliert. Die fünfte Generation von JUnit, bekannt als JUnit 5, bringt wesentliche Neuerungen und Flexibilisierungen mit sich, insbesondere im Bereich der parametrierten Tests. Diese ermöglichen es, Testfälle mit verschiedenen Eingabewerten zu betreiben, wodurch Redundanzen reduziert und die Testabdeckung erhöht werden kann.

Ein grundlegendes Konzept von JUnit 5 ist die Modularisierung, die durch die Trennung in JUnit Platform, JUnit Jupiter und JUnit Vintage erreicht wird. JUnit Jupiter stellt hierbei die neue Programmierschnittstelle zur Verfügung, während JUnit Vintage zur Unterstützung älterer JUnit-Versionen dient. Die Plattform selbst fungiert als Laufzeitumgebung für unterschiedliche Test Engines. Innerhalb von JUnit Jupiter wurden zahlreiche neue Features eingeführt, darunter ein modernes Extension Model, die Möglichkeit, dynamische Tests zu generieren sowie ein überarbeitetes Modell für parametrisierte Tests. Letztere sind insbesondere für Testszenarien von Interesse, bei denen eine Methode mit verschiedenen Eingabewerten getestet werden soll, ohne dass für jede Kombination ein eigener Testfall geschrieben werden muss.

Die Einführung von @ParameterizedTest in JUnit 5 stellt eine erhebliche Verbesserung gegenüber den in früheren JUnit-Versionen verwendeten Mechanismen dar. Während in JUnit 4 parametrisierte Tests über die Parameterized-Klasse realisiert wurden, die einen eigenen Test-Runner erforderte und eine umständliche Implementierung bedingte, bietet JUnit 5 eine elegantere und flexiblere Lösung. Statt einer separaten Klasse können Testmethoden direkt mit der Annotation @ParameterizedTest versehen werden, wobei die Werte über sogenannte Argument Provider injiziert werden. Hierbei gibt es verschiedene Mechanismen zur Bereitstellung der Testdaten, darunter @ValueSource@CsvSource@CsvFileSource@EnumSource und @MethodSource.

Eine besonders häufig genutzte Möglichkeit zur Bereitstellung von Testdaten ist @CsvSource, da es eine kompakte und leicht verständliche Methode darstellt, mehrere Werte in tabellarischer Form zu übergeben. Diese Annotation erlaubt die Angabe von Testdaten in Form von kommagetrennten Werten, die dann als Argumente in den Test eingespeist werden. Dies ist insbesondere dann von Vorteil, wenn mehrere verschiedene Eingabewerte mit den jeweils erwarteten Ergebnissen überprüft werden sollen, beispielsweise in arithmetischen Berechnungen oder bei der Validierung von Zeichenketten.

Um die Funktionsweise von @CsvSource zu verdeutlichen, betrachten wir das Beispiel einer einfachen mathematischen Berechnung. Gegeben sei eine Methode zur Addition zweier Ganzzahlen, die mit verschiedenen Wertepaaren getestet werden soll. Anstatt für jede Kombination eine eigene Testmethode zu schreiben, kann der folgende parametrisierte Test genutzt werden:

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

import static org.junit.jupiter.api.Assertions.assertEquals;

class CalculatorTest {

@ParameterizedTest
@CsvSource({
    "1, 2, 3",
    "3, 7, 10",
    "-2, 5, 3",
    "0, 0, 0",
    "100, 200, 300"
})
void testAddition(int a, int b, int expectedSum) {
    Calculator calculator = new Calculator();
    assertEquals(expectedSum, calculator.add(a, b));
}

}

class Calculator {
 int add(int x, int y) {
 return x + y;
 }
}

Dieser Test ruft die Methode add() der Klasse Calculator mit den angegebenen Werten aus @CsvSource auf und überprüft, ob das berechnete Ergebnis mit dem erwarteten Wert übereinstimmt. Jede Zeile innerhalb der @CsvSource-Annotation entspricht dabei einem separaten Testlauf. Die Werte innerhalb der Annotation werden als String-Werte interpretiert, weshalb JUnit 5 eine automatische Konvertierung in die entsprechenden Datentypen durchführt. In diesem Fall werden die Werte als Ganzzahlen (int) verarbeitet, was durch die Methodensignatur des Testfalls spezifiziert wird.

Die Vorteile parametrisierter Tests mit @CsvSource liegen auf der Hand: Zum einen wird die Redundanz von Testcode reduziert, da sich mehrere Eingabewerte innerhalb einer einzigen Testmethode definieren lassen. Dies verbessert die Wartbarkeit und Lesbarkeit der Tests erheblich, insbesondere wenn eine hohe Anzahl an Testfällen notwendig ist. Zum anderen ermöglicht es eine systematische Variation von Eingabewerten, wodurch auch Randfälle leichter getestet werden können. Gerade im Bereich der mathematischen Berechnungen, String-Manipulationen oder Validierungslogiken sind solche Tests unverzichtbar, um die Robustheit eines Programms sicherzustellen.

Ein weiterer Vorteil ergibt sich aus der Möglichkeit, @CsvSource mit unterschiedlichen Datentypen zu verwenden. JUnit 5 unterstützt eine Vielzahl von Typkonvertierungen, sodass nicht nur numerische Werte, sondern auch Zeichenketten, Booleans oder sogar benutzerdefinierte Objekte genutzt werden können. Falls eine automatische Konvertierung nicht möglich ist, kann durch eigene Implementierungen von ArgumentConverter eine spezifische Transformation der Werte vorgenommen werden.

Neben @CsvSource existieren weitere Mechanismen zur Bereitstellung von Testdaten, die je nach Anwendungskontext sinnvoll sein können. @CsvFileSource ermöglicht beispielsweise das Laden von Testdaten aus einer externen CSV-Datei, wodurch sich größere Datenmengen effizient verwalten lassen. @MethodSource erlaubt es hingegen, Testwerte über eine separate Methode zu definieren, was insbesondere dann nützlich ist, wenn komplexere Datenstrukturen oder Objekte als Eingaben benötigt werden.

Zusammenfassend lässt sich festhalten, dass parametrisierte Tests mit JUnit 5 eine wesentliche Verbesserung gegenüber früheren Versionen darstellen. Sie erlauben eine prägnante und wartungsfreundliche Definition von Testfällen und tragen damit entscheidend zur Qualitätssicherung von Software bei. Insbesondere die Nutzung von @CsvSource erweist sich als äußerst praktikabel, um einfache Wertkombinationen effizient zu testen. In der Praxis empfiehlt es sich, parametrisierte Tests gezielt einzusetzen, um eine möglichst hohe Testabdeckung mit minimalem Aufwand zu erreichen. Letztlich ist es gerade in sicherheitskritischen oder geschäftsrelevanten Anwendungen unabdingbar, die Korrektheit und Verlässlichkeit von Software durch fundierte Tests sicherzustellen, wobei JUnit 5 eine moderne und leistungsfähige Grundlage bietet.

Happy Coding!

Über den Autor: Sven Ruppert

Sven programmiert seit 1996 Java in Industrieprojekten, seit über 15 Jahren weltweit Java in Branchen wie Automobil, Raumfahrt, Versicherungen, Banken, der UNO und der Weltbank. Seit zehn Jahren ist er als Sprecher auf Konferenzen und Community-Events in Ländern von Amerika bis Neuseeland. Er hat als Developer Advocate für JFrog und Vaadin gearbeitet und schreibt regelmäßig Artikel für IT-Magazine und Technologieportale.


Diese Beiträge könnten dich auch interessieren: