Gegeben ist folgende Klasse
public class IsIntegerPositive implements Predicate<Integer> {
@Override
public boolean test(Integer integer) {
return integer != null && integer > 0;
}
}
Nun möchte man mehrere positive Werte testen. Da die Testmethode doch sehr überschaubar ist, würde man in der Regel zur folgenden Lösung neigen:
@Test
void testIsPositive() {
assertTrue(underTest.test(1));
assertTrue(underTest.test(100));
assertTrue(underTest.test(10_000));
}
Unabhängig von der Diskussion, ob gute Tests nur eine Assertion haben sollten, ließe sich dieser Test wunderbar parametrisiert lösen:
@ParameterizedTest
@ValueSource(ints = { 1, 100, 10_000 })
void testPositives(Integer integer) {
assertTrue(underTest.test(integer));
}
Die ParameterizedTest
-Annotation markiert die Methode als Test und ersetzt die Test
-Annotation. In @ValueSource
sind die Daten angegeben, die jeweils als Parameter an die Methode übergeben wird. Für jeden angegebenen Wert wird der Test nun ein Mal ausgeführt. IntelliJ zeigt diese auch gesondert an
Für das Testen von negativen Werten schreibt man nun noch eine entsprechende Methode mit negativen Werten als Parameter.
@ParameterizedTest
@ValueSource(ints = { -1, -100, -10_000 })
void testNegatives(Integer integer) {
assertFalse(underTest.test(integer));
}
Um abschließend noch null
und 0
zu testen können wird dem ValueSource
-Argument ints
nicht einfach null
übergeben, sondern müssen die zusätzliche Annotation NullSource
nutzen:
@ParameterizedTest
@ValueSource(ints = 0)
@NullSource
void testNullAndZero(Integer integer) {
assertFalse(underTest.test(integer));
}
Parameter als CSV
Gegeben ist folgende Klasse
public class UpperCaseString implements UnaryOperator<String> {
@Override
public String apply(String string) {
return string.toUpperCase();
}
}
Nun möchte man dem Test neben den zu testenden String auch den erwarteten Rückgabewert übergeben - sprich, man möchte der Testmethode mehrere Parameter übergeben. Da dies @ValueSource
nicht möglich ist, nutzen wir dazu das CSV-Format mit entsprechender Annotation.
@ParameterizedTest
@CsvSource({ "foo,FOO", "bAr,BAR", "f00 b4r,F00 B4R" })
void apply(String input, String expected) {
assertEquals(expected, underTest.apply(input));
}
Das Ganze kann noch etwas übersichtlicher gestaltet werden, indem man die Testdaten aus einer passenden CSV-Datei aus dem resources
-Verzeichnis laden lässt.
input,expected
foo,FOO
bAr,BAR
f00 b4r,F00 B4R
Die erste Zeile der CSV-Datei lässt sich durch das Setzten des numLinesToSkip
-Arguments ignorieren.
@ParameterizedTest
@CsvFileSource(resources = "/upper-case-string-data.csv", numLinesToSkip = 1, delimiter = )
void apply_fromFileSource(String input, String expected) {
assertEquals(expected, underTest.apply(input));
}
@ParameterizedTest
@CsvFileSource(resources = "/upper-case-string-data.csv", numLinesToSkip = 1, delimiter = )
void apply_fromFileSource(String input, String expected) {
assertEquals(expected, underTest.apply(input));
}
Methode als Parameterquelle
Gegeben ist folgende Klasse
public class ConcatStrings implements Function<List<String>, String> {
@Override
public String apply(List<String> strings) {
if (strings == null) {
return "";
}
return String.join("", strings);
}
}
Mit der MethodSource
-Annotation kann eine statische Methode als Datenquelle genutzt werden. Diese Methode muss dann einen Stream von Arguments
zurückgeben. Das bietet sich bspw. an, wenn komplexere Datentypen als Testparameter genutzt werden sollen.
private static Stream<Arguments> provideStrings() {
return Stream.of(
Arguments.of(List.of("foo", "bar"), "foobar"),
Arguments.of(List.of(), ""),
Arguments.of(null, "")
);
}
Die Source-Methode wird dann einfach der MethodSource
-Annotation als value
-Argument übergeben.
@ParameterizedTest
@MethodSource("provideStrings")
void apply(List<String> input, String expected) {
assertEquals(expected, underTest.apply(input));
}
Fazit
Mit parametrisierten Tests lassen sich Testklassen wesentlich einfacher und übersichtlicher gestalten. Kopierte Testmethoden mit angepassten Daten lassen sich somit komplett eliminieren und auch eine extrem starke Abstrahierung (sodass die Test-Suites gerne schon eine völlig eigene Businesslogik haben) kann so vermieden werden. Das hier war natürlich nur ein kleiner Einblick in das, was mit @ParameterizedTest
möglich ist. Detaillierte Informationen sind in der JUnit-Dokumentation zu finden.
Ein ausführbares Beispielprojekt lässt sich auf meinem GitHub-Account finden: github.com/pklink/article-junit5-parameteri..
(Das Artikelfoto stammt von Biblioteca de Arte / Art Library Fundação Calouste Gulbenkian)