# Parametrisierte Tests mit JUnit 5

Gegeben ist folgende Klasse

```java
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:

```java
@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:

```java
@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

![Parametrisierte Tests mit JUnit 5](https://cdn.hashnode.com/res/hashnode/image/upload/v1602190919042/2CUtYRsYR.png)

Für das Testen von negativen Werten schreibt man nun noch eine entsprechende Methode mit negativen Werten als Parameter.

```java
@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:

```java
@ParameterizedTest
@ValueSource(ints = 0)
@NullSource
void testNullAndZero(Integer integer) {
    assertFalse(underTest.test(integer));
}
```

## Parameter als CSV

Gegeben ist folgende Klasse

```java
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.

```java
@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.

```csv
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.

```java
@ParameterizedTest
@CsvFileSource(resources = "/upper-case-string-data.csv", numLinesToSkip = 1, delimiter = )
void apply_fromFileSource(String input, String expected) {
    assertEquals(expected, underTest.apply(input));
}
```

```java
@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

```java
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.

```java
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.

```java
@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](https://junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests)  zu finden.

Ein ausführbares Beispielprojekt lässt sich auf meinem GitHub-Account finden: https://github.com/pklink/article-junit5-parameterized-tests

---

*(Das Artikelfoto stammt von  [Biblioteca de Arte / Art Library Fundação Calouste Gulbenkian](https://www.flickr.com/photos/biblarte/5342208423/))*
						
