Flaky Tests: warum dieselbe Suite mal grün und mal rot ist
Wer den Lauf einfach wiederholt, gewöhnt das Team daran, rote Ergebnisse zu ignorieren. Ab da schützt die Suite niemanden mehr, kostet aber weiter.
KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Der Wiederholungsknopf ist die teuerste Lösung
Der Ablauf ist überall gleich. Ein Test schlägt fehl, jemand sieht sich die Änderung an, findet nichts, startet den Lauf erneut, und diesmal ist er grün. Beim dritten Mal spart man sich den Blick und drückt direkt auf Wiederholen. Nach ein paar Wochen ist das die Normalreaktion auf jedes rote Ergebnis, und damit ist die Information verloren, für die die Suite überhaupt gebaut wurde.
Der Schaden trifft nicht nur die instabilen Tests. Sobald Rot als Zustand gilt, den man wegklickt, verlieren auch die zuverlässigen Tests ihre Wirkung, weil niemand mehr unterscheidet. Ein echter Regressionsfehler geht dann in derselben Bewegung unter wie die zehnte Zeitüberschreitung, und er fällt erst auf, wenn ihn jemand außerhalb des Teams meldet.
Der zweite Schaden ist Rechenzeit und Wartezeit. Ein Lauf, der bei jedem dritten Anlauf wiederholt wird, verlängert die Rückmeldung um die Hälfte. Bei einer Suite, die zwanzig Minuten braucht, sind das über ein Jahr gerechnet Wochen an Wartezeit, die niemand als Kostenposten führt, weil sie sich auf viele kleine Momente verteilt.
KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Die Fälle, die im Alltag wirklich auflaufen
Sortiert nach dem, was du siehst, nicht nach der Kategorie dahinter. Die Häufigkeitsangaben bei den einzelnen Fällen stammen aus einer Untersuchung von 201 Korrekturen aus Open-Source-Projekten.
Symptom
Der Test schlägt auf dem Build-Knoten unregelmäßig fehl, auf dem Entwicklungsrechner nie. Im Protokoll steht, dass ein Element nicht gefunden wurde oder ein Wert noch der alte war, obwohl der Vorgang längst hätte fertig sein müssen.
Ursache
Der Test wartet eine feste Zeitspanne, statt auf eine Bedingung zu warten. Das ist mit deutlichem Abstand die häufigste Ursache: In der Untersuchung von Luo und Kollegen entfielen 74 von 161 zugeordneten Fällen auf diese Kategorie. Auf einem ausgelasteten Build-Knoten reicht die Zeitspanne nicht, auf dem Entwicklungsrechner reicht sie immer, und deshalb sieht es nach einem Umgebungsproblem aus.
Lösung
Ersetz die Wartezeit durch Warten auf einen Zustand. Moderne Werkzeuge für Oberflächentests bringen das mit, indem sie vor jeder Aktion prüfen, ob das Element bedienbar ist, und bis zu einer oberen Grenze warten. Bei eigenem Code nimmst du eine Schleife, die eine Bedingung abfragt, mit großzügigem Zeitlimit und kurzem Abstand zwischen den Versuchen. In der genannten Untersuchung wurden 54 Prozent dieser Fälle genau so behoben, und zwar dauerhaft und nicht nur mit geringerer Wahrscheinlichkeit.
Symptom
Der Test läuft allein zuverlässig durch, in der vollständigen Suite fällt er gelegentlich aus. Wenn ihr die Suite parallel oder in anderer Reihenfolge startet, ändert sich das Bild deutlich.
Ursache
Eine Abhängigkeit zwischen Testfällen über geteilten Zustand. Typische Träger sind statische Felder, ein Zwischenspeicher, eine gemeinsam benutzte Datenbank, angelegte Dateien, gesetzte Umgebungsvariablen oder ein Datensatz, den ein vorheriger Test erzeugt hat. Solange die Reihenfolge stabil bleibt, fällt das nicht auf. Sobald ein neuer Test dazukommt oder der Runner parallelisiert, kippt es.
Lösung
Jeder Test stellt seinen Ausgangszustand selbst her und räumt hinterher auf. In der Untersuchung wurden 74 Prozent dieser Fälle durch das Bereinigen des geteilten Zustands vor und nach dem Lauf behoben. Praktisch heißt das: eigene Datensätze mit eindeutigen Kennungen statt fester Testdaten, ein Zurücksetzen in der Aufräummethode statt in der Vorbereitung des nächsten Tests, und für die Datenbank entweder eine Transaktion, die zurückgerollt wird, oder ein frisch aufgebauter Container je Lauf.
Symptom
Der Fehlschlag betrifft einen Test, in dem mehrere Threads beteiligt sind, und das Protokoll zeigt jedes Mal ein etwas anderes Bild. Manchmal fehlt ein Eintrag, manchmal steht ein Zähler auf einem unerwarteten Wert.
Ursache
Echte Nebenläufigkeit ohne ausreichende Synchronisierung, entweder im Test oder im geprüften Code. Die Untersuchung ordnet dieser Kategorie 32 Fälle zu, und in fast allen genügten zwei Ausführungsstränge, um den Fehler zu erzeugen. Der wichtige Punkt: Hier ist der Test häufig nur der Bote. Ein Zähler, der unter Last falsch steht, steht im Betrieb genauso falsch, nur merkt es dort niemand.
Lösung
Vor dem Umschreiben des Tests prüfen, ob der geprüfte Code die Ursache ist. Wenn ja, gehört die Korrektur dorthin, also eine Sperre, ein atomarer Zugriff oder ein Umbau, der den geteilten Zustand vermeidet. Liegt es am Test, muss er die Reihenfolge ausdrücklich erzwingen, statt sich auf die zufällige Verteilung zu verlassen. Was nicht hilft, ist eine längere Wartezeit, denn sie verschiebt nur die Wahrscheinlichkeit.
Symptom
Die ersten Tests eines Laufs sind grün, gegen Ende häufen sich Fehlschläge. Nach einem Neustart der Umgebung ist alles wieder in Ordnung. Im Protokoll tauchen belegte Ports, zu viele offene Dateien oder Zeitüberschreitungen beim Verbindungsaufbau auf.
Ursache
Eine nicht freigegebene Ressource. Verbindungen, Dateizeiger, Ports, Ausführungsstränge oder temporäre Verzeichnisse werden geöffnet und nicht geschlossen, meistens weil die Aufräummethode bei einem Fehlschlag übersprungen wird. Die Untersuchung ordnet dieser Kategorie 11 Fälle zu. Charakteristisch ist die Reihenfolge im Zeitverlauf: Der Fehler wandert mit der Laufdauer nach hinten, statt an einer festen Stelle zu sitzen.
Lösung
Ressourcen in einem Konstrukt anfordern, das sie garantiert freigibt, also try-with-resources in Java, ein Kontextmanager in Python oder die vom Test-Framework vorgesehene Aufräumregistrierung. Für Ports keine festen Nummern verwenden, sondern einen freien Port vom Betriebssystem zuteilen lassen. Und einmal messen: Wenn der Speicherverbrauch über den Lauf hinweg steigt, ist das derselbe Befund aus einer anderen Richtung.
Symptom
Die Fehlschläge treten in Wellen auf, mehrere Tests gleichzeitig, und immer die, die mit einem fremden Dienst reden. Zwischendurch läuft alles wochenlang sauber.
Ursache
Abhängigkeit von Netz oder einem fremden Dienst. Ein Anmeldedienst antwortet langsamer, eine Testinstanz eines Zahlungsanbieters wird gewartet, eine Namensauflösung schlägt fehl, oder eine Anfragebegrenzung greift, weil mehrere Läufe gleichzeitig starten. Die Untersuchung zählt 10 Fälle in dieser Kategorie, im Alltag ist der Anteil in verteilten Anwendungen deutlich höher.
Lösung
Trenn die Ebenen sauber. Für die Prüfung eurer eigenen Logik gehört der fremde Dienst ersetzt, entweder durch eine feste Antwort oder durch einen Vertragstest gegen ein aufgezeichnetes Schema. Die Prüfung, ob die echte Verbindung funktioniert, gehört in einen eigenen, selteneren Lauf, dessen Fehlschlag nicht die Auslieferung blockiert, sondern eine Meldung an den Betrieb erzeugt. Eine Anfragebegrenzung umgehst du nicht mit Wiederholungen, sondern mit eigenen Zugangsdaten je Lauf.
Symptom
Ein Test schlägt reproduzierbar zu bestimmten Zeiten fehl, etwa nachts, am Monatsende oder rund um die Zeitumstellung. Oder er fällt in unregelmäßigen Abständen aus, ohne dass sich ein Muster zeigt.
Ursache
Abhängigkeit von der Uhr, von der Zeitzone oder von Zufall. Ein Test rechnet mit dem heutigen Datum und geht davon aus, dass der Monat noch nicht endet. Eine Umrechnung nimmt die Zeitzone des Build-Knotens statt einer festgelegten. Zufällig erzeugte Testdaten treffen alle paar hundert Läufe eine Kombination, die die Fachlogik nicht abdeckt. Eine Sammlung ohne definierte Reihenfolge wird verglichen, als hätte sie eine.
Lösung
Zeit als Abhängigkeit behandeln und im Test einsetzen, statt sie global abzufragen. Zeitzone und Sprachumgebung im Testrahmen fest vorgeben, statt sie vom Build-Knoten zu erben. Zufallsgeneratoren mit einem festen Startwert versehen und diesen Wert im Fehlerprotokoll ausgeben, damit ein Fehlschlag reproduzierbar bleibt. Und bei Vergleichen von Sammlungen prüfen, ob die Reihenfolge überhaupt Teil der Aussage ist. Wenn nicht, gehört sie aus dem Vergleich heraus.
Fünf Schritte von der Beobachtung zur Ursache
- 01 Fehlschlagquote je Testfall über mehrere Wochen erheben, nicht nur den letzten Lauf ansehen.
- 02 Den Test isoliert und mehrfach hintereinander laufen lassen. Bleibt er grün, liegt es an der Umgebung.
- 03 Die Suite in zufälliger Reihenfolge starten. Bricht er jetzt, ist es eine Abhängigkeit zwischen Tests.
- 04 Nach festen Wartezeiten und nach Zugriffen auf Uhrzeit, Zufall oder Netz im Test suchen.
- 05 Zuletzt prüfen, ob der Produktivcode die eigentliche Ursache ist, bevor ihr den Test umschreibt.
Was ihr tut, statt den Lauf zu wiederholen
Sechs Maßnahmen, die zusammen wirken. Die ersten drei machen Instabilität sichtbar und zuordenbar, die letzten drei verhindern, dass neue entsteht.
Instabilität messbar machen
Ihr braucht pro Testfall die Zahl der Läufe und die Zahl der Fehlschläge über einen Zeitraum, nicht nur das Ergebnis des letzten Laufs. Erst diese Quote trennt den Test, der einmal aus einem echten Grund rot war, von dem, der in jedem zwanzigsten Lauf ohne Anlass ausfällt. Ohne diese Zahl redet ihr über Eindrücke.
Jeden Fall wie einen Fehler behandeln
Ein instabiler Test bekommt eine Meldung mit Zuständigkeit und Frist, genau wie ein Produktfehler. Das klingt nach Bürokratie und ist der einzige Weg, wie er nicht als Hintergrundgeräusch verschwindet. Wichtig ist die Reihenfolge: erst untersuchen, dann entscheiden, ob er stillgelegt wird, nie umgekehrt.
Stilllegen nur mit Datum und Namen
Ein Test darf aus dem blockierenden Lauf herausgenommen werden, damit die Auslieferung weitergeht. Aber nur mit einem Eintrag, der sagt, wer ihn wieder aufmacht und bis wann. Ohne diese beiden Angaben ist das Stilllegen ein Löschen mit besserem Gewissen, und die Suite verliert schleichend Abdeckung.
Feste Wartezeiten aus der Suite verbannen
Eine Anweisung, die zwei Sekunden wartet, ist eine Wette auf die Geschwindigkeit der Maschine. Sie geht auf dem Entwicklungsrechner auf und in einem ausgelasteten Build-Knoten nicht. Ersetzt sie durch Warten auf eine Bedingung, also auf einen Zustand, ein Element oder eine Antwort, mit einer großzügigen oberen Grenze.
Jeden Test seinen Zustand selbst herstellen lassen
Ein Testfall darf nicht davon abhängen, dass ein anderer vorher gelaufen ist. Das prüft ihr am schnellsten, indem ihr die Suite in zufälliger Reihenfolge und parallel laufen lasst. Was dann bricht, hatte schon vorher eine Abhängigkeit, sie war nur unsichtbar, weil die Reihenfolge zufällig stimmte.
Bei jedem Fund fragen, ob der Produktivcode gemeint ist
Ein Test, der auf eine Antwort wartet, die manchmal ausbleibt, kann eine fehlende Synchronisierung im Produktivcode aufdecken. In der Untersuchung von Luo und Kollegen veränderte rund ein Viertel der Korrekturen den Produktivcode, und in fast allen diesen Fällen wurde dort ein echter Fehler behoben. Diese Frage gehört an den Anfang der Analyse, nicht ans Ende.
KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Warum Instabilität fast immer schon beim Schreiben entsteht
Eine der überraschendsten Beobachtungen der Untersuchung von Luo und Kollegen ist, dass 78 Prozent der instabilen Tests schon bei ihrer Entstehung instabil waren. Es ist also selten so, dass ein solider Test durch spätere Änderungen unzuverlässig wird. Er war es von Anfang an, und es hat nur eine Weile gedauert, bis die ungünstige Kombination aus Last, Reihenfolge und Zeitpunkt eintrat.
Das verschiebt den sinnvollsten Ansatzpunkt nach vorn. Ein neuer Test lässt sich beim Hinzufügen deutlich härter prüfen als später, wenn er in einer Suite von tausend anderen sitzt: mehrfach hintereinander laufen lassen, unter künstlicher Last, in geänderter Reihenfolge und auf einem Knoten mit weniger Kernen. Das kostet Minuten und spart Monate.
Für das Review folgt daraus eine kurze Prüfliste: Steht im Test eine feste Wartezeit? Greift er auf die aktuelle Uhrzeit zu, ohne sie festzulegen? Benutzt er einen Datensatz, den ein anderer Test angelegt hat? Legt er etwas an, das er nicht wieder entfernt? Vier Fragen, die den größten Teil der späteren Fälle verhindern.
Der Test ist oft der Bote und nicht der Täter
In derselben Untersuchung veränderten 24 Prozent der Korrekturen den geprüften Produktivcode, und in 94 Prozent dieser Fälle wurde dort ein echter Fehler behoben. Das ist der wichtigste Satz zu diesem Thema und der am seltensten beachtete: Fast ein Viertel der instabilen Tests zeigte auf einen Defekt im Produkt.
Der Mechanismus dahinter ist einleuchtend. Ein Test läuft schneller, öfter und unter ungewöhnlicheren Bedingungen als jede manuelle Prüfung. Damit trifft er Zeitfenster, die im Alltag selten sind, aber unter Last im Betrieb regelmäßig auftreten. Eine fehlende Synchronisierung, eine Antwort, die manchmal ausbleibt, ein Zustand, der nicht sauber initialisiert wird, all das wird im Test sichtbar, bevor es Kunden trifft.
Die praktische Folge ist eine Regel für die Analyse: Bevor jemand einen Test umschreibt oder stilllegt, muss er beantworten, ob das beobachtete Verhalten im Betrieb ebenfalls auftreten könnte. Wenn ja, gehört die Korrektur in den Produktivcode, und der Test bleibt, wie er ist. Wer diese Frage überspringt, macht ein Warnlicht aus.
Wiederholungen und Stilllegungen richtig einsetzen
Automatische Wiederholungen sind nicht grundsätzlich falsch, sie sind nur meistens falsch eingesetzt. Als Dauerlösung verstecken sie die Ursache und gewöhnen das Team an rote Ergebnisse. Als Messinstrument sind sie dagegen nützlich: Wenn ihr protokolliert, welcher Test in welchem Anlauf grün wurde, habt ihr genau die Fehlschlagquote, die ihr für die Priorisierung braucht.
Wenn ihr Wiederholungen benutzt, dann eng begrenzt und immer sichtbar. Ein Test, der im zweiten Anlauf grün wird, darf den Lauf nicht als sauber ausweisen, sondern muss als instabil markiert erscheinen. Diese Unterscheidung ist der ganze Unterschied zwischen einem Werkzeug und einer Vertuschung.
Für das Stilllegen gilt dasselbe in strenger Form. Ein Test aus dem blockierenden Lauf zu nehmen, ist eine legitime Notmaßnahme, wenn eine Auslieferung ansteht. Sie braucht aber immer drei Angaben: warum, wer, bis wann. Ohne Datum bleibt so ein Eintrag stehen, und nach zwei Jahren enthält die Suite eine ganze Reihe von Tests, die niemand mehr ausführt und deren Abdeckung trotzdem in der Statistik steht.
Was die Wahl des Werkzeugs beiträgt und was nicht
Werkzeuge für Oberflächentests haben in den letzten Jahren einen erheblichen Teil der Async-Wait-Fälle aus der Welt geschafft, indem sie vor jeder Aktion selbst prüfen, ob ein Element sichtbar, verbunden und bedienbar ist, und bis zu einer oberen Grenze warten. Das ist kein Marketingversprechen, sondern die direkte Antwort auf die häufigste Ursachenkategorie überhaupt. Wer heute eine neue Suite aufbaut, sollte diese Eigenschaft zu einem harten Auswahlkriterium machen.
Was kein Werkzeug löst, sind Abhängigkeiten zwischen Testfällen, ungeklärte Testdaten und fehlende Synchronisierung im Produktivcode. Diese drei Kategorien hängen an eurem Entwurf, nicht am Test-Runner. Ein Wechsel des Werkzeugs verschiebt sie höchstens, und wer nach dem Wechsel dieselben Fehlschläge sieht, hat die Ursache nun immerhin eingegrenzt.
Auch die Auswahl der Selektoren gehört hierher. Ein Test, der ein Element über eine generierte Klassenbezeichnung oder einen langen Pfad im Dokument findet, bricht bei jeder Umgestaltung. Ein Test, der es über die Rolle und den sichtbaren Text findet, überlebt sie. Das ist streng genommen kein Instabilitätsproblem, sondern ein Wartbarkeitsproblem, im Alltag laufen beide aber unter derselben Beschwerde auf.
Wie ihr wieder Vertrauen in die Suite aufbaut
Eine Suite, die das Vertrauen des Teams verloren hat, gewinnt es nicht dadurch zurück, dass alle Tests repariert werden. Das dauert zu lange, und in der Zwischenzeit gilt Rot weiterhin als bedeutungslos. Der schnellere Weg ist eine kleine, ausdrücklich benannte Menge von Tests, die als verlässlich gilt und deren Fehlschlag die Auslieferung wirklich anhält.
Diese Menge wächst danach schrittweise. Jeder Test, dessen Fehlschlagquote über einen definierten Zeitraum bei null liegt, kommt dazu. Jeder, der in dieser Menge instabil wird, fliegt sofort wieder heraus und bekommt eine Meldung. Nach ein paar Monaten habt ihr einen Kern, auf den sich das Team verlässt, und einen Rest, an dem gearbeitet wird, ohne dass er die Auslieferung blockiert.
Der letzte Baustein ist Sichtbarkeit. Eine Übersicht, die je Test die Fehlschlagquote der letzten Wochen zeigt, macht aus einem diffusen Ärgernis eine Liste mit Reihenfolge. Teams, die diese Liste haben, arbeiten sie erfahrungsgemäß auch ab, weil der Fortschritt sichtbar ist. Teams ohne diese Liste diskutieren stattdessen darüber, ob es besser oder schlechter geworden ist.
Dazu passende Kurse
Weil sich Wartebedingungen und Selektoren am schnellsten an einem echten Projekt lernen lassen, sind Kurse zu stabilen End-to-End-Tests der kürzere Weg als das Nachlesen in der Werkzeugdokumentation.
Ein Teil der Fehlschläge kommt gar nicht aus dem Test, sondern aus der Umgebung, in der er läuft, und dazu gibt es Kurse zur Build-Kette und ihren Umgebungen .
Wie sicher bist du beim Thema wirklich?
Lesen fühlt sich schnell nach Können an. Ein kurzer Test zeigt dir, was davon schon sitzt und wo sich ein Kurs lohnt. Kostenlos, ohne Anmeldung, mit einer Erklärung zu jeder Antwort.
Wo du genau das übst
Sehr umfangreicher Überblick über Zusammenhänge und Anwendungsbereiche. Sehr zu empfehlen.
Effektives, gemeinsames Erarbeiten der Aufgaben mit vielen praktischen Tipps.
Sehr guter Trainer und gute Aufbereitung der Inhalte und auch eingehen auf Fragen der Beteiligten.
Häufige Fragen
Ab welcher Fehlschlagquote gilt ein Test als instabil?
Sind Wiederholungen im Test-Runner generell verboten?
Wie finde ich heraus, ob es an der Reihenfolge liegt?
Hilft mehr Rechenleistung im Build gegen instabile Tests?
Sollen wir einen hartnäckig instabilen Test einfach löschen?
Deine Ansprechpartner
Du bist dir nicht sicher, welcher Kurs oder welches Level zu dir passt? Wir beraten dich persönlich und kostenlos.
Yves Hoppe
Weiterbildung & Beratung
Hilft dir, aus dem Programmierung-Programm den passenden Kurs für deinen Stand zu finden.
Norbert Jansen
Beratung & Inhouse
Plant mit dir Inhouse-Trainings, die auf eure Abläufe und euren Datenbestand zugeschnitten sind.
Eine Suite bauen, der euer Team wieder glaubt
Ob ihr mit einem Keyword-getriebenen Framework arbeitet oder direkt im Testcode, die Ursachen sind dieselben. In den Automatisierungskursen bei cmt geht es deshalb ausdrücklich um Struktur, stabile Selektoren und Wartebedingungen und nicht nur um die Bedienung des Werkzeugs.
Passt dazu
- Wann sich Testautomatisierung rechnet und wann sie Geld verbrennt
- Wie viele Unit-, Integrations- und Oberflächentests euer Projekt braucht
- pytest läuft lokal, scheitert in der CI: die häufigsten Ursachen
- REST-APIs testen: was neben dem Happy Path geprüft gehört