Parameter in JUnit-Jupiter-Unit-Tests injizieren

1. Übersicht

Vor der Einführung von JUnit 5 müsste das JUnit-Team es mit der Core-API tun, um eine coole neue Funktion einzuführen. Mit JUnit 5 entschied das Team, es sei an der Zeit, die Erweiterung der JUnit-API außerhalb von JUnit selbst, einer Kernphilosophie von JUnit 5, mit dem Namen „https://github.com/junit-team/junit5/wiki/Core-Principles“ voranzutreiben[Erweiterungspunkte gegenüber Funktionen bevorzugen]“.

In diesem Artikel konzentrieren wir uns auf eine dieser Erweiterungspunktschnittstellen - ParameterResolver -, die Sie zum Einfügen von Parametern in Ihre Testmethoden verwenden können. Es gibt verschiedene Möglichkeiten, die JUnit-Plattform auf Ihre Erweiterung aufmerksam zu machen (ein Vorgang, der als "Registrierung" bezeichnet wird). In diesem Artikel konzentrieren wir uns auf die declarative -Registrierung (d. H. Registrierung über Quellcode).

2. ParameterResolver

Das Injizieren von Parametern in Ihre Testmethoden kann mithilfe der JUnit 4-API durchgeführt werden, war jedoch recht begrenzt. Mit JUnit 5 kann die Jupiter-API durch die Implementierung von ParameterResolver erweitert werden, um Objekte beliebigen Typs für Ihre Testmethoden bereitzustellen. Werfen wir einen Blick.

2.1. FooParameterResolver

public class FooParameterResolver implements ParameterResolver {
  @Override
  public boolean supportsParameter(ParameterContext parameterContext,
    ExtensionContext extensionContext) throws ParameterResolutionException {
      return parameterContext.getParameter().getType() == Foo.class;
  }

  @Override
  public Object resolveParameter(ParameterContext parameterContext,
    ExtensionContext extensionContext) throws ParameterResolutionException {
      return new Foo();
  }
}

Zuerst müssen wir ParameterResolver – implementieren, das zwei Methoden hat:

  • supportsParameter () - gibt "true" zurück, wenn der Typ des Parameters lautet

unterstützt (Foo in diesem Beispiel) und ** resolveParamater () - liefert ein Objekt des richtigen Typs (ein neues Objekt)

Foo-Instanz in diesem Beispiel), die dann in Ihre Testmethode eingefügt wird

2.2. FooTest

@ExtendWith(FooParameterResolver.class)
public class FooTest {
    @Test
    public void testIt(Foo fooInstance) {
       //TEST CODE GOES HERE
    }
}

Um die Erweiterung zu verwenden, müssen Sie sie dann über die Annotation @ ExtendWith (Zeile 1) deklarieren, d.

Wenn die JUnit-Plattform den Komponententest ausführt, erhält sie eine Foo -Instanz von FooParameterResolver und übergibt sie an die testIt () -Methode (Zeile 4).

Die Erweiterung hat einen Einflussbereich, der die Erweiterung aktiviert, je nachdem, wo ** sie deklariert ist

Die Erweiterung kann entweder aktiv sein bei:

  • Methodenebene, auf der es nur für diese Methode aktiv ist, oder

  • Klassenstufe, wo sie für die gesamte Testklasse aktiv ist, oder

@ Nested test class wie wir bald sehen werden

Hinweis: Sie sollten a ParameterResolver __at nicht für beide Bereiche für denselben Parametertyp deklarieren, da sich die JUnit-Plattform über diese Mehrdeutigkeit beschwert.

In diesem Artikel erfahren Sie, wie Sie zwei Erweiterungen schreiben und verwenden, um Person -Objekte einzufügen: Eine, die "gute" Daten (genannt ValidPersonParameterResolver ) und eine, die "schlechte" Daten ( InvalidPersonParameterResolver ) einfügt. Wir verwenden diese Daten, um eine Klasse namens PersonValidator zu testen, die den Status eines Person -Objekts überprüft.

3. Schreiben Sie die Erweiterungen

Nachdem wir nun verstanden haben, was eine ParameterResolver -Erweiterung ist, können wir jetzt schreiben:

  • eine, die gültige Person -Objekte zur Verfügung stellt

( ValidPersonParameterResolver ) und eine, die ungültige ** Person -Objekte enthält

( InvalidPersonParameterResolver )

3.1. ValidPersonParameterResolver

public class ValidPersonParameterResolver implements ParameterResolver {

  public static Person[]VALID__PERSONS = {
      new Person().setId(1L).setLastName("Adams").setFirstName("Jill"),
      new Person().setId(2L).setLastName("Baker").setFirstName("James"),
      new Person().setId(3L).setLastName("Carter").setFirstName("Samanta"),
      new Person().setId(4L).setLastName("Daniels").setFirstName("Joseph"),
      new Person().setId(5L).setLastName("English").setFirstName("Jane"),
      new Person().setId(6L).setLastName("Fontana").setFirstName("Enrique"),
  };

Beachten Sie das VALID PERSONS -Array von Person -Objekten. Dies ist das Repository gültiger Person -Objekte, aus denen eines ausgewählt wird, wenn die Methode resolveParameter () __ von der JUnit-Plattform aufgerufen wird.

Wenn Sie die gültigen Person-Objekte hier haben, werden zwei Dinge erreicht:

  1. Trennung der Bedenken zwischen dem Gerätetest und den Daten, die das Laufwerk steuert

es . Wiederverwenden Sie, falls andere Komponententests gültige Person -Objekte zum Fahren erfordern

Sie

@Override
public boolean supportsParameter(ParameterContext parameterContext,
  ExtensionContext extensionContext) throws ParameterResolutionException {
    boolean ret = false;
    if (parameterContext.getParameter().getType() == Person.class) {
        ret = true;
    }
    return ret;
}

Wenn der Parametertyp Person ist, teilt die Erweiterung der JUnit-Plattform mit, dass sie diesen Parametertyp unterstützt. Andernfalls gibt sie den Wert false zurück.

Warum sollte das wichtig sein? Während die Beispiele in diesem Artikel einfach sind, können Unit-Test-Klassen in einer realen Anwendung sehr umfangreich und komplex sein, wobei viele Testmethoden unterschiedliche Parametertypen verwenden. Die JUnit-Plattform muss bei der Auflösung von Parametern innerhalb des aktuellen Einflussbereichs alle registrierten __ParameterResolver __s prüfen.

@Override
public Object resolveParameter(ParameterContext parameterContext,
  ExtensionContext extensionContext) throws ParameterResolutionException {
    Object ret = null;
    if (parameterContext.getParameter().getType() == Person.class) {
        ret = VALID__PERSONS[new Random().nextInt(VALID__PERSONS.length)];
    }
    return ret;
}

Ein zufälliges Person -Objekt wird vom VALID PERSONS__-Array zurückgegeben.

Beachten Sie, wie resolveParameter () nur von der JUnit-Plattform aufgerufen wird, wenn supportsParameter () true zurückgibt.

3.2. InvalidPersonParameterResolver

public class InvalidPersonParameterResolver implements ParameterResolver {
  public static Person[]INVALID__PERSONS = {
      new Person().setId(1L).setLastName("Ad__ams").setFirstName("Jill,"),
      new Person().setId(2L).setLastName(",Baker").setFirstName(""),
      new Person().setId(3L).setLastName(null).setFirstName(null),
      new Person().setId(4L).setLastName("Daniel&").setFirstName("{Joseph}"),
      new Person().setId(5L).setLastName("").setFirstName("English, Jane"),
      new Person()/** .setId(6L).setLastName("Fontana").setFirstName("Enrique")** /,
  };

Beachten Sie das INVALID PERSONS -Array von Person -Objekten. Genau wie bei ValidPersonParameterResolver enthält diese Klasse einen Speicher für "schlechte" (d. H. Ungültige) Daten zur Verwendung durch Komponententests, um beispielsweise sicherzustellen, dass PersonValidator.ValidationExceptions__ bei ungültigen Daten ordnungsgemäß ausgelöst wird:

@Override
public Object resolveParameter(ParameterContext parameterContext,
  ExtensionContext extensionContext) throws ParameterResolutionException {
    Object ret = null;
    if (parameterContext.getParameter().getType() == Person.class) {
        ret = INVALID__PERSONS[new Random().nextInt(INVALID__PERSONS.length)];
    }
    return ret;
}

@Override
public boolean supportsParameter(ParameterContext parameterContext,
  ExtensionContext extensionContext) throws ParameterResolutionException {
    boolean ret = false;
    if (parameterContext.getParameter().getType() == Person.class) {
        ret = true;
    }
    return ret;
}

Der Rest dieser Klasse verhält sich natürlich genauso wie sein "guter" Gegenüber.

4. Erweiterungen deklarieren und verwenden

Jetzt, da wir zwei _ParameterResolver s haben, ist es an der Zeit, sie zu verwenden. Erstellen Sie eine JUnit-Testklasse für PersonValidator mit dem Namen PersonValidatorTest_ .

Wir verwenden mehrere Funktionen, die nur in JUnit Jupiter verfügbar sind:

  • @ DisplayName - dies ist der Name, der in den Testberichten angezeigt wird

viel lesbarer ** @ Nested - erstellt eine verschachtelte Testklasse mit einem eigenen Test

Lebenszyklus, getrennt von seiner übergeordneten Klasse ** @ RepeatedTest - der Test wird so oft wiederholt, wie angegeben

durch das Wertattribut (10 in jedem Beispiel)

Durch die Verwendung von @ Nested -Klassen können wir sowohl gültige als auch ungültige Daten in derselben Testklasse testen, wobei sie gleichzeitig vollständig voneinander entfernt sind:

@DisplayName("Testing PersonValidator")
public class PersonValidatorTest {

    @Nested
    @DisplayName("When using Valid data")
    @ExtendWith(ValidPersonParameterResolver.class)
    public class ValidData {

        @RepeatedTest(value = 10)
        @DisplayName("All first names are valid")
        public void validateFirstName(Person person) {
            try {
                assertTrue(PersonValidator.validateFirstName(person));
            } catch (PersonValidator.ValidationException e) {
                fail("Exception not expected: " + e.getLocalizedMessage());
            }
        }
    }

    @Nested
    @DisplayName("When using Invalid data")
    @ExtendWith(InvalidPersonParameterResolver.class)
    public class InvalidData {

        @RepeatedTest(value = 10)
        @DisplayName("All first names are invalid")
        public void validateFirstName(Person person) {
            assertThrows(
              PersonValidator.ValidationException.class,
              () -> PersonValidator.validateFirstName(person));
        }
    }
}

Beachten Sie, wie wir die Erweiterungen ValidPersonParameterResolver und InvalidPersonParameterResolver innerhalb derselben Haupttestklasse verwenden können, indem wir sie nur auf der Klassenebene @ Nested deklarieren. Versuchen Sie das mit JUnit 4! (Spoiler-Alarm: Sie können es nicht tun!)

5. Schlussfolgerung

In diesem Artikel haben wir untersucht, wie zwei ParameterResolver -Erweiterungen geschrieben werden, um gültige und ungültige Objekte bereitzustellen. Dann haben wir uns angesehen, wie man diese beiden ParameterResolver -Implementierungen in einem Unit-Test verwendet.

Der Code ist wie immer verfügbar: https://github.com/eugenp/tutorials/tree/master/testing-modules/junit-5 Über Github].

Wenn Sie mehr über das Erweiterungsmodell von JUnit Jupiter erfahren möchten, besuchen Sie das JUnit 5 Benutzerhandbuch oder https://www . ibm.com/developerworks/library/j-introducing-junit5-part1-jupiter-api/index.html Abschnitt 2 meines Tutorials zu developerWorks].