Unzuverlässige Tests sind automatisierte Tests, denen die Eigenschaft der Wiederholbarkeit fehlt: Sie bestehen manchmal und schlagen ein anderes Mal für denselben Code fehl, ohne dass die Codebasis verändert wurde. Das zerstört das Vertrauen in KI-Pipelines (kontinuierliche Integration), weil die Ingenieure nicht mehr erkennen können, ob eine Fehlerwirkung einen echten Fehler signalisiert. Die beiden häufigsten Ursachen sind der gleichzeitige Zugriff auf gemeinsame Daten und der veränderbare gemeinsame Zustand.
Das Wichtigste in Kürze
- Ein unzuverlässiger Test bricht die grundlegende Eigenschaft der Wiederholbarkeit auf, und sobald die Entwickler den Ergebnissen der kontinuierlichen Integration nicht mehr trauen, bricht der gesamte Wert der kontinuierlichen Integration zusammen.
- Fehlerhafte Tests bergen manchmal echte Risiken für die Produktion: vorübergehende Fehlhandlungen im Netzwerk, nicht verfügbare Abhängigkeiten und Wettlaufbedingungen in Tests sind die gleichen Fehlerwirkungen, auf die die Benutzer im Live-System stoßen werden.
- Einen fehlerhaften Test aus der CI-Rotation zu entfernen, ist notwendig, aber nicht ausreichend; die Verantwortung muss einer bestimmten Person zugewiesen werden, nicht einem Team, sonst wird die Korrektur nie durchgeführt.
- Die Praxis von Meta, jeden neuen Test über Nacht hundertmal gleichzeitig laufen zu lassen, bevor er in die CI aufgenommen wird, ist eine konkrete, automatisierte Gatekeeping-Strategie, die verhindert, dass fehlerhafte Tests überhaupt in den Build gelangen.
- Das Löschen eines fehlerhaften Tests ist eine legitime Lösung, vor allem, wenn Tests auf niedrigerer Ebene bereits das gleiche Risiko abdecken, denn es ist wichtiger, Änderungen schnell und sicher auszuliefern, als den Testcode zu erhalten.
Was macht einen Test fehlerhaft?
Ein fehlerhafter Test ist ein Test, der gegen die Regel der Wiederholbarkeit verstößt. Wenn du ihn immer wieder durchführst, sollte er immer die gleiche Antwort geben: bestanden oder fehlgeschlagen, aber immer die gleiche. Ein flockiger Test tut das nicht. Er besteht, er schlägt fehl, er besteht, er schlägt fehl. Diese Inkonsistenz ist das ganze Problem.
Gute Tests haben ein paar Qualitäten gemeinsam. Sie sind selbstprüfend, sodass kein Mensch das Ergebnis bestätigen muss. Sie sind schnell, denn die Rückmeldung sollte schnell erfolgen. Sie sind isoliert, damit sie gleichzeitig laufen können, ohne sich gegenseitig zu behindern. Und sie sind wiederholbar. Flakiness greift diese letzte Qualität an und nichts anderes.
Der Schaden zeigt sich in deinem System für kontinuierliche Integration. Du richtest die kontinuierliche Integration aus einem Grund ein: um zu überprüfen, ob eine Änderung sicher ist. Wenn der Build grünes Licht gibt, sagt jede automatisierte Prüfung, dass die Änderung so sicher ist, wie sie nur sein kann. Ein fehlerhafter Test verfälscht dieses Signal. Du weißt nicht mehr, ob Rot eine echte Fehlerwirkung oder nur ein Störgeräusch bedeutet.
Warum fehlerhafte Tests nicht nur Zeit, sondern auch Vertrauen kosten
Der tiefere Preis eines unzuverlässigen Tests ist das Vertrauen in die CI-Pipeline. Alles, was fehlgeschlagen ist, egal ob bestanden oder nicht, untergräbt dieses Vertrauen. Sobald du dem Build nicht mehr vertraust, kannst du ihn auch nicht mehr ausführen.
Auch der Zeitverlust ist real. Die meisten Teams kennen das Ritual: Der Build wird rot, jemand zuckt mit den Schultern und sagt: “Ach, das macht er halt so”, und lässt ihn erneut laufen. Manchmal besteht er, manchmal schlägt er fehl. Erst beim zweiten oder dritten Durchlauf fragt man sich, ob es sich wirklich um eine Fehlerwirkung handelt. Bis dahin hast du deine Feedback-Schleife gedehnt und den Wert verbrannt, den die KI dir eigentlich geben sollte.
Eine schnelle Rückmeldung über eine Änderung ist eines der zentralen Ziele der Softwareentwicklung. Unzuverlässige Tests laufen diesem Ziel direkt zuwider.
Ein schlaffer Test kann ein nützlicher Test sein
Unregelmäßigkeit bedeutet nicht automatisch, dass der Test schlecht ist. Manchmal deutet er auf etwas Wissenswertes hin. Wenn der Test bestanden ist, sagt er dir, dass der Code funktioniert, wenn alles passt. Wenn er fehlgeschlagen ist, kann er eine echte Schwachstelle in deinem System aufdecken.
Die Ursachen für die Fehlerwirkung liegen selten im Test-Framework selbst. Meistens handelt es sich um eine vorübergehende Fehlhandlung im Netzwerk, eine kurzzeitig ausgefallene Abhängigkeit, eine nicht verfügbare Datenbank oder Nachrichtenwarteschlange oder eine Race Condition. Das sind genau die Bedingungen, mit denen dein produktiver Code konfrontiert sein wird, sobald er in den Händen der Benutzer ist.
Ein fehlerhafter Test kann also ein Segen sein. Er zwingt dich zu fragen, warum der Test fehlgeschlagen ist und ob dein Code robust genug ist, um die gleiche Situation in der Produktion zu überstehen. Du kannst von deinen Abhängigkeiten keine 100-prozentige Betriebszeit erwarten. Wenn eine vorübergehende Fehlerwirkung deinen Test unterbricht, wird die gleiche Fehlerwirkung auch deine Nutzer/innen erreichen.
Der gängige Reflex ist falsch. Die Entwickler befunden einen fehlerhaften Test und geben dem Test oder der Infrastruktur die Schuld: “Es hätte einfach funktionieren müssen.” Oft ist die ehrliche Antwort, dass der Code mit dem Schluckauf hätte umgehen müssen, es aber nicht getan hat.
Warum fehlerhafte Tests schwer zu beheben sind
Die Ursache für fehlerhafte Tests aufzuspüren, kostet viel Zeit, und deshalb vermeiden Ingenieure sie. Eine wiederholbare Fehlerwirkung ist viel einfacher zu debuggen als eine, die bei jedem zwanzigsten Durchlauf auftritt. Wenn das Symptom kaum auftaucht, zieht sich die Untersuchung in die Länge.
Zwei Ursachen dominieren. Die erste ist der gleichzeitige Zugriff auf gemeinsame, veränderbare Zustände. In Java zum Beispiel lebt ein statischer Wert in der gesamten JVM. Ein Test ändert ihn, ein späterer Test liest oder ändert ihn, und in dieser Reihenfolge ist alles in Ordnung. Wenn du die Tests nicht in der richtigen Reihenfolge durchführst, treten Fehlerwirkungen aus dem Nichts auf.
Der zweite Fall ist die Änderung eines gemeinsam genutzten Zustands, z. B. einer Datenbank. Wenn die erste Zeile deines Tests alles in einer Tabelle löscht, während ein anderer Test gerade dabei ist, dieselbe Tabelle zu ändern, ist das eine Katastrophe. Ein zuverlässiger Weg, um beide Muster zu erkennen: Der Test besteht isoliert, aber er schlägt fehl, wenn er zusammen mit anderen läuft. Das ist das Signal, dass du etwas gefunden hast.
Race Conditions sind am schwierigsten zu finden. Simon Stewart schrieb einmal eine benutzerdefinierte Implementierung der Zip-Komprimierung in Java, die erst bestand, dann zweimal fehlschlug und dann wieder bestand, wobei der Code solide aussah. Die Ursache war die Granularität der Zeitstempel. Das Zip-Format hält Zeitstempel auf zwei Sekunden genau, nicht auf eine. Wurde der Test bei einer geraden Sekunde gestartet, bestand er, bei einer ungeraden Sekunde schlug er fehl. Die Normalisierung der Zeitstempel machte den Test stabil, aber die Suche war brutal.
Wie man einen fehlerhaften Test behandelt, Schritt für Schritt
Der erste Schritt besteht darin, den Test aus der CI zu nehmen. Dein CI muss ein starkes Signal geben, was bedeutet, dass der flockige Test aus der Rotation kommen muss. Von da an sorgt eine klare Reihenfolge dafür, dass der Test nicht verloren geht.
Du hast zwei Möglichkeiten, ihn zu entfernen. Die einfachste ist eine Ignorieren-Anmerkung, die in den meisten Test-Frameworks verfügbar ist. Wenn du eine solche Anmerkung verwendest, verknüpfe sie mit einem Eintrag in deinem Bug-Tracker, damit der Test verfolgbar bleibt. Die Alternative ist eine Blockliste, die du im Projektarchiv oder anderswo aufbewahrst und in der du die Tests angibst, die bei einem bestimmten Durchlauf übersprungen werden sollen.
Das Selenium-Projekt verwendet eine Datei mit übersprungenen Tests, die in CI eingespeist wird. Eine einzige Datei, in der alle übersprungenen Tests gesammelt werden, ist besser als verstreute Anmerkungen, die schwer zu finden sind und leicht vergessen werden können. Ein einziger Ort, an dem du nachsehen kannst, bedeutet, dass du immer wieder zurückgehen kannst, um das zu finden, was du übersprungen hast.
| Schritt | Was du tust | Warum es wichtig ist |
|---|---|---|
| Entfernen | Nimm den Test per Kommentar oder Blockliste aus der CI heraus | Schützt die Stärke deines CI-Signals |
| Verfolgen | Mit einem Bug verlinken oder eine Datei mit übersprungenen Tests führen | Hält den Test sichtbar und wiederherstellbar |
| Own | Weisen Sie den Test einer bestimmten Person zu | Vermeiden Sie die Tragödie der Allmende |
| Auflösen | Debuggen, verkleinern, wiederholen oder löschen | Gibt ein vertrauenswürdiges Signal zurück oder beseitigt Ballast |
Die meisten Unternehmen hören auf, nachdem sie den Test entfernt haben. Der Build ist wieder stabil, also fühlen sie sich erledigt. Aber solange der Test nicht jemandem gehört, passiert nichts weiter.
Weisen Sie jeden fehlerhaften Test einer Person zu, nicht einem Team
Die Verantwortung für einen fehlerhaften Test liegt bei einer Person, niemals bei einem Team. Wenn du ihn einem Team übergibst, kommt es zur Tragödie der Allmende, bei der alle davon ausgehen, dass sich jemand anderes darum kümmert.
Die Verantwortung für einen Test bedeutet nicht, dass du die ganze Arbeit machst. Es bedeutet, dass du dafür verantwortlich bist, herauszufinden, wie er repariert wird. Vielleicht überträgt der Eigentümer die Aufgabe weiter. Vielleicht behandelt er ihn wie jeden anderen eingehenden Fehler. Aber eine bestimmte Person führt ihn weiter.
Sobald ein Test einen Besitzer hat, löst du ihn auf. Lasse ihn wiederholt laufen und versuche, die Ursache einzugrenzen. Finde heraus, warum der Fehler auftritt, und schreibe dann den kleinstmöglichen Test, der beweist, dass deine Lösung funktioniert. Wenn du das konsequent tust, entwickelst du die klassische Testpyramide: viele kleine Tests an der Basis, wenige große an der Spitze.
Einen Test zu löschen ist eine legitime Lösung
Du kannst den fehlerhaften Test auch löschen. Solange du die Versionskontrolle verwendest, ist das Löschen keine Zerstörung. Wenn sich der Test als wertvoll erweist, holst du ihn zurück.
Manche wehren sich dagegen, weil der Test “einen wichtigen Arbeitsablauf abdeckt” Das ist ein zweischneidiges Argument. Wenn ein Arbeitsablauf wirklich wichtig ist, lohnt es sich, Zeit in die Entwicklung zu investieren, um den Test solide zu machen. Wenn er diese Zeit nicht wert ist, lohnt es sich wahrscheinlich nicht, den Test zu behalten. Je wichtiger ein Test ist, desto höher ist die Priorität, ihn stabil zu machen.
Manche Teams setzen eine Frist für einen fehlerhaften Test. Wenn er nicht vor Ablauf der Frist behoben ist, wird er als toter Code gelöscht. Da er keinen Nutzen bringt und zu viel Gewicht hat, ist es in Ordnung, ihn zu entfernen.
Mehrschichtige Teststrategien geben dir einen weiteren Grund zum Löschen. Wenn frühere, kleinere Tests bereits das Risiko abdecken, das ein großer Test absichern sollte, wird der große Test überflüssig. Flakiness neigt dazu, in diesen großen Tests zu leben. Wenn du also einen Test entfernst, dessen Überdeckung an anderer Stelle besteht, ist das ein klarer Gewinn.
Wann Wiederholungen akzeptabel sind und wann sie zu viel kosten
Die Wiederholung eines Tests im selben Build ist ein akzeptabler Kompromiss, kein Ideal. Wenn ein Test eine fünfprozentige Chance auf eine Fehlerwirkung hat, sinkt die Wahrscheinlichkeit bei einem zweiten Durchlauf auf fünf Prozent von fünf Prozent. Die Wahrscheinlichkeit, dass ein Test falsch rot ist, sinkt mit jedem Wiederholungsversuch schnell.
Der Preis dafür ist die Bauzeit. Ein fehlerhafter Test ist in der Regel groß, weil er die meisten beweglichen Teile hat. Wenn du einen vierminütigen Test fünfmal wiederholst, sprengst du dein Zeitbudget für die Rückgabe der Ergebnisse.
Im Durchschnitt sind drei Wiederholungen gut, aber das solltest du an deinem eigenen Service Level Ziel messen. Wenn du CI-Ergebnisse innerhalb von zehn Minuten haben willst und ein durchschnittlicher Test drei Minuten dauert, kannst du ihn dreimal wiederholen, aber nicht viermal. Es ist ein Balanceakt zwischen Vertrauen und Rückmeldegeschwindigkeit.
Wie man fehlerhafte Tests stoppt, bevor sie in den Build gelangen
Gatekeeping verhindert, dass fehlerhafte Tests in den Build gelangen. Lass einen unzuverlässigen Test erst gar nicht in dein Build einfließen.
Meta führt dies in großem Umfang durch. Bevor ein Test in die KI einfließen darf, wird er über Nacht hundertmal gleichzeitig mit jedem anderen Test getestet. Nur wenn er absolut stabil bleibt, wird er in die echten CI-Läufe aufgenommen, und der gesamte Prozess ist automatisiert. Kein Mensch könnte diese Last tragen.
Du brauchst nicht die Ressourcen von Meta, um die Idee umzusetzen. Bevor du einen Test hinzufügst, führe ihn zehnmal lokal durch und achte auf Instabilität. Das bedeutet, dass du dir notieren musst, welche Tests bereits existieren, und dass triviale Änderungen wie das Umbenennen eines Tests einen erneuten Durchlauf auslösen. Das ist vertretbar, denn ein stabiler Test besteht die Prüfung, während ein fehlerhafter Test niemals hätte eingeführt werden dürfen.
Die zweite präventive Angewohnheit ist die regelmäßige Wiederholung bekannter fehlerhafter Tests. Manchmal wird eine systemische Ursache an anderer Stelle behoben und ein ganzer Stapel von Tests besteht wieder. Eine separate Datei, in der die fehlgeschlagenen Tests aufgelistet sind, macht dies einfach, denn eine Zeile aus der Liste zu entfernen, ist besser, als den Code zu durchforsten, um Anmerkungen zu entfernen. Sobald sich diese Tests als stabil erweisen, werden sie wieder in die allgemeinen CI-Läufe aufgenommen und liefern wieder ein vertrauenswürdiges Signal.
Wo hilft KI bei fehlerhaften Tests, und wo nicht?
KI kommt beim Debugging zum Einsatz, nicht beim Schreiben deiner Tests. Sie analysiert komplizierten Code gut und kann darauf hinweisen, dass ein Wert nie gesetzt wird oder dass ein Problem in einer bestimmten Zeile sitzt. Um die Ursache für eine Fehlerwirkung zu finden, ist das wirklich nützlich.
Das Risiko ist falsches Vertrauen.
Es ist wie bei einem Betrunkenen in einer Bar, der eine Meinung hat, die er mit Nachdruck vertritt und mit der er vielleicht falsch liegt. Simon Stewart
Behandle also jeden KI-Output so, als würde ein Mensch ihn reviewen. Ein Gegenmittel ist, ein zweites Modell zu bitten, die Diagnose des ersten Modells zu überprüfen. Wenn sich beide einig sind, dass eine bestimmte Zeile die Fehlerwirkung erklärt, hast du eine gute Ausgangsbasis.
KI ist gut darin, viele plausible Testfälle zu generieren, aber Simon Stewart zieht es vor, die Bedingungen selbst zu definieren, anstatt sie zu übergeben. Das Framing, das sich durchsetzt: KI ist ein Hilfsmittel für deine Arbeit, kein Ersatz für sie. Und das übergeordnete Ziel bleibt bestehen. Eine sichere Änderung so schnell wie möglich in die Hände der Nutzerinnen und Nutzer zu bringen, was bedeutet, je weniger Code du hast, desto besser.


