Der grüne Test, der nichts beweist — warum eine Test-Suite den Fehler bestätigte, den sie finden sollte

Ein Fehler auf der Website des DI²-Projekts galt vier Wochen lang als behoben. Beim Wechsel der Sprache sollte die Seite an derselben Stelle stehen bleiben, an der der Leser gerade war. Drei Fix-Commits hatten das nacheinander versprochen, jeder mit einer eigenen Diagnose, und nach jedem war die automatisierte Test-Suite grün. Dann führte der Maintainer zum ersten Mal einen manuellen Testfall dafür aus. Auf jedem Desktop-Bildschirm war der Fehler noch da.

Die automatisierte Test-Suite hatte nicht gelogen. Geschrieben hatte sie ein Coding-Agent, Claude Code mit dem Modell Claude Fable 5. Die Suite prüfte genau das, was dieser Agent für die Wirklichkeit hielt, und der Agent hatte sich geirrt. Der automatisierte Test hielt einen Wert fest, den es im echten Browser an dieser Stelle nie gab. Er bestätigte das Verhalten unter genau der Annahme, die im echten Browser falsch war.

Dieser Artikel erzählt diesen Fall und zwei weitere aus demselben Projekt und derselben Woche. Sie führen auf drei verschiedenen Wegen zum selben Ergebnis, und jeder bekommt einen eigenen Artikel dieses Blogs mit allen Einzelheiten. Hier stehen sie nebeneinander, damit der gemeinsame Mechanismus sichtbar wird. Die Tests, Stubs und Testdaten in allen drei Fällen stammen von Claude Code, in den Modellen Claude Fable 5 und Claude Opus 5. Die falschen Annahmen, um die es hier geht, steckten in genau diesen Artefakten. Der Maintainer hat die manuellen Testfälle angestoßen und ausgeführt.

Das Wichtigste vorab:

  • Ein grüner automatisierter Test mit Platzhaltern zeigt, dass sich der Code gegenüber diesen Platzhaltern so verhält, wie der Test es erwartet: Ob die Platzhalter die Wirklichkeit treffen, kann er nicht prüfen. Spielen sie ihm etwas Falsches vor, ist er grün und das Programm trotzdem kaputt.
  • Drei Wege führen einen automatisierten Test am Fehler vorbei: Der Stub, der Platzhalter für Browser oder Server, spielt eine Umgebung vor, die es so nicht gibt. Der richtige Baustein wird geprüft, aber nicht an der Stelle, an der er aufgerufen wird. Oder Test und Gegenprobe teilen sich dieselben falschen Testdaten.
  • Die Gegenprobe gegen den alten Stand kostet Minuten und hat eine Grenze: Ein neuer automatisierter Test wird einmal gegen den fehlerhaften Stand laufen gelassen. Ist er dort rot, reagiert er auf den Code. Ob er die Wirklichkeit trifft, sagt das nicht.
  • Gefunden hat alle drei Fälle ein Mensch am Bildschirm: zweimal mit einem aufgeschriebenen manuellen Testfall, einmal mit einem einzelnen Klick, der einen Guard auslöste, eine Sperre im Code, die immer zuschlug — die damalige Suite hatte in keinem der drei Fälle etwas gemeldet.

Voraussetzung: Keine. Die Beispiele stammen aus einer React-Anwendung, die mit Vitest getestet wird. Die Prinzipien gelten für jede Testumgebung, in der Teile der Wirklichkeit durch Platzhalter ersetzt werden. Die Begriffe Stub, Testdaten und Gegenprobe werden beim ersten Auftreten erklärt. Zwei Wörter hält der Text auseinander: Test meint eine automatisierte Prüfung, die grün oder rot endet. Testfall meint einen aufgeschriebenen Ablauf mit Erwartungen, den ein Mensch am Bildschirm ausführt.

Inhalt

Der Fall: dreimal behoben, Tests grün, Fehler trotzdem da

Die Website des DI²-Projekts gibt es auf Deutsch und auf Englisch. Wer auf der Startseite ein Stück nach unten gescrollt hat und dann die Sprache wechselt, soll auf der Zielseite ungefähr an derselben Stelle landen und nicht wieder ganz oben. Das war die Anforderung, und sie klingt nach einem Nachmittag Arbeit.

Drei Fixes folgten. Der erste, Ende Juli, merkte sich den Abschnitts-Anker, den der Leser zuletzt angesprungen hatte. Der zweite, noch am selben Vormittag, merkte sich stattdessen die Scroll-Position des Fensters. Der dritte, vier Wochen später, behob ein reales Nebenproblem, das nur in der Entwicklungsumgebung auftrat, weil der Strict Mode von React Effekte dort doppelt ausführt. Jeder der drei Commits nannte eine Ursache, jeder wurde als erledigt verbucht.

Am 28. August 2026 wurde für diesen Fehler zum ersten Mal ein manueller Testfall aufgeschrieben und ausgeführt: Startseite öffnen, einen Abschnitt anspringen, die Sprache wechseln, die Position betrachten. Der Lauf schlug fehl: Die Position wurde beim Sprachwechsel nicht berücksichtigt, genau wie am ersten Tag.

Die Ursache stand in keiner der drei Diagnosen. Auf der Website scrollt ab einer Fensterbreite von 768 Pixeln nicht das Browserfenster, sondern ein innerer Container, weil das Layout das Scrollen des Seitenkörpers abstellt. Ab dieser Breite, also auf jedem Desktop, war die Scroll-Position des Browserfensters konstant null. Gemerkt wurde immer null, und das Zurücksetzen bewegte ein Element, das gar nicht scrollt. Unterhalb der 768 Pixel scrollt das Fenster ganz normal. Dort hat der Fix funktioniert, und das hat die Fehldiagnosen genährt: Wer in einem schmalen Fenster oder auf dem Telefon nachsah, sah ein funktionierendes Feature.

Weder der Maintainer noch der Agent hatten den Fehler beim Ausprobieren im Browser bemerkt, dreimal nicht. Das ist die eine Hälfte der Geschichte. Die andere Hälfte ist die automatisierte Test-Suite, die in diesen vier Wochen bei jedem Lauf grün war.

Erster Weg: Ein Stub mit falschem Vertrag

Die Funktion, die sich die Scroll-Position merkt, hatte einen automatisierten Test in Vitest. Er lief in der Node-Umgebung ohne nachgebildeten Browser, und dort gibt es kein Fenster und keine Scroll-Position. Also bekommt der Test ein Ersatz-Objekt, einen sogenannten Stub, der die Rolle des Fensters spielt. Der Test gab diesem Stub eine Scroll-Position von 842 Pixeln und dazu einen Ersatz für den Sitzungsspeicher des Browsers, der sich Werte nur für die Dauer des Tests merkt. Dann rief er die Funktion auf und sah nach, ob der gerundete Wert 843 in diesem Speicher gelandet war.

  1: it("merkt die gerundete Scroll-Position", () => {
  2:   const sessionStorage = fakeSessionStorage()
  3:   vi.stubGlobal("window", { scrollY: 842.6, sessionStorage })
  4:
  5:   rememberScroll()
  6:   expect(sessionStorage.getItem("lang-scroll")).toBe("843")
  7: })

Der Test war in seiner Umgebung korrekt. Die Funktion las die Scroll-Position des Fensters, rundete sie und legte sie ab. Dass sie im echten Browser auf einem Desktop immer null las, konnte der Test nicht wissen: Der Stub hatte dem Fenster eine Scroll-Position gegeben, die es dort nie hat. Der Test bestätigte das Verhalten unter der Annahme, dass das Fenster scrollt, und genau diese Annahme war auf dem Desktop falsch.

Man kann das dem Stub nicht vorwerfen. Der Fehler liegt eine Stufe früher: Niemand hatte geprüft, ob das, was der Stub vorspielt, mit dem übereinstimmt, was der Browser tatsächlich tut. Ein Stub ist eine aufgeschriebene Annahme über die Umgebung, der Vertrag, den der Code mit ihr zu haben glaubt: Das Fenster scrollt, und seine Position lässt sich lesen. Annahmen, die man nicht als solche erkennt, prüft man nicht.

Der vierte Fix gab dem inneren Container eine feste Kennung, las die Position zuerst dort und fiel nur dann auf das Fenster zurück, wenn der Container nicht scrollt. Der neue Test stubt beides: ein Fenster, das auf null steht, und einen Container, der auf 640 Pixeln steht.

  1: it("liest die Position aus dem Scroll-Container, wenn dieser scrollt", () => {
  2:   const sessionStorage = fakeSessionStorage()
  3:   vi.stubGlobal("window", { scrollY: 0, sessionStorage }) // Fenster steht (Desktop)
  4:   stubScrollHost({ scrollTop: 640 })
  5:
  6:   expect(readScrollTop()).toBe(640)
  7:   rememberScroll()
  8:   expect(sessionStorage.getItem("lang-scroll")).toBe("640")
  9: })

Dieser Test ist besser, weil sein Stub die Situation nachbildet, in der der Fehler lebt: ein stehendes Fenster und ein scrollender Container. Auf dem alten Code schlägt er mit dem beobachteten Symptom fehl, erwartet 640, gelesen 0. Die vier Anläufe im Einzelnen und ein zweiter Fall derselben Klasse zwei Tage später haben einen eigenen Artikel.

Zweiter Weg: Die Funktion getestet, die Aufrufstelle nicht

Der zweite Fall liegt einen Tag später, im Dialog, in dem die Prüfregeln einer Tabelle angelegt werden. Sein Feld für die Spalte bietet auch Spalten an, die im Projekt noch nicht ausgewählt sind, mit der Markierung „NEU“. Wählt der Nutzer eine solche Spalte, soll die Anwendung sie sofort im Projekt auswählen, ein Schreibvorgang auf dem Server. Das tat sie nicht, wenn die Spalte schon einmal ausgewählt und wieder abgewählt worden war. Der Dialog entschied nicht am Zustand der Spalte, sondern an einem internen Wert: Schon einmal erfasste Spalten tragen eine Kennung, alle anderen ihren Namen. Eine abgewählte Spalte behält ihre Kennung, der Dialog hielt sie deshalb für ausgewählt und ließ den Aufruf weg, und der Server lehnte die Regel ab.

Der Fix zog die Entscheidung in eine reine Funktion, die nur den Zustand der Spalte ansieht, mit zwölf automatisierten Tests. Alle zwölf waren grün, die gesamte Suite mit 773 Tests ebenso. Der Prüfdurchgang danach fragte, was ein grüner Lauf nicht beantwortet: Würden die zwölf Tests den Fehler melden, wenn er zurückkäme? Der Prüf-Agent setzte im Dialog vorübergehend die alte Entscheidung wieder ein. Alle zwölf Tests blieben grün, denn sie rufen die Funktion direkt auf, und die antwortet weiterhin richtig. Ob der Dialog sie überhaupt benutzt, sehen sie nicht. Rot wurde erst ein neuer Test, der den Dialog rendert, eine abgewählte Spalte anklickt und prüft, ob der Aufruf abgesetzt wird.

Hier hat kein Fehler vier Wochen überlebt, die Lücke fand die bewusste Frage nach dem alten Stand. Der Fall gehört trotzdem in diese Reihe, weil er einen zweiten Weg zeigt: Ein vollständig getesteter Baustein sagt nichts darüber, ob er verbaut ist. Im Projekt ist daraus eine eigene Fehlerklasse geworden. Fünf spätere Test-Suiten nennen diesen Fall als Vorbild dafür, die Aufrufstelle eigens abzusichern, Stand 16. September 2026. Prüfdurchgang, Tests und Bilder aus der Anwendung haben einen eigenen Artikel.

Dritter Weg: Falsche Testdaten in Test und Gegenprobe

Der dritte Fall liegt am selben Nachmittag wie der zweite und ist der unbequemste, weil er die Gegenmaßnahme trifft, die aus dem ersten Fall entstanden war: Der Test enthielt eine Server-Antwort, die der echte Server nie liefert, und die Gegenprobe lief gegen dieselbe Antwort.

Derselbe Dialog bekam eine neue Eigenschaft: Wer dort eine Spalte mit der Markierung „NEU“ wählt und den Dialog dann verwirft, soll die Spalte wieder abgewählt bekommen. Weil das Abwählen abhängige Prüfregeln, Fremdschlüssel und zusammengehörige Schlüssel-Spalten mit zurücksetzen würde, fragt der Dialog vorher beim Server, was an der Spalte hängt, und lässt sie stehen, wenn die Antwort etwas enthält. Die Antwort enthält eine Liste von Spalten-Kennungen. Der Agent las sie als Liste nur der anderen Spalten, die mit zurückgesetzt würden. Tatsächlich enthält sie alle betroffenen Spalten, also immer auch die angefragte selbst. Die Bedingung „Liste nicht leer, also blockieren“, im Code ein sogenannter Guard, war damit immer wahr: Der Rollback lief nie, der Hinweis „daran hängen inzwischen Regeln“ erschien bei jedem Verwerfen.

Die Komponenten-Tests waren grün, Typprüfung und Linter sauber. Und die Gegenprobe war es auch: Der Agent hatte den Rollback ausgehängt, die Tests dafür wurden rot, der Rollback kam zurück, die Tests wurden wieder grün. Nach der Regel aus dem ersten Fall war damit alles getan. Die Ursache steckte in den Testdaten, also in der vorbereiteten Server-Antwort, die der Test dem Dialog unterschiebt. Sie lieferte eine leere Liste, die der echte Server für eine angefragte Spalte nie liefert. Test und Gegenprobe teilten denselben Irrtum. Gefunden hat es der erste echte Klick: Der Maintainer führte den manuellen Testfall für das Verwerfen aus, sah die Spalte stehen bleiben und den Hinweis erscheinen, und meldete beides. Die Testdaten spiegeln seitdem die angefragte Kennung zurück, und gegen diese Fassung wird der alte Guard rot.

  1: // Testdaten vorher: eine Antwort, die der Server auf diese Anfrage nie gibt
  2: impact: { cascadeColumnIds: [], rules: [], fks: [] }
  3:
  4: // Testdaten nachher: die angefragte Spalte ist immer enthalten
  5: impact: { cascadeColumnIds: [Number(input.columnMetadataId)], rules: [], fks: [] }

Ein Umstand macht diesen Fall zu mehr als einer Anekdote: Der Satz „Ein Test, der die eigene Annahme stubt, bestätigt nur sich selbst“ stand seit dem Nachmittag des Vortags in der Commit-Nachricht zum Scroll-Fix, geschrieben von einer Claude-Code-Sitzung mit Claude Opus 5. Gut 24 Stunden später machte eine andere Sitzung im selben Projekt, diesmal mit Claude Fable 5, denselben Fehler in anderer Form, obwohl Regel und Vertrag im Repository standen. Wer Testdaten schreibt, schreibt seine Annahme über den Server auf. Und wer eine Annahme nicht als Annahme erkennt, prüft sie nicht, egal wie viele Regeln er kennt. Die Entstehung des Rollbacks, der Kommentar in den Testdaten und zwei weitere Fälle derselben Klasse haben einen eigenen Artikel.

Die Gegenprobe: jeden Regressions-Test einmal gegen den alten Stand

Aus dem ersten Fall ist eine Arbeitsweise entstanden, die sich im Projekt an den Commits ablesen lässt. In den sechs Wochen vor dem 28. August 2026 erwähnte keiner von gut hundert Fix-Commits eine Gegenprobe gegen den alten Stand, in den zwei Wochen danach fast jeder zweite. Die Arbeitsweise geht so: Ein neuer automatisierter Test, der einen behobenen Fehler absichern soll, wird einmal gegen den alten, fehlerhaften Stand laufen gelassen. Dafür wird der Fix vorübergehend zurückgebaut oder ausgehängt, die Tests laufen, und sie müssen dort mit dem beobachteten Symptom fehlschlagen. Erst dann ist gezeigt, dass sie auf diesen Fehler reagieren, und erst dann kommt der Fix zurück.

Die Kosten sind gering: ein temporäres Zurückbauen und ein Testlauf, in einem Projekt dieser Größe wenige Minuten. Das gilt, solange sich der alte Stand mit wenig Aufwand wiederherstellen lässt. Steckt der Fix in einem größeren Umbau oder tritt der Fehler nur unter Last auf, wird die Gegenprobe aufwendiger, aber nicht überflüssig. Der Nutzen ist die Antwort auf die Frage, die ein grüner Lauf allein nicht beantwortet: Hätte dieser Test den Fehler gefunden? Im ersten Fall lautete sie für den alten Test nein und für den neuen ja, im zweiten für zwölf Tests nein und für einen ja.

Für jemanden, der den Code nicht lesen kann, ist diese Regel besonders wertvoll. Sie liefert ein Ergebnis, das ohne Sprachkenntnis zu beurteilen ist: zwei Läufe, einer rot, einer grün. Der Maintainer des DI²-Projekts kennt weder TypeScript noch die Testbibliothek. Er kann die Gegenprobe trotzdem verlangen und ihr Ergebnis abnehmen. Genau so ist sie in das Projekt gekommen.

Der dritte Fall zeigt die Grenze. Die Gegenprobe beweist, dass ein Test auf den Code reagiert. Ob die Testdaten die Wirklichkeit treffen, kann sie nicht sagen, denn sie läuft mit denselben Testdaten. Die Ergänzung kostet kaum mehr: Die Testdaten werden einmal mit dem abgeglichen, was das System tatsächlich liefert. Quellen dafür sind die Dokumentation der Schnittstelle, der Server-Code oder eine echte Antwort aus der laufenden Anwendung. Festgehalten wird die Eigenschaft, auf die sich der Test verlässt, nicht die ganze Antwort. Im dritten Fall hätte dafür ein Satz gereicht: Die Liste enthält immer die angefragte Kennung. Ein Kommentar an den Testdaten altert allerdings mit dem System. Wer die Eigenschaft dauerhaft absichern will, schreibt zusätzlich einen Integrationstest, der genau diese Eigenschaft gegen die laufende Anwendung prüft.

Zwei Prüfungen, beide klein, beide mit eindeutigem Ergebnis:

  • Gegen den alten Stand: Ist der neue Test dort rot? Wenn nicht, prüft er nicht, was er zu prüfen vorgibt.
  • Gegen den echten Vertrag: Verhält sich das System so, wie die Testdaten es behaupten? Wenn nicht, prüfen Test und Gegenprobe gemeinsam die falsche Welt.

Was das für den manuellen Katalog heißt

Die drei Fälle sind kein Plädoyer gegen automatisierte Tests. Die Suite des Projekts ist innerhalb dieser zwei Tage von 761 auf 804 Tests gewachsen, und die Regressions-Tests aus allen drei Fällen sind Teil davon. Sie werden die drei Fehler beim nächsten Mal in Sekunden melden, ohne dass jemand klicken muss.

Was die drei Fälle zeigen, ist etwas anderes. In allen dreien war die einzige Prüfung, die in einem definierten Zustand am Bildschirm nachgesehen hat, ein Mensch mit einem manuellen Testfall. Der Testfall für den Sprachwechsel bestand aus sieben Schritten mit je einer Erwartung und verlangte einen Viewport breiter als 1024 Pixel. Sein erster Lauf hat einen Fix widerlegt, der seit vier Wochen als erledigt galt.

Was aus dieser Erfahrung im DI²-Projekt als Arbeitsweise geworden ist, beschreibt der Artikel zur Kontroll-Schleife. Wie ein manueller Testfall aufgebaut ist, woher die Fälle eines Katalogs kommen und was ein Lauf beweist, ist Thema eines eigenen Artikels dieses Blogs. Und dass die erste Ausführung eines Testfalls zuerst den Testfall selbst prüft, bevor sie die Anwendung prüft, ist ein zweiter.

Arbeits-Prompts als Beispiel

Die Sitzungen, in denen die drei Fälle bearbeitet wurden, sind nicht versioniert. Die zwei Prompts hier sind aus den Bug-Dateien und Commit-Nachrichten rekonstruiert und keine Zitate. Sie zeigen, was der Maintainer verlangt hat, nicht wie der Agent es umgesetzt hat.

Nach dem vierten Fix des Scroll-Fehlers ging es darum, den neuen Tests zu trauen, ohne sie lesen zu können:

Bau den Fix vorübergehend zurück und lass die drei neuen Tests laufen.
Abnahme: Sie sind rot, und die Meldung zeigt das Symptom aus dem Testfall
(gemerkt wird 0 statt der echten Position). Danach den Fix wieder rein und
beide Läufe im Commit dokumentieren.

Der Agent hat beide Läufe gefahren und die Meldung des roten Laufs in die Commit-Nachricht übernommen. Diese Meldung ist der Nachweis, nicht der grüne Lauf danach.

Nach dem Fehlschlag des manuellen Testfalls im dritten Fall ging es darum, den Widerspruch zwischen grüner Suite und rotem Klick aufzuklären, statt nur den Guard zu reparieren:

Der manuelle Testfall für das Verwerfen ist fehlgeschlagen: Die Spalte bleibt
ausgewählt, und der Hinweis erscheint bei jedem Durchlauf. Die Tests und
die Gegenprobe waren grün. Klär zuerst, warum sie das nicht gemeldet haben,
dann korrigiere den Guard. Abnahme: Die Testdaten bilden ab, was der Server
wirklich liefert, der alte Guard wird gegen diese Testdaten rot, und der Grund
steht als Kommentar an den Testdaten.

Der Agent fand die Ursache im Vertrag der Antwort, zog die Testdaten auf das Verhalten des Servers und ließ den alten Guard dagegen laufen: zwei Tests rot. Der Kommentar, der seitdem an den Testdaten steht, nennt die Eigenschaft, die der erste Wurf übersehen hatte: Die Liste enthält beim echten Server immer die angefragte Spalte selbst.

Beide Prompts formulieren, was geprüft werden soll und woran das Ergebnis abgenommen wird. Wie der Agent das umsetzt, mit welchen Bibliotheks-Funktionen und in welcher Datei, bleibt ihm überlassen. Der Maintainer könnte es nicht vorgeben.

Was am Ende trägt

Die drei Fälle unterscheiden sich technisch und haben dieselbe Ursache: Jede Prüfung war innerhalb ihrer eigenen Annahmen stimmig, und niemand hatte die Annahmen selbst geprüft.

WegDer Test prüfteWas fehlteGefunden durch
Stubden Code gegen ein Fenster, das so nie scrolltder Abgleich des Stubs mit dem echten Browsereinen manuellen Testfall
Aufrufstelledie Funktion, nicht ihren Aufruf im Dialogein Test dort, wo der Fehler lebtdie Frage nach dem alten Stand
Testdatenden Code gegen eine Antwort, die der Server nie gibtder Abgleich der Testdaten mit dem echten Servereinen manuellen Testfall

Fünf Fragen, die ein grüner Lauf nicht beantwortet und die deshalb jemand stellen muss:

  • Trifft der Platzhalter die Wirklichkeit? Was der Stub vorspielt, muss mit dem übereinstimmen, was Browser oder Server tatsächlich tun.
  • Ist der Baustein auch verbaut? Ein Test an der Aufrufstelle prüft, ob die getestete Funktion überhaupt aufgerufen wird.
  • Liefert das System, was die Testdaten behaupten? Einmal gegen Dokumentation, Server-Code oder eine echte Antwort halten und die Eigenschaft festhalten, auf die sich der Test verlässt.
  • Wird der Test auf dem alten Stand rot? Wenn nicht, prüft er nicht, was er zu prüfen vorgibt. Die Gegenprobe kostet Minuten und ist ohne Sprachkenntnis abzunehmen.
  • Gibt es eine Prüfung, die die Annahmen des Tests nicht teilt? In allen drei Fällen war das ein Mensch mit einem manuellen Testfall. Das ersetzt keine Suite, es prüft, was die Suite nicht sehen kann.

FAQ

Warum sind die automatisierten Tests grün, obwohl der Fehler noch da ist?

Weil ein automatisierter Test nur prüfen kann, was seine Umgebung ihm vorspielt. Ersetzt er Browser, Server oder Datenbank durch einen Platzhalter, prüft er das Verhalten des Codes gegenüber diesem Platzhalter. Spielt der Platzhalter etwas vor, das es in Wirklichkeit nicht gibt, ist der Test grün und der Fehler bleibt. Die Frage lautet dann nicht „stimmt der Test?“, sondern „stimmt die Annahme im Platzhalter?“.

Sind Stubs und Mocks das Problem?

Nein. In einer Testumgebung ohne Browser muss irgendetwas seine Rolle übernehmen, und in einem Komponenten-Test will man keinen echten Server. Der Fehler liegt nicht im Ersetzen, sondern im fehlenden Abgleich: Niemand hat geprüft, ob der Ersatz das tut, was das echte System tut. Dieser Abgleich ist ein Blick in die Dokumentation der Schnittstelle oder ein Aufruf gegen das laufende System, verglichen mit dem, was die Testdaten behaupten.

Wie prüft man, ob ein Regressions-Test den Fehler überhaupt finden würde?

Indem man ihn einmal gegen den fehlerhaften Stand laufen lässt. Der Fix wird vorübergehend zurückgebaut, der Test läuft und muss rot sein, dann kommt der Fix zurück. Das Ergebnis sind zwei Läufe, einer rot, einer grün, und beide sind ohne Kenntnis der Sprache zu beurteilen. Bleibt der Test auf dem alten Stand grün, prüft er nicht das, was er zu prüfen vorgibt.

Reicht die Gegenprobe gegen den alten Stand?

Nicht gegen einen Irrtum in den Testdaten. Die Gegenprobe läuft mit denselben Testdaten wie der Test. Sind die Testdaten falsch, sind beide falsch, und die Gegenprobe meldet trotzdem rot und grün wie erwartet. Sie beweist, dass der Test auf den Code reagiert. Ob die Testdaten die Wirklichkeit treffen, prüft nur ein Abgleich mit dem echten Vertrag des Systems.

Wie prüft man KI-generierte Tests auf falsche Annahmen?

Die Fehlerklasse ist älter als jeder Coding-Agent. Ein Stub, der die eigene Annahme festhält, ist ein klassischer Fehler aus Handarbeit. Im KI-Kontext fehlt aber die zweite Perspektive: Dieselbe Instanz schreibt Fix, Test, Stub und Testdaten und trägt ihre Annahme in alle vier. Wo kein zweiter Entwickler die Testdaten hinterfragt, müssen Prüfungen, die die Annahme nicht teilen, seine Rolle übernehmen: einmal gegen den alten Stand, einmal gegen den echten Vertrag.

Verwandte Artikel

Übergeordnet:

Kontrolle und Diagnose:

  • Die Kontroll-Schleife — wie die Gegenprobe gegen den alten Stand Teil einer wiederkehrenden Kontrolle wurde, wenn man den Code nicht lesen kann.
  • Der Agent misst, wo der Mensch klickt — die Gegenrichtung: warum ein roter Testlauf im geteilten Arbeitsbaum zunächst nur eine Hypothese ist.