W3docs

Java JUnit Parametrisierte Tests

Führe denselben JUnit-Test mit verschiedenen Eingaben via @ParameterizedTest und Wertquellen aus.

Ein parametrisierter Test führt dieselbe Testmethode mehrmals aus – einmal für jeden Satz von Eingaben, den du ihm übergibst. Anstatt testReverseAbc, testReverseEmpty und testReverseSingle zu kopieren, schreibst du die Logik einmal und gibst eine Datenquelle an – eine Liste von Eingaben und erwarteten Ergebnissen. JUnit 5 (die Jupiter-Engine) macht dies mit @ParameterizedTest und einer Familie von Quell-Annotationen erstklassig. Der Vorteil: weniger Zeilen, dichtere Abdeckung und jede Eingabe wird als eigenes Bestanden/Nicht-bestanden-Ergebnis angezeigt.

Dieses Kapitel setzt voraus, dass du bereits weißt, wie ein einfacher Test geschrieben und geprüft wird; falls nicht, starte mit der JUnit-Einführung und den JUnit-Assertions. Es behandelt, wann ein parametrisierter Test sinnvoll ist, wie man eine Argumentquelle auswählt (@ValueSource, @CsvSource, @MethodSource und andere) und den häufigsten Fehler – ein falscher erwarteter Wert statt eines Code-Fehlers.

Von wiederholten Tests zu einem parametrisierten Test

Eine einfache @Test-Methode testet genau ein Szenario. Wenn du dasselbe Verhalten über eine Tabelle von Eingaben prüfen möchtest, wiederholt der naive Ansatz die Methode:

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTrue;

class PrimesTest {
  @Test void two_isPrime()   { assertTrue(Primes.isPrime(2)); }
  @Test void seven_isPrime() { assertTrue(Primes.isPrime(7)); }
  @Test void thirteen_isPrime() { assertTrue(Primes.isPrime(13)); }
}

Die parametrisierte Version fasst alle drei in eine Methode zusammen. Du annotierst mit @ParameterizedTest (nicht @Test) und hängst eine Quelle an, die das Argument für jeden Durchlauf liefert:

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.assertTrue;

class PrimesTest {
  @ParameterizedTest
  @ValueSource(ints = {2, 7, 13})
  void isPrime(int candidate) {
    assertTrue(Primes.isPrime(candidate));
  }
}

JUnit ruft isPrime dreimal auf – candidate=2, dann 7, dann 13 – und meldet drei Ergebnisse. Ein fehlschlagender Wert verdeckt die anderen nicht.

Eine Argumentquelle auswählen

Die Annotation @ParameterizedTest ist allein nutzlos; sie benötigt eine Quelle, die die Argumente liefert. JUnit Jupiter stellt mehrere bereit, jede für eine andere Datenform geeignet:

QuelleLiefertAm besten für
@ValueSourceEinen einzelnen Literalwert pro Durchlauf (ints, strings, doubles, …)Einargument-Tests
@CsvSourceEine Zeile kommagetrennte Werte pro DurchlaufWenige Inline-Zeilen mit mehreren Spalten
@CsvFileSourceZeilen aus einer .csv-Datei im KlassenpfadGroße oder extern gepflegte Tabellen
@MethodSourceWas auch immer eine Factory-Methode als Stream/Collection zurückgibtKomplexe Objekte, berechnete Fälle
@EnumSourceDie Konstanten eines EnumsVollständige Abdeckung eines Enums
@NullSource / @EmptySourcenull- und Leer-WerteGrenzfall-Abdeckung für Strings/Collections

Als Faustregel gilt: @ValueSource für eine einfache Eingabe, @CsvSource für eine kleine mehrspaltige Tabelle und @MethodSource, sobald die Daten nicht mehr in Annotationsliterale passen.

Mehrere Spalten mit @CsvSource

Wenn jeder Fall eine Eingabe und eine erwartete Ausgabe hat, bietet @CsvSource eine kleine Inline-Tabelle. Jeder String ist eine Zeile; Kommas teilen ihn in geordnete Methodenparameter auf:

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.assertEquals;

class StringsTest {
  @ParameterizedTest
  @CsvSource({
      "abc,     cba",
      "racecar, racecar",
      "'',      ''"          // single quotes denote an empty string
  })
  void reverse(String input, String expected) {
    assertEquals(expected, Strings.reverse(input));
  }
}

JUnit konvertiert jedes kommagetrennte Token in den deklarierten Parametertyp, sodass @CsvSource({"4, 16"}) in (int n, int square) landen kann. Verwende einfache Anführungszeichen, um Kommas oder leere Strings in einer Zelle einzuschließen.

Berechnete Fälle mit @MethodSource

Annotationswerte müssen Compile-Time-Konstanten sein. Sobald Argumente echte Objekte sind oder berechnet werden müssen, wechsle zu @MethodSource. Es benennt eine statische Methode, die einen Stream<Arguments> (oder eine beliebige Collection/Array) zurückgibt:

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;

class TaxTest {
  static Stream<Arguments> brackets() {
    return Stream.of(
        Arguments.of(0,      0.0),
        Arguments.of(10_000, 1_000.0),
        Arguments.of(50_000, 7_500.0)
    );
  }

  @ParameterizedTest(name = "income {0} -> tax {1}")
  @MethodSource("brackets")
  void computesTax(int income, double expectedTax) {
    assertEquals(expectedTax, Tax.of(income));
  }
}

Das optionale name-Attribut passt an, wie jede Ausführung im Testbericht erscheint, wobei {0}, {1} für die Argumente stehen – unverzichtbar, wenn eine einzelne fehlschlagende Zeile auf einen Blick identifiziert werden muss.

Ein vollständiges Beispiel: ein parametrisierter Runner ohne JUnit

Der Code-Runner hat kein JUnit im Klassenpfad, daher modelliert dieses Programm den Mechanismus, den ein parametrisierter Test verkörpert, mit einfachem JDK-Code: Eine einzelne Prüfung wird einmal definiert und dann über eine Liste von Fällen ausgeführt – genau das, was @ParameterizedTest hinter den Annotationen tut. Ein Fall ist absichtlich falsch, damit du sehen kannst, wie einzelne Zeilen bestehen oder fehlschlagen.

java— editable, runs on the server

Was man aus dem Durchlauf mitnehmen kann:

  • Der reverse-Block gibt vier PASS-Zeilen und >> reverse: 4 passed, 0 failed aus – ein Methodenrumpf (reverse) lief gegen vier Zeilen, was widerspiegelt, wie eine einzelne @ParameterizedTest-Methode einmal pro @CsvSource-Zeile aufgerufen wird.
  • Der isPrime-Block gibt PASS für die Eingaben 2, 7, 9 und 1 aus, aber FAIL für Eingabe 4, weil isPrime(4) false zurückgibt, während die Zeile true behauptete – eine falsche Erwartung, kein Code-Fehler, was der häufigste Fehler bei parametrisierten Tests ist.
  • Dieser einzelne Fehlschlag wird in einer eigenen Zeile gemeldet und als >> isPrime: 4 passed, 1 failed gezählt; die anderen Zeilen bestehen weiterhin, was den Hauptvorteil gegenüber einer handgefertigten Schleife mit einer Assertion demonstriert – jede Eingabe ist ein unabhängiger, individuell gemeldeter Fall.
  • Der runAll-Helfer nimmt die Einheit als Function und die Fälle als List, wodurch die zu testende Logik von den Daten getrennt wird – genau die Trennung, die @ParameterizedTest zusammen mit einer Argumentquelle bietet.
  • Jede Zeile zeigt expected neben actual, sodass die Zeile 4 / expected=true / actual=false genau angibt, welcher Wert nicht übereinstimmte – den gleichen Diagnosewert liefern JUnits assertEquals-Meldung und das name = "..."-Template.

Wann ein parametrisierter Test sinnvoll ist

Greife zu @ParameterizedTest, wenn ein Verhalten über eine Tabelle von Eingaben gelten soll – Grenzwerte, Äquivalenzklassen oder eine Regressionsliste von Eingaben, die einmal fehlgeschlagen sind. Verwende weiterhin einen einfachen @Test, wenn ein Szenario ein einzigartiges Setup oder eigene Assertions benötigt; unzusammenhängende Fälle in eine parametrisierte Methode zu quetschen, macht den Bericht nur schwerer lesbar. Für gemeinsames Setup bei beiden Stilen, siehe das Kapitel zum Test-Lifecycle, und für das vollständige Assertion-Vokabular, das innerhalb jedes Durchlaufs verwendet wird, das Kapitel zu Assertions.

Übung

Übung
In JUnit 5 soll dein Test einmal für jede Zeile einer Tabelle ausgeführt werden, wobei jede Zeile einen Eingabe-String und den erwarteten umgekehrten String enthält. Welche ist die geeignetste einzelne Annotation, um diese Daten inline bereitzustellen?
In JUnit 5 soll dein Test einmal für jede Zeile einer Tabelle ausgeführt werden, wobei jede Zeile einen Eingabe-String und den erwarteten umgekehrten String enthält. Welche ist die geeignetste einzelne Annotation, um diese Daten inline bereitzustellen?
Was this page helpful?