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.
| Annotation | Wann ausgeführt | Methode muss sein |
|---|---|---|
@BeforeAll | Einmal, vor allen Tests in der Klasse | static (im Standard-Lebenszyklus) |
@BeforeEach | Vor jeder @Test-Methode | Instanzmethode |
@Test | Der Test selbst | Instanzmethode |
@AfterEach | Nach jeder @Test-Methode | Instanzmethode |
@AfterAll | Einmal, nachdem alle Tests ausgeführt wurden | static (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.
| Aspekt | PER_METHOD (Standard) | PER_CLASS |
|---|---|---|
| Erstellte Instanzen | eine pro @Test | eine pro Klasse |
| Zustand der Instanzfelder | wird vor jedem Test zurückgesetzt | wird über Tests geteilt |
@BeforeAll/@AfterAll | müssen static sein | dürfen Instanzmethoden sein |
| Am besten für | maximale Isolation | aufwendiges 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.
Was aus der Ausführung zu entnehmen ist:
- Der
PER_METHOD-Block gibtinstance#1,instance#2,instance#3fü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]-Zeilecounter=1, niemals2oder3. 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 verwendetinstance#1für alle drei Tests, und seincountersteigt1 → 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. @BeforeAllund@AfterAllerscheinen 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 fehlgeschlagenescheckwirft einenAssertionErrormit einerFAIL:-Meldung, was widerspiegelt, wieAssertions.assertEqualseinen einzelnen Test mit einemAssertionFailedErrorabbricht, während die übrigen weiterlaufen.
Verwandte Kapitel
- JUnit-Einführung — JUnit 5 einrichten und den ersten Test ausführen.
- JUnit-Annotationen — alle Annotationen, die den Lebenszyklus gestalten.
- JUnit assertions — wie ein Test tatsächlich Bestehen oder Scheitern meldet.
- Parametrisierte Tests — einen Testkörper über viele Eingaben ausführen.