Start / Seminare / Java Testing mit JUnit, Mockito und Testcontainers
Modul
Lifecycle, Extensions und parallele Ausführung
4 Kapitel in diesem Modul-Video · Laufzeit
Transkript
Der gesprochene Text dieses Moduls zum Mitlesen, Überfliegen und Durchsuchen. Ein Klick auf einen Zeitstempel springt an die Stelle im Video.
Lifecycle, Extensions und parallele Ausführung
0:00 Zum Abschluss des ersten Tages kommen wir zu dem Teil von JUnit, den viele nie anfassen — und der genau deshalb interessant ist. Es geht um alles, was um den Test herum passiert: Wann wird eine Testinstanz erzeugt, wie kontrolliere ich Zeitzone und Sprache, wie schreibe ich eine eigene Erweiterung, und was passiert, wenn Tests plötzlich gleichzeitig laufen.
0:19 Das klingt nach Randthemen. In Wahrheit entscheidet dieser Bereich darüber, ob Ihre Tests auf einem fremden Rechner dasselbe sagen wie auf Ihrem eigenen.
Lifecycle, Extensions und parallele Ausführung
0:28 Das letzte Modul des ersten Tages. Wir kontrollieren zuerst die Umgebung, schreiben dann eine eigene Extension und schalten am Ende die parallele Ausführung ein. Die Extension aus diesem Modul begegnet uns am zweiten Tag wieder, wenn wir über testbare Architektur sprechen.
Lifecycle und Testumgebung kontrollieren
0:45 Fangen wir mit einer Erkenntnis an, die im Alltag oft zu spät kommt: Zeitzone, Sprache und Systemeigenschaften sind Testdaten. Und Testdaten gehören in den Test, nicht auf den Rechner. Jupiter legt standardmäßig für jede Testmethode eine neue Instanz der Testklasse an. Das ist eine kluge Voreinstellung: Sie verhindert, dass Tests sich über Felder gegenseitig beeinflussen.
1:08 Es erklärt aber auch, warum die Methoden für den einmaligen Auf- und Abbau statisch sein müssen — sie gehören zur Klasse, nicht zur Instanz. Wer das nicht will, stellt den Lebenszyklus auf eine Instanz je Klasse um; dann dürfen sie Instanzmethoden sein, und man handelt sich die geteilte Zustandsfrage wieder ein. Für alles außerhalb der Testinstanz bringt Jupiter inzwischen eigene Erweiterungen mit.
1:32 Diese Tabelle enthält eine kleine Neuigkeit, die viel Alltagsschmerz nimmt. Die ersten beiden Einträge kennen die meisten: ein temporäres Verzeichnis, das automatisch wieder verschwindet, und das automatische Schließen von Ressourcen. Die vier darunter sind mit JUnit 6.1 dazugekommen — Sprache, Zeitzone und Systemeigenschaften lassen sich jetzt direkt am Test setzen und werden danach zurückgestellt.
1:55 Bisher brauchte man dafür eine Fremdbibliothek, und in vielen Projekten hat man es deshalb einfach gelassen. Genau dieses Lassen ist der Grund für Tests, die im Ausland rot werden. Achten Sie auf die beiden Annotationen über der Methode. Ohne sie entscheidet der Rechner, auf dem der Test läuft, über das Ergebnis — und das ist keine Theorie.
2:15 Ein Datumsformat unterscheidet sich zwischen deutscher und amerikanischer Spracheinstellung, ein Zeitstempel zwischen Berlin und Bangalore. Solche Tests laufen bei Ihnen zuverlässig grün und beim Kollegen im anderen Land zuverlässig rot. Der dritte Punkt ist das temporäre Verzeichnis als Parameter: JUnit legt es an, gibt es Ihnen und räumt es danach weg. Sie brauchen weder einen festen Pfad noch Aufräumcode.
2:40 Es gibt Tests, die auf bestimmten Systemen nicht gelten können — weil ein Dateisystem sich anders verhält oder eine Java-Version etwas anders macht. Der verbreitete Umgang damit ist das Auskommentieren, und das ist die schlechteste Lösung: Der Test verschwindet spurlos aus dem Bericht. Mit den passenden Bedingungen wird er stattdessen als übersprungen ausgewiesen — sichtbar, zählbar, mit einem Grund.
3:03 Und wenn Sie eigene Bedingungen brauchen, gehören die in eine Bedingungserweiterung, nicht in ein if am Anfang der Testmethode. Ein Test, der sich selbst wegduckt, ist grün ohne Aussage. Der erste Punkt ist die Fehlermeldung, die jeder einmal sieht: Die Methode für den einmaligen Aufbau ist nicht statisch — dann fehlt die Umstellung des Lebenszyklus.
3:23 Der zweite ist die Kehrseite dieser Umstellung: Sie wird gewählt, um Aufbau zu sparen, und plötzlich beeinflussen sich die Tests. Der dritte ist eine Frage des Orts: Zeitzone und Sprache im Build zu setzen wirkt global und verdeckt genau die Tests, die es betrifft. Und der vierte ist ein Klassiker mit langer Lebensdauer — ein Test schreibt in ein festes Verzeichnis und funktioniert nur dort, wo dieses Verzeichnis existiert.
Eigene Extensions entwickeln
3:47 Jetzt bauen wir selbst. Und die Leitfrage lautet: Was drei Testklassen gemeinsam brauchen, wo gehört das hin? Die Antwort lautet nicht Basisklasse. Eine Extension klinkt sich in den Lebenszyklus ein oder liefert Parameter. Die zweite Variante ist die zugänglichste: Ein Parameterauflöser beantwortet genau zwei Fragen — unterstütze ich diesen Parameter, und welchen Wert liefere ich dafür?
4:12 Mehr ist es nicht. Registriert wird auf drei Wegen: deklarativ mit einer Annotation an der Klasse, programmatisch über ein Feld, wenn die Extension konfiguriert werden muss, oder automatisch über den ServiceLoader. Der große Vorteil gegenüber einer Basisklasse: Sie können mehrere Extensions kombinieren, aber nur von einer Klasse erben.
4:33 Das ist die ganze Extension — zwei Methoden. Die erste sagt: Ich bin zuständig, wenn ein Testparameter vom Typ Clock ist. Die zweite liefert eine feste Uhr. Der fachliche Gewinn dahinter ist erheblich: Ab jetzt kann jeder Test im Projekt einen Zeitpunkt anfordern, ohne sich um die Systemzeit zu kümmern, und alle bekommen denselben.
4:53 Damit werden Tests reproduzierbar, die vorher vom Kalender abhingen. Beachten Sie, dass die feste Uhrzeit eine Konstante ist. Wäre sie eine berechnete Zeit, wäre nichts gewonnen — dann würde sich der Testlauf morgen wieder anders verhalten. Auf der Anwendungsseite bleibt fast nichts übrig: eine Annotation an der Klasse, ein Parameter an der Methode.
5:15 Genau das ist die Qualität einer guten Extension — sie verschwindet aus dem Blickfeld. Der Test liest sich, als wäre die Uhr schon immer dagewesen. Und wenn Sie in einem halben Jahr entscheiden, dass die feste Zeit ein anderer Zeitpunkt sein soll, ändern Sie eine Zeile in der Extension statt vierzig Tests. Genau dieser Hebel ist der Grund, wiederkehrende Testinfrastruktur als Extension zu bauen und nicht in jede Testklasse zu kopieren.
5:41 Hier lauert die Falle, die man erst spät bemerkt. Eine Extension ist ein Objekt, und ein Objekt hat Felder — die Versuchung, dort etwas zu merken, ist groß. Sobald aber Tests parallel laufen, teilen sie sich dieses Feld, und die Ergebnisse werden verfälscht. Deshalb gibt es den Store: einen Ablageort, der nach Test oder nach Klasse getrennt ist.
6:02 Ein angenehmer Nebeneffekt: Was dort liegt und schließbar ist, räumt JUnit selbst wieder ab. Und mehrere Extensions kommen sich nicht ins Gehege, weil jede in ihrem eigenen Namensraum ablegt. Der erste Punkt ist genau die eben beschriebene Falle: Zustand im Feld, funktioniert seriell, bricht parallel — und zwar sporadisch, was die Suche besonders unangenehm macht.
6:25 Der zweite betrifft die programmatische Registrierung: Soll die Extension je Klasse gelten, muss das Feld statisch sein. Der dritte ist ein Transparenzproblem — automatische Erkennung ist bequem, aber dann weiß niemand mehr, welche Extensions gerade mitlaufen. Und der vierte ist eine verpasste Aufräumgelegenheit: Die Extension wird gebaut, die alte Basisklasse bleibt aber stehen, und jetzt gibt es beides.
Tests parallel ausführen
6:49 Zum Schluss ein Thema mit zwei Gesichtern. Parallelität macht Testläufe schneller — und deckt gnadenlos auf, welche Tests einander nie vertraut haben. Parallele Ausführung ist in Jupiter bewusst abgeschaltet. Sie schalten sie über Konfigurationsparameter frei, und danach entscheidet der Ausführungsmodus, was tatsächlich gleichzeitig läuft — Klassen, Methoden oder beides.
7:04 Der zweite Teil ist der wichtigere: Sobald Tests gleichzeitig laufen, gibt es geteilte Ressourcen. Systemeigenschaften, die Standardausgabe, die Zeitzone — all das existiert nur einmal pro JVM. Für genau diese Fälle gibt es Ressourcensperren. Sie sagen: Diesen Test bitte nicht gleichzeitig mit anderen, die dieselbe Ressource anfassen. Das ist kein Notbehelf, sondern der vorgesehene Weg.
7:14 Vier Parameter, und die Reihenfolge in der Tabelle ist auch die Reihenfolge der Entscheidungen. Zuerst überhaupt einschalten. Dann festlegen, was gleichzeitig laufen darf. Dann die Strategie: dynamisch richtet sich nach den verfügbaren Prozessorkernen, fest gibt eine Zahl vor. Der letzte Eintrag ist neu — mit JUnit 6.1 gibt es neben dem bekannten Fork-Join-Pool auch einen klassischen Worker-Thread-Pool, allerdings noch als experimentell gekennzeichnet.
7:40 Er lohnt einen Blick, wenn Ihre Tests viel warten statt zu rechnen, denn dann arbeitet ein normaler Thread-Pool oft besser. Diese drei Annotationen ergänzen die globale Konfiguration um lokale Entscheidungen. Die erste erlaubt einer Klasse ausdrücklich die gleichzeitige Ausführung. Die zweite ist die wichtige: Sie meldet an, dass dieser Test eine geteilte Ressource anfasst — hier die Systemeigenschaften — und JUnit sorgt dafür, dass er nicht mit anderen kollidiert, die dasselbe tun.
8:09 Die dritte ist die grobe Kelle: Diese Klasse läuft allein, alles andere wartet. Das ist ein wirksames Mittel und ein teures. Wenn Sie es oft brauchen, stimmt etwas mit der Isolation der Tests nicht. Der erste Punkt beschreibt den typischen ersten Tag mit Parallelität: eingeschaltet, und die Hälfte der Tests wird sporadisch rot.
8:29 Das ist übrigens keine Fehlfunktion, sondern eine Diagnose — diese Tests waren schon vorher voneinander abhängig, es ist nur nie aufgefallen. Der zweite Punkt nennt die häufigste Ursache: statische Felder mit Zustand. Der dritte ist ein Hinweis auf morgen: Die Jupiter-Integration von Testcontainers ist ausdrücklich für serielle Ausführung gedacht.
8:49 Und der vierte ist eine ehrliche Kosten-Nutzen-Rechnung — zehn Prozent weniger Laufzeit gegen viel Analyseaufwand ist selten ein guter Tausch.
Übung
8:57 In der Übung bauen wir die Uhr-Extension selbst und schalten danach die Parallelität ein. Und die eigentliche Arbeit liegt nicht dort, wo Sie es vermuten. Krautgut liefert werktags bis sechzehn Uhr — und heute hängt das Testergebnis am Rechner, auf dem der Test läuft. Ihre Aufgabe ist, diese Abhängigkeit in einen Testparameter zu verwandeln.
9:17 Das Erfolgskriterium ist bewusst zweiteilig: Die Tests müssen unabhängig von Systemzeit und Zeitzone sein, und der parallele Lauf muss dreimal hintereinander grün sein. Das dritte Mal ist der eigentliche Prüfstein — sporadische Fehler zeigen sich selten beim ersten Versuch. Wenn einer der drei Läufe rot wird, haben Sie eine echte Abhängigkeit gefunden, und die ist wertvoller als eine grüne Ausgabe.
9:41 Der rote Faden ist: erst umbauen, dann erweitern. Sie finden zuerst die Stelle, an der Krautgut die Systemzeit direkt abfragt, und reichen die Uhr über den Konstruktor hinein. Das ist die eigentliche Arbeit dieses Vorgehens — danach ist die Extension zwölf Zeilen. Schritt vier nagelt zusätzlich Zeitzone und Sprache fest, und Schritt fünf schaltet die Parallelität ein und sichert die Tests ab, die Ressourcen teilen.
10:06 Merken Sie sich die Reihenfolge: Wer die Extension vor dem Umbau schreibt, hat eine Extension und trotzdem eine statische Zeitabfrage. Der erste Punkt ist die verlockende Abkürzung: die Uhr statisch setzen statt hineinreichen. Das funktioniert im ersten Test und vererbt sich an den nächsten. Der zweite ist unsere bekannte Falle — die Extension speichert die Uhr in einem Feld und bricht bei paralleler Ausführung.
10:31 Der dritte ist das Auslassen von Schritt vier: Der Test bleibt in einer anderen Zeitzone rot, und der Aufwand war halb umsonst. Und der vierte ist menschlich: Nach dem ersten grünen Parallellauf gilt die Sache als erledigt. Genau deshalb steht im Erfolgskriterium die Zahl drei.
Lieber mit Trainer? Dieses Modul ist Teil unserer Team-Schulungen — mit Übungen, Ihrem eigenen Code und Fragen, die ein Video nicht beantwortet. Mehr erfahren →