W3docs

Java JUnit Test-Lebenszyklus

Testinstanz-Lebenszyklus und das Verhalten pro Methode vs. pro Klasse in JUnit 5.

Jeder JUnit 5-Test läuft innerhalb eines klar definierten Lebenszyklus: eine Abfolge von Setup- und Teardown-Hooks, die in garantierter Reihenfolge um Ihre @Test-Methoden herum ausgeführt werden. Diese Reihenfolge zu verstehen — sowie die Regel, dass JUnit für jede Testmethode eine neue Instanz der Testklasse erstellt — ist das, was fragile, reihenfolgeabhängige Test-Suites von sauberen, isolierten unterscheidet. Dieses Kapitel behandelt die fünf Lebenszyklus-Annotationen und die zwei Instanz-Lebenszyklen, die JUnit anbietet.

Die fünf Lebenszyklus-Annotationen

JUnit 5 (das Paket org.junit.jupiter.api) definiert vier Callback-Annotationen, die Ihre Tests einrahmen, plus @Test selbst. Eine ausführlichere Übersicht zu jeder einzelnen finden Sie unter JUnit-Annotationen; wenn Sie neu im Framework sind, beginnen Sie mit der JUnit-Einführung.

AnnotationWann ausgeführtMethode muss sein
@BeforeAllEinmal, vor allen Tests in der Klassestatic (im Standard-Lebenszyklus)
@BeforeEachVor jeder @Test-MethodeInstanzmethode
@TestDer Test selbstInstanzmethode
@AfterEachNach jeder @Test-MethodeInstanzmethode
@AfterAllEinmal, nachdem alle Tests ausgeführt wurdenstatic (im Standard-Lebenszyklus)

Eine einzelne Testklasse mit drei Tests löst daher @BeforeAll einmal aus, dann @BeforeEach@Test@AfterEach dreimal, und schließlich @AfterAll einmal.

import org.junit.jupiter.api.*;

class CalculatorTest {
  @BeforeAll  static void initSuite() { System.out.println("once, up front"); }
  @BeforeEach void setUp()           { System.out.println("before each test"); }

  @Test void add()      { Assertions.assertEquals(4, 2 + 2); }
  @Test void subtract() { Assertions.assertEquals(0, 2 - 2); }

  @AfterEach void tearDown()    { System.out.println("after each test"); }
  @AfterAll  static void close() { System.out.println("once, at the end"); }
}

Eine frische Instanz pro Testmethode

Die wichtigste Regel des Lebenszyklus: Standardmäßig erstellt JUnit eine brandneue Instanz der Testklasse vor jeder Testmethode. Felder, die Sie in einem Test verändern, können nicht in einen anderen überlaufen, da der nächste Test auf einem anderen Objekt läuft. Das macht Tests unabhängig von der Ausführungsreihenfolge.

class IsolationTest {
  private int counter = 0; // re-initialised for every test

  @Test void first()  { counter++; Assertions.assertEquals(1, counter); }
  @Test void second() { counter++; Assertions.assertEquals(1, counter); } // also 1, not 2
}

Beide Tests sehen counter == 1. Würde JUnit eine Instanz wiederverwenden, würde der zweite Test 2 beobachten und je nach Reihenfolge bestehen oder scheitern — genau die Fragilität, die dieses Design verhindert.

PER_METHOD vs. PER_CLASS

Sie können die Instanz-pro-Methode-Strategie mit @TestInstance(Lifecycle.PER_CLASS) deaktivieren. Dann erstellt JUnit eine Instanz für die gesamte Klasse, Instanzfelder bleiben über Tests hinweg erhalten, und — als Komfort — dürfen @BeforeAll/@AfterAll nicht-static sein.

AspektPER_METHOD (Standard)PER_CLASS
Erstellte Instanzeneine pro @Testeine pro Klasse
Zustand der Instanzfelderwird vor jedem Test zurückgesetztwird über Tests geteilt
@BeforeAll/@AfterAllmüssen static seindürfen Instanzmethoden sein
Am besten fürmaximale Isolationaufwendiges gemeinsames Setup
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.TestInstance.Lifecycle;

@TestInstance(Lifecycle.PER_CLASS)
class SharedFixtureTest {
  @BeforeAll void openConnection() { /* non-static is now legal */ }
  @AfterAll  void closeConnection() { }
}

Verwenden Sie PER_CLASS nur, wenn das Setup wirklich aufwendig und sicher teilbar ist. Der Standard bietet Ihnen Isolation kostenlos.

Assertions sind die Art, wie ein Test einen Fehler meldet

Ein Lebenszyklus existiert, um Assertions auszuführen. Assertions.assertEquals(expected, actual) wirft einen AssertionFailedError, wenn die Werte abweichen, was diesen einzelnen Test abbricht (sein @AfterEach läuft dennoch) und ihn als fehlgeschlagen markiert — andere Tests laufen weiter. Siehe JUnit assertions für den vollständigen Satz der assert*-Methoden.

import static org.junit.jupiter.api.Assertions.*;

@Test void example() {
  assertEquals(42, compute());
  assertTrue(isReady());
  assertThrows(IllegalArgumentException.class, () -> parse("bad"));
}

Ein ausgearbeitetes Beispiel: den Lebenszyklus manuell nachverfolgen

Auf diesem Code-Playground gibt es keinen JUnit-Runner, daher modelliert das folgende Programm den Lebenszyklus in reinem JDK-Code: Es löst die Hooks in JUnit-Reihenfolge aus, vergleicht PER_METHOD (eine neue Instanz pro Test) mit PER_CLASS (eine gemeinsame Instanz) und endet mit einem kleinen selbstprüfenden Testrahmen im Geiste von assertEquals.

java— editable, runs on the server

Was aus der Ausführung zu entnehmen ist:

  • Der PER_METHOD-Block gibt instance#1, instance#2, instance#3 für die drei Tests aus und beweist damit JUnits Standardregel: Für jede @Test-Methode wird eine neue Testinstanz erstellt, sodass kein Test den veränderten Zustand eines anderen sehen kann.
  • Im PER_METHOD-Block meldet jede [TEST]-Zeile counter=1, niemals 2 oder 3. Jede Instanz erhielt ihr eigenes frisches Feld, weshalb Tests unabhängig von der Ausführungsreihenfolge bleiben — der Kernvorteil des Standard-Lebenszyklus.
  • Der PER_CLASS-Block verwendet instance#1 für alle drei Tests, und sein counter steigt 1 → 2 → 3. Mit einer gemeinsamen Instanz leckt der Instanzfeldstatus bewusst zwischen Tests — nützlich für aufwendige gemeinsame Fixtures, gefährlich wenn man es vergisst.
  • @BeforeAll und @AfterAll erscheinen jeweils genau einmal pro Block und umschließen die @BeforeEach/@AfterEach-Paare, die dreimal ausgeführt werden — die exakte Verschachtelungsreihenfolge, die JUnit um Ihre Tests herum garantiert.
  • Der abschließende Testrahmen gibt PASS: für alle drei Prüfungen aus; ein fehlgeschlagenes check wirft einen AssertionError mit einer FAIL:-Meldung, was widerspiegelt, wie Assertions.assertEquals einen einzelnen Test mit einem AssertionFailedError abbricht, während die übrigen weiterlaufen.

Verwandte Kapitel

Übungen

Übung
Wie viele Instanzen einer Testklasse mit drei @Test-Methoden werden in JUnit 5 mit dem Standard-Testinstanz-Lebenszyklus erstellt?
Wie viele Instanzen einer Testklasse mit drei @Test-Methoden werden in JUnit 5 mit dem Standard-Testinstanz-Lebenszyklus erstellt?
Was this page helpful?