CommunitySchreiben & Editierengithub.com

Flaky Test Detective

Ein Claude Agent Skill, das die Grundursache von flaky (nicht-deterministischen) Tests findet und sie deterministisch umschreibt. Unterstützt pytest · Jest · RSpec · Go · JUnit.

Was ist Flaky Test Detective?

Das „Flaky Test Detective“-Skill befähigt Claude, flaky Tests zu identifizieren, ihre nicht-deterministischen Ursachen zu klassifizieren und sie in deterministische, zuverlässige Tests umzuwandeln. Es deckt die gängigsten Ursachen für Flakiness ab – wie Testreihenfolge-Abhängigkeit, Zeit- und Zeitzonenprobleme, ungeseetete Zufälligkeit, asynchrone Race Conditions, echte Netzwerkaufrufe und ungeordnete Sammlungs-Assertions – und bietet für jede ein strukturiertes Vorgehen. Das Skill unterstützt die gängigsten Test-Frameworks (pytest, Jest/Vitest, RSpec/Minitest, Go testing, JUnit, PHPUnit) und enthält Referenzdateien für tiefgehende Ursachenanalyse, frameworkspezifische Mechaniken (z.B. Zeit einfrieren, Zufallsgenerator seeden), Reproduktionstechniken und deterministische Testmuster. Entwickler erhalten einen klaren Workflow: Kontext etablieren, Nicht-Determinismus reproduzieren, Ursache klassifizieren (Flaky / Latent / Deterministic), beheben und mit einer standardisierten Vorlage berichten. Das Ziel ist immer ein Test, der seine Eingaben kontrolliert – nicht einer, der „meistens grün“ ist.

Funktioniert mit✓Claude Code~Codex CLI~Cursor
npx skills add windchillscalanthes-ship-it/flaky-test-detective

Installed? Explore more Schreiben & Editieren skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Was macht Flaky Test Detective?

Finde den Test, der beim gleichen Code mal bestanden und mal nicht bestanden wird – und schreibe ihn deterministisch um, bevor er dein Team daran gewöhnt, rote Builds zu ignorieren.

Ein flaky Test ist schlimmer als gar kein Test. Er schlägt einmal fehl, jemand startet CI neu, er wird grün, und alle lernen, dass Rot nicht kaputt bedeutet. Bald segelt eine echte Regression hinter einem „Mach einfach nochmal“ durch. Die Ursache liegt fast nie im getesteten Code – es ist eine versteckte Abhängigkeit von etwas, das der Test nicht kontrolliert: die Uhr, der Zufallsgenerator, die Reihenfolge, in der Tests laufen, eine gemeinsam genutzte Zeile in der Datenbank, ein echter Netzwerkaufruf, die Iterationsreihenfolge einer Map. Dieses Skill findet diese versteckte Eingabe, erklärt, warum sie den Test nicht-deterministisch macht, und schreibt den Test so um, dass er sie kontrolliert.

Wann man dies verwendet

  • Ein Test schlägt sporadisch fehl – rot in einem Durchlauf, grün im nächsten, ohne Codeänderung.
  • Ein Test ist lokal grün, aber in CI rot (oder umgekehrt), oder schlägt nur unter Parallelisierung oder einer bestimmten Reihenfolge fehl.
  • Jemand sagt: „Mach einfach nochmal“, „Der ist flaky“ oder „Beim nächsten Mal wird er grün“.
  • Du schreibst oder reviewst einen Test und möchtest Nicht-Determinismus erkennen, bevor er committed wird.
  • Du möchtest eine Suite härten – die latente Flakiness finden, bevor sie ausbricht.

Wenn ein Test jedes Mal fehlschlägt, ist er nicht flaky – es ist ein echter Fehler oder ein Bug; korrigiere den Code, stabilisiere nicht den Test. Und „repariere“ Flakiness niemals, indem du einen Retry oder einen sleep hinzufügst, der sie versteckt – das begräbt das Signal. Das Ziel ist ein Test, der deterministisch ist, nicht einer, der meistens grün ist.

Workflow

  1. Kontext herstellen. Identifiziere die Sprache / das Framework / den Runner (pytest, Jest/Vitest, RSpec/Minitest, Go testing, JUnit, PHPUnit, …) und wo er flaky ist – lokal, in CI, nur parallel, nur in einer bestimmten Reihenfolge, nur zu einer bestimmten Tageszeit. Wo und wann er flaky ist, ist der größte Hinweis auf die Ursache.

  2. Den Nicht-Determinismus reproduzieren. Ein flaky Test, den du nicht auf Kommando zum Fehlschlagen bringen kannst, ist nicht repariert, nur ruhiggestellt. Erzwinge ihn: Führe ihn viele Male aus, führe ihn in zufälliger Reihenfolge aus, führe ihn isoliert vs. mit der gesamten Suite aus, binde ihn an einen Worker oder verschiebe die Uhr/Zeitzone. reference/detection.md enthält die genauen Befehle pro Framework.

  3. Die Grundursache klassifizieren anhand des untenstehenden Katalogs und einen Schweregrad zuweisen:

    • 🔴 Flaky – eine konkrete nicht-deterministische Eingabe erreicht eine Assertion; sie wird irgendwann fehlschlagen. Muss deterministisch gemacht werden.
    • 🟡 Latent – prinzipiell nicht-deterministisch, aber derzeit maskiert (ein Seed, der zufällig stabil ist, eine Reihenfolge, die zufällig hält, eine schnell genuge CI). Nenne die Bedingung, die ihn brechen würde.
    • 🟢 Deterministisch – der Test kontrolliert seine Eingaben. Keine Änderung.
  4. Die Grundursache beheben – nicht übertünchen. Gib für jedes 🔴/🟡 an:

    • warum es nicht-deterministisch ist – die genaue unkontrollierte Eingabe und wie sie die Assertion erreicht,
    • die deterministische Umschreibung als ausführbaren Code (framework-idiomatisch) und
    • das Ergebnis (z. B. „reihenfolgeabhängig → isoliert“ oder „flaky 1 von 20 → grün 1000ד).
  5. Berichten mit der Ausgabevorlage am Ende. Führe mit den 🔴-Ergebnissen an.

Lade für sprach- und ursachenspezifische Tiefe die Referenzdatei, die für den vor dir liegenden Test relevant ist:

  • reference/root-causes.md – die acht Grundursachen-Familien im Detail: wie jede einen Test flaky macht, wie man sie erkennt und die kanonische Lösung.
  • reference/frameworks.md – frameworkspezifische Mechaniken: Kontrolle der Uhr, Seeds des RNGs, Randomisieren/Festlegen der Testreihenfolge, Isolieren von Zuständen und Mocken des Netzwerks in pytest, Jest/Vitest, RSpec/Minitest, Go, JUnit und PHPUnit.
  • reference/detection.md – wie man Flakiness auf Kommando reproduziert: Wiederholungsschleifen, Reihenfolgen-Randomisierung, Isolation/Bisektion, Parallelität und Lesen von CI-Signalen.
  • reference/patterns.md – die Prinzipien deterministischer Tests: Hermetizität, Kontrolle von Uhr/RNG/Netzwerk/Reihenfolge, Isolation pro Test und der Erkennen→Reparieren→Quarantäne-Lebenszyklus.

Lies die Referenz für das vor dir liegende Framework, anstatt zu raten – die Mechaniken unterscheiden sich (Zeit einfrieren, Seed setzen und Reihenfolge steuern funktionieren in pytest völlig anders als in Jest oder Go).

Grundursachen-Katalog (die häufigsten Übeltäter)

„Unkontrollierte Eingabe“ = alles, wovon das Testergebnis abhängt, das der Test nicht fixiert. Flakiness ist immer eine unkontrollierte Eingabe, die eine Assertion erreicht.

#GrundursacheWarum nicht-deterministischDeterministische Lösung
1Test-Reihenfolgeabhängigkeit / gemeinsamer veränderlicher ZustandEin Test verlässt sich auf den Zustand, den ein anderer hinterlassen hat (ein Global, ein Klassenattribut, ein Modul-Singleton, eine nicht zurückgerollte Zeile). Bestanden in einer Reihenfolge, fehlgeschlagen in einer anderen.Isolieren: frische Fixtures pro Test, Globals im Teardown zurücksetzen, jeden Test in einen Transaktions-Rollback einwickeln. Reihenfolge in CI randomisieren, um es zu finden.
2Zeit & Zeitzonenow()/Date.now()/today() im Test oder Code – schlägt fehl um Mitternacht, Monats-/Jahresgrenzen, unter Sommerzeit oder in der Zeitzone von CI.Uhr einfrieren (freezegun/time-machine, Jest Fake Timers, timecop, Go injizierbare Uhr); TZ setzen; niemals gegen die echte Wanduhr asserten.
3Unseeded Zufälligkeitrandom(), uuid4(), faker, gemischte Daten – ein seltener Wert löst einmal in N Durchläufen eine Assertion aus.RNG deterministisch pro Test seeden; IDs/Fakes injizieren; auf Eigenschaften asserten, nicht auf einen bestimmten Zufallswert.
4Asynchrone Race Conditions / sleep-basierte Wartezeitensleep(100), dann asserten – funktioniert, bis CI langsamer ist; nicht abgewartete Promises; Callbacks, die assertiert werden, bevor sie laufen; Ereignisreihenfolge.Auf die Bedingung warten, nicht auf eine Dauer (waitFor/await des tatsächlichen Signals); jedes Promise awaiten; Fake Timers verwenden, um die Planung zu steuern.
5Echtes Netzwerk / externe DiensteEin echter HTTP/API/DNS-Aufruf – schlägt fehl auf Latenz, Ratenbegrenzung, Ausfallzeiten oder geänderte Daten.An der Grenze mocken (responses/respx, nock/msw, VCR/WireMock); niemals ein echtes Netzwerk in einem Unit-Test treffen.
6Ungeordnete Sammlungs-AssertionsAuf eine bestimmte Reihenfolge aus einem set/dict/map asserten, eine DB-Abfrage ohne ORDER BY, os.listdir oder gleichzeitige Ergebnisse.Vor dem Vergleich sortieren, auf Mengengleichheit/Enthaltensein asserten oder ein explizites ORDER BY hinzufügen. Nicht von zufälliger Reihenfolge abhängig machen.
7Durchgesickerter globaler Zustand / RessourcenleckUmgebungsvariablen, Singletons, Modul-Caches, das aktuelle Arbeitsverzeichnis, offene Verbindungen/Dateien/Ports, die mutiert und nicht wiederhergestellt wurden – verschmutzt spätere Tests.Um jeden Test herum speichern/wiederherstellen (Fixtures/beforeEach/t.Cleanup); jede Ressource schließen; Monkeypatching mit automatischem Rückgängigmachen.
8Zu knappe Timeouts / LeistungsannahmenEin hartcodiertes Timeout oder assert elapsed < X, das auf einem schnellen Laptop hält, aber nicht auf einem ausgelasteten CI-Runner.Leistungsassertionen zur Wanduhr aus Korrektheitstests entfernen; wenn ein Timeout nötig ist, großzügig und bedingungsbasiert machen, keine knappe Konstante.
9Gleitkomma-Genauigkeitsgleichheitassert result == 0.3 nach Arithmetik – Rundung macht es gelegentlich ungleich.Mit einer Toleranz asserten (pytest.approx, toBeCloseTo, Epsilon-Vergleich).
10Locale / UmgebungsannahmenZahlen-/Datums-/Zeichenkettenformatierung, Groß-/Kleinschreibung, Pfadtrennzeichen oder Dateisystem-Groß-/Kleinschreibung, die sich zwischen Maschinen/CI unterscheiden.Locale/Kodierung im Test festlegen; locale-unabhängige Formatierung verwenden; nicht auf betriebssystemspezifische Pfade asserten.
11Gemeinsame Fixtures / DB nicht isoliertTests teilen sich eine Datenbank, einen Cache oder ein temporäres Verzeichnis und treten sich unter Parallelisierung gegenseitig.Pro-Test-Transaktionen/Rollback, eindeutige Schemas/Namespaces pro Worker, tmp_path pro Test; kein gemeinsam schreibbares Fixture.
12Versteckte Nebenläufigkeit im getesteten CodeHintergrund-Threads, Timer oder asynchrone Aufgaben, die unvorhersehbar relativ zur Assertion enden (oder nicht).Die Grenze awaitbar/injizierbar machen; die Warteschlange deterministisch leeren; einen steuerbaren Executor/Clock injizieren.

Die 🟡-Stufe ist, wo Flakiness lauert, bevor sie ausbricht: ein Test, der reihenfolgeabhängig ist, aber zufällig als letzter läuft, ein Seed, der derzeit stabil ist, ein sleep, der derzeit lang genug ist. Nenne die Bedingung, die ihn zu 🔴 macht – das ist die Reparatur, die darauf wartet, passiert zu werden.

Leitprinzipien

  • Flakiness ist eine unkontrollierte Eingabe, kein Pech. Jeder flaky Test hängt von etwas ab, das er nicht fixiert: der Uhr, dem Seed, der Reihenfolge, dem Netzwerk, einer gemeinsamen Zeile. Finde diese Eingabe und kontrolliere sie – friere sie ein, seede sie, isoliere sie, mocke sie.
  • Reproduziere, bevor du reparierst. Wenn du ihn nicht auf Kommando zum Fehlschlagen bringen kannst (Wiederholungsschleife, zufällige Reihenfolge, Isolation), hast du die Ursache nicht gefunden – du hast sie versteckt.
  • Ein Test muss seinen eigenen Zustand besitzen. Er sollte alleine, in jeder Reihenfolge, parallel, tausendmal bestanden werden. Setup erstellt, was er braucht; Teardown stellt wieder her, was er berührt hat.
  • Kontrolliere die vier üblichen Verdächtigen. Uhr, Zufälligkeit, Netzwerk und Reihenfolge sind die Quelle der meisten Flakiness – mache jede standardmäßig deterministisch.
  • Warte auf Bedingungen, niemals auf Zeit. sleep(n) ist ein Race, das du auf einem langsamen Runner verlierst. Warte auf das tatsächliche Signal oder prüfe die Bedingung mit einer großzügigen Obergrenze.
  • Repariere Flakiness niemals mit einem Retry. Retries und pauschale sleeps verwandeln einen sichtbaren Fehler in einen stillen und zerstören die Glaubwürdigkeit der Suite.
  • Quarantäne, nicht löschen. Wenn ein flaky Test nicht sofort repariert werden kann, markiere ihn (skip/quarantine) mit einem Tracking-Issue, damit er nicht blockiert – aber behalte ihn und behalte das Signal, dass er kaputt ist.
  • Richtig dimensionieren. Ein wirklich reihenfolgeunabhängiger, hermetischer Test braucht nichts davon – zwinge kein Deterministischkeits-Theater einem Test auf, der bereits seine Eingaben kontrolliert.

Ausgabevorlage

## Flaky Test Review

**Stack:** <Sprache / Framework / Runner>  ·  **Flakes:** <nur CI / parallel / zufällige Reihenfolge / zu Zeit X>  ·  **Geprüfte Tests:** <n>

### 🔴 <Datei::Testname> — FLAKY
**Grundursache:** <Reihenfolgeabhängigkeit / Zeit / Zufälligkeit / async Race / Netzwerk / Ordnung / globaler Zustand / Timeout / …>
**Warum nicht-deterministisch:** <die genaue unkontrollierte Eingabe und wie sie die Assertion erreicht>
**Reproduzieren:** <der Befehl, der ihn auf Kommando zum Fehlschlagen bringt – zufällige Reihenfolge, Wiederholungsschleife, Isolation, Uhr verschieben>
**Deterministische Umschreibung:**
<ausführbarer, framework-idiomatischer Code>
**Ergebnis:** <z. B. „reihenfolgeabhängig → isoliert; grün in 1000 Durchläufen mit zufälliger Reihenfolge“>

### 🟡 <Datei::Test> — LATENT
**Grundursache / Warum derzeit maskiert / Die Bedingung, die ihn zu flaky machen würde / Empfehlung**

### 🟢 <Datei::Test> — DETERMINISTISCH
<eine Zeile: er kontrolliert seine Eingaben – keine Änderung>

---
**Zusammenfassung:** <n flaky, n latent.> <die schlimmste unkontrollierte Eingabe in einer Zeile.> <Fazit: stabil zum Mergen, oder zuerst Test X reparieren.>

Gib immer die deterministische Umschreibung und den Befehl zum Reproduzieren an – ein Review, das sagt „das ist flaky“, ohne die kontrollierte-Eingabe-Lösung und eine Möglichkeit, es zu beweisen, gibt den schwierigen Teil einfach zurück. Die unkontrollierte Eingabe zu benennen ist die Expertise, die das Skill liefert.

Verwandte Skills