Testdaten-Provider

Aus MimiPedia

Um einen parametrisierte Test-Methode mit Daten zu versorgen bieten sich Methoden als Testdaten-Provider an. Eine solcher Provider-Methode liefert einen Stream von Testdaten-Sätzen. Ein Testdaten-Satz besteht in der Regel auf mehreren Einzeldaten, er kann aber natürlich auch aus einem einzelnen Datum bestehen. Jeder Satz ist in ein Arguments-Objekt verpackt und besteht daher immer aus Objekten; Daten atomarer Typen werden dabei in Objekte der korrespondierenden Klassen verpackt.

Betrachten wir eine einfache Test-Methode die für die Ausführung zwei Strings benötigt. Wie jede Test-Methode die eine nichtleere Parameterliste hat, muß sie mit der Annotation @ParameterizedTest versehen sein:

@ParameterizedTest
void passwortWirdNichtAkzeptiert(String passwort, String grund) {
    assertThat(policy.validate(passwort)).as(grund).isFalse();
}

Variante 1: die statische Methode

Der einfachste Weg einen Testdaten-Provider zu definieren führt über eine statische Methode die sich in der selben Klasse befindet wie die Test-Methode:

static Stream<Arguments> pwdWirdNichtAkzeptiert() {
    return Stream.of(
        Arguments.of("Hallo", "nur Buchstaben"),
        Arguments.of("1234567", "nur Ziffern"),
        Arguments.of("!%#&/()", "nur Sonderzeichen")
    );
}

Stimmt der Name des Testdaten-Providers mit dem Namen der Test-Methode überein, kann man den Parameter in der Annotation auch weglassen. Für diese Variante gelten folgende Einschränkungen:

  • Die Methode muß in der gleichen Klasse liegen wie die Test-Methode
  • Die Methode muß als static deklariert sein
  • Die Methode darf nicht private deklariert sein

Variante 2: die qualifizierte statische Methode

Nachteil der ersten Variante ist, daß sich der Testdaten-Provider in der gleichen Klasse befinden muß wie die Test-Methode. Das gilt auch, wenn sich die Methode in einer Member Class oder einer Inner Class befindet. Damit lassen sich keine Testdaten-Provider definieren, die von mehreren geschachtelte Test-Klassen -- per Annotation @Nested -- gemeinsam genutzt werden.

Befindet sich der Testdaten-Provider in einer anderen Klasse als die Test-Methode, kann man zusätzlich zur Methode die Klasse angeben in der sich die Methode befindet. Die Package-Angabe muß voll qualifiziert sein, die Methode wird mit # getrennt angefügt:

MethodSource("org.mletkin.unittest.TestDaten#pwdWirdNichtAkzeptiert")

Wie in Variante 1 erfolgt die Angabe in Form eines Strings. Das ist fehleranfällg, weil die Klasse und die Methode erst zur Laufzeit -- also bei der Test-Ausführung -- auf Existenz geprüft werden.

Man kann das mitigieren, indem man den String in eine Konstante auslagert:

private static final String TEST_DATA = "org.mletkin.unittest.TestDaten#pwdWirdNichtAkzeptiert";
...
@MethodSource(TEST_DATA)

Das eröffnet zum einen die Möglichkeit Test-Methoden zentral bereitzustellen und zum anderen die Möglichkeit die Strings auf Gültigkeit zu überprüfen. Das geschieht dann zwar auch erst zur Laufzeit, der Fehler tritt dann aber wenigstens da auf wo er geschehen ist.

Variante 3: die Testdaten-Provider-Klasse

Eine weitere Möglichkeit den Testdaten-Provider zu implementieren ist die Definition in einer eigenen Klasse. Eine solche Klasse kann allerdings nur eine einzige Testdaten-Methode anbieten, da der Zugriff über ein Interface erfolgt:

public class UngueltigePassworte implements ArgumentsProvider {
    @Override
    public Stream<Arguments> provideArguments(ExtensionContext context) {
        return Stream.of(
            Arguments.of("Hallo", "nur Buchstaben"),
            Arguments.of("1234567", "nur Ziffern"),
            Arguments.of("!%#&/()", "nur Sonderzeichen")
        );
    }
}

Die Testdaten-Provider-Klasse muß das Interface ArgumentsProvider implementieren und dazu die oben gezeigte Methode implementierten. Sie erhält als Argument ein Kontext-Objekt das Informationen zur Umgebung der Test-Ausführung enthält. In unserem Beispiel machen wir keinen Gebrauch davon.

Die Verwendung erfolgt über die Annotation @ArgumentsSource, der jetzt die Testdaten-Provider-Klasse in Form eines Class-Objekts übergeben wird:

@ArgumentSource(UngueltigePassworte .class)

Im Gegensatz zur @MethodSource-Verwendung in den vorangegangenen Varianten wird hier schon zur Compile-Zeit sichergestellt daß die Testdaten-Provider-Klasse und die -Methode existieren.