20. Teststrategien II
Nach diesem Kapitel kannst du:
- ein Verhalten testgetrieben im Red-Green-Refactor-Zyklus entwickeln und einschätzen, wann TDD trägt und wann ein Prototyp der bessere Weg ist.
- ATDD und die doppelte Schleife (Akzeptanztest außen, Unit-TDD innen) erklären und begründen, warum ein ausführbarer Akzeptanztest beim agentic coding zum Steuerungswerkzeug wird.
- End-to-End-Tests für die zentralen Nutzerpfade (Flows) schreiben und die Auswahl der Pfade begründen.
- Ursachen für flaky Tests erkennen und mit “auf Bedingungen warten” beheben.
- eine dokumentierte Teststrategie für ein Projekt entwickeln, die Ebene und Umfang je Systemteil aus dem Risiko begründet und Pipeline, Coverage und “Bug zuerst als Test” einordnet.
Zurück ins Autowerk aus Kapitel 19. Dort ging es darum, was auf welcher Ebene geprüft wird: Bauteil auf dem Prüfstand, Baugruppe, Probefahrt. Jetzt geht es um das Wann und Wie. Bevor ein neues Bremsteil konstruiert wird, steht schon fest, welchen Wert es auf dem Prüfstand erreichen muss. Bevor ein neues Modell gebaut wird, steht fest, welche Abnahmefahrt es bestehen muss. Und die Prüfung ist fest in die Fertigungsstraße eingebaut: Ein Teil, das durchfällt, kommt nicht weiter zur Montage.
flowchart LR
accTitle: Im Autowerk steht die Prüfung vor dem Bau und ist ins Band eingebaut, so wie bei TDD, ATDD und der Pipeline
V["Prüfwert festlegen"] --> K["Bauteil konstruieren"]
K --> P{"Prüfstand bestanden?"}
P -- "nein" --> K
P -- "ja" --> M["weiter zur Montage"]
V -.->|im Code| T["Test zuerst: TDD, ATDD"]
P -.->|im Code| C["Pipeline blockiert den Merge"]
Das vorige Kapitel hat die Bausteine geliefert: Ebenen, Pyramide, Doubles, Testdaten. Dieses Kapitel bringt sie in Bewegung. Erst eine Arbeitsweise, bei der der Test vor dem Code entsteht, dann Tests für ganze Nutzerpfade, dann der Umgang mit Tests, die mal laufen und mal nicht - und schließlich die Zusammenführung zu einer dokumentierten Teststrategie für das Jahresprojekt.
Begriffe vorab
Abschnitt betitelt „Begriffe vorab“- TDD (Test-Driven Development, testgetriebene Entwicklung): Arbeitsweise, bei der zuerst ein fehlschlagender Test entsteht und dann der Code dazu.
- Red-Green-Refactor: der Dreitakt von TDD - Test rot, minimal grün machen, aufräumen.
- ATDD (Acceptance Test-Driven Development): derselbe Gedanke für ein ganzes Feature aus Nutzersicht, mit einem Akzeptanztest als Abnahmekriterium.
- Flow (Nutzerpfad): zusammenhängende Folge von Schritten, mit der ein Nutzer ein Ziel erreicht; der Happy Path ist der Flow, bei dem alles glattgeht.
- Auto-Waiting: ein Testwerkzeug wartet von selbst, bis ein Element bedienbar ist.
- Coverage (Testabdeckung): Anteil des Codes, den die Tests durchlaufen.
Testgetriebene Entwicklung
Abschnitt betitelt „Testgetriebene Entwicklung“Testgetriebene Entwicklung (TDD) dreht die übliche Reihenfolge um: Zuerst wird ein fehlschlagender Test geschrieben, dann gerade so viel Code, dass er läuft, dann wird aufgeräumt. Der Zyklus hat drei Takte:
flowchart LR accTitle: Der TDD-Zyklus Red, Green, Refactor wiederholt sich für jedes kleine Verhalten R["Red: einen fehlschlagenden<br/>Test schreiben"] --> G["Green: minimal<br/>zum Laufen bringen"] G --> F["Refactor: aufräumen,<br/>Tests bleiben grün"] F --> R
- Red: Ein Test für das nächste kleine Verhalten. Er schlägt fehl - er muss fehlschlagen, sonst prüft er nichts.
- Green: Der einfachste Code, der ihn grün macht. Bewusst der einfachste, auch wenn er noch unvollständig wirkt.
- Refactor: Struktur verbessern, Doppeltes entfernen - im Schutz der grünen Tests, die jede Verschlechterung sofort melden.
Genauer erklärt: Warum muss der Test zuerst rot sein? Ein Test, der auf Anhieb grün ist, beweist nicht, dass er etwas prüft. Vielleicht ruft er die falsche Funktion auf, vielleicht fehlt das expect, vielleicht war das Verhalten schon da. Siehst du ihn dagegen einmal rot und dann nach deiner Änderung grün, weißt du, dass genau diese Änderung ihn grün gemacht hat. Im Autowerk ist es dasselbe: Ein Prüfstand, der nie ein defektes Teil abgelehnt hat, ist noch nicht als Prüfstand bewiesen.
Ein Verhalten wächst so in kleinen Schritten. Das folgende Beispiel formatiert eine Dauer und ist Test für Test entstanden - jeder Test kam vor der Zeile, die ihn erfüllt:
describe("formatDuration (grown via red-green-refactor)", () => { it("formats zero", () => { expect(formatDuration(0)).toBe("0:00"); // 1st test }); it("pads the seconds", () => { expect(formatDuration(5)).toBe("0:05"); // forced the padStart }); it("rolls over into minutes", () => { expect(formatDuration(65)).toBe("1:05"); // forced the division }); it("rejects negative input", () => { expect(() => formatDuration(-1)).toThrow("must not be negative"); });});function formatDuration(totalSeconds: number): string { if (totalSeconds < 0) throw new Error("duration must not be negative"); const minutes = Math.floor(totalSeconds / 60); const seconds = totalSeconds % 60; return `${minutes}:${String(seconds).padStart(2, "0")}`;}Beispielprojekt: 20_teststrategien_2_examples/ex01_tdd_format_duration
Der Nutzen von TDD ist dreifach: Jede Zeile Code hat einen Grund (einen Test), das Ergebnis ist automatisch testbar aufgebaut, und du denkst vor dem Implementieren über das gewünschte Verhalten nach statt über die erstbeste Umsetzung.
Akzeptanztestgetriebene Entwicklung (ATDD)
Abschnitt betitelt „Akzeptanztestgetriebene Entwicklung (ATDD)“TDD arbeitet im Kleinen: eine Funktion, eine Regel. ATDD (Acceptance Test-Driven Development) hebt denselben Gedanken auf die Ebene eines ganzen Features aus Nutzersicht. Bevor eine Zeile Implementierung entsteht, wird ein Akzeptanztest formuliert, der beschreibt, woran man erkennt, dass das Feature fertig ist - in der Sprache des Nutzers, nicht der Technik. Dieser Test ist zunächst rot und ist zugleich das Abnahmekriterium: Ist er grün, gilt das Feature per Definition als fertig.
Der Akzeptanztest beschreibt meist einen kompletten Flow (siehe unten) und wird oft im Given-When-Then-Schema notiert, das sich direkt aus einer User Story ergibt:
Given ein Buffet mit einem verfügbaren ArtikelWhen der Schüler den Artikel bestelltThen erhält er eine AbholnummerAnd der Bestand des Artikels sinkt um einsIn der Praxis verschachtelt man ATDD und TDD zur doppelten Schleife: Die äußere Schleife ist der Akzeptanztest (“baue ich das richtige Feature?”), die innere sind die Unit-Tests im Red-Green-Refactor (“baue ich es richtig?”). Du bleibst in der inneren Schleife, bis der äußere Akzeptanztest grün wird.
flowchart LR
accTitle: Die doppelte Schleife mit dem Akzeptanztest außen und TDD innen
A["Akzeptanztest schreiben<br/>(rot, Nutzersicht)"] --> B["innere TDD-Schleife:<br/>Red-Green-Refactor"]
B --> C{"Akzeptanz-<br/>test grün?"}
C -- "nein" --> B
C -- "ja" --> D["Feature fertig"]
ATDD und Agentic Coding
Abschnitt betitelt „ATDD und Agentic Coding“Beim agentic coding - KI-Agenten, die weitgehend selbstständig programmieren - hat sich ATDD als Arbeitsweise durchgesetzt, und der Grund ist einleuchtend: Ein Akzeptanztest ist ein maschinell prüfbarer, eindeutiger Vertrag. Ein Agent braucht ein klares Ziel und eine verlässliche Rückmeldung, ob er es erreicht hat; genau das liefert ein ausführbarer Akzeptanztest. Der Mensch legt das Was fest (der Test als Spezifikation), der Agent iteriert selbstständig am Wie, bis der Test grün ist.
Damit verschiebt sich die Rolle des Tests: vom nachträglichen Kontrollwerkzeug zum vorausgehenden Steuerungswerkzeug. Ohne diesen Prüfstein müsste ein Mensch jedes Zwischenergebnis des Agenten von Hand bewerten; mit ihm hat der Agent eine Abbruchbedingung, an der er sich selbst messen kann - er weiß, wann er fertig ist. Wer künftig mit solchen Werkzeugen arbeitet, schreibt seltener selbst jede Zeile und häufiger den Akzeptanztest, der die Absicht präzise genug festhält, dass eine Maschine darauf hinarbeiten kann. Das gute, vorab formulierte Kriterium wird damit wichtiger, nicht unwichtiger.
Genauer erklärt: Was passiert, wenn das Kriterium unscharf ist? Lautet der Auftrag nur “die Bestellung soll funktionieren”, kann ein Agent (oder ein Mensch) eine Abholnummer anzeigen, ohne den Bestand zu verringern, und hält sich für fertig. Erst die Zeile “And der Bestand des Artikels sinkt um eins” macht diesen Fehler sichtbar: Der Akzeptanztest bleibt rot, bis auch das stimmt. Was nicht im Akzeptanztest steht, wird nicht geprüft und damit leicht vergessen.
Auflösung
(a) Wenig: Ein Test, der nie rot war, beweist nicht, dass er etwas prüft. Halte inne und schreibe den nächsten Test so, dass er fehlschlägt, etwa mit zwei Clips. Das zeigt, ob die Funktion wirklich summiert. (b) In der inneren Schleife: Es geht um eine einzelne Funktion (TDD), nicht um ein Feature aus Nutzersicht (ATDD).
End-to-End-Tests
Abschnitt betitelt „End-to-End-Tests“Unit- und Integrationstests prüfen Teile. Ein End-to-End-Test prüft das ganze System aus der Sicht eines Nutzers: Er steuert einen echten Browser, klickt, tippt und liest ab, was auf dem Bildschirm erscheint - vom ersten Klick bis zur Datenbank und zurück. Im Autowerk ist das die Probefahrt.
Browser öffnen -> zur Buffet-Seite navigieren -> Artikel auswählen und "Bestellen" klicken -> prüfen: Abholnummer erscheintsequenceDiagram accTitle: Ein End-to-End-Test steuert den Browser durch einen ganzen Flow bis zur Datenbank und prüft das sichtbare Ergebnis participant T as E2E-Test participant B as Browser participant S as Server participant D as Datenbank T->>B: Buffet-Seite öffnen T->>B: Artikel wählen, "Bestellen" klicken B->>S: Bestellung senden S->>D: Bestellung speichern, Bestand senken S-->>B: Abholnummer T->>B: prüfen: Abholnummer sichtbar
Weil E2E-Tests teuer und langsam sind (siehe Pyramide), testet man nicht alles auf dieser Ebene, sondern die wenigen wichtigsten Nutzerpfade (in der Werkzeugwelt meist Flows genannt) - die, deren Ausfall das Produkt wertlos machen würde. Für ein Bestellsystem etwa: eine Bestellung aufgeben, eine Bestellung als fertig markieren, eine Bestellung stornieren. Alles Weitere gehört auf die günstigeren Ebenen darunter.
Werkzeuge dafür (etwa Playwright) starten einen echten Browser und stellen Befehle bereit, um Elemente zu finden, mit ihnen zu interagieren und Erwartungen zu prüfen. Die Auswahl des Werkzeugs folgt demselben Kriterium wie zuvor: Es muss in der Pipeline automatisch laufen.
Flaky Tests
Abschnitt betitelt „Flaky Tests“Ein flaky Test schlägt mal fehl und läuft beim nächsten Versuch durch, ohne dass sich am Code etwas geändert hat. Solche Tests sind gefährlicher als fehlende: Sie untergraben das Vertrauen in die ganze Suite, und irgendwann drückt jemand bei jedem roten Test auf “nochmal”, auch bei den echten Fehlern.
Die Ursache ist fast immer Timing - und genau hier zahlt sich das Verständnis nebenläufiger Abläufe aus:
| Ursache | Gegenmittel |
|---|---|
| Test prüft, bevor eine asynchrone Aktion fertig ist | auf die Bedingung warten, nicht auf eine feste Zeit |
feste Wartezeiten (sleep(500)) | auf ein sichtbares Ergebnis warten statt auf die Uhr |
| gemeinsamer Zustand zwischen Tests | jeden Test mit frischem Zustand starten |
| echte Zeit oder Zufall | Uhr und Zufall im Test durch feste Werte ersetzen |
Die Grundregel lautet: Auf Bedingungen warten, nicht auf Zeit. Ein Test, der eine halbe Sekunde wartet und dann prüft, ist sowohl langsam als auch unzuverlässig - auf einem langsameren Rechner reicht die halbe Sekunde nicht, auf einem schnellen ist sie verschwendet. Werkzeuge wie Playwright lösen das mit Auto-Waiting: Ein Befehl wie “klicke auf diesen Button” wartet von selbst, bis der Button sichtbar und bedienbar ist, statt sofort zu scheitern oder blind eine feste Zeit verstreichen zu lassen - das eingebaute Gegenmittel gegen genau die erste Zeile der Tabelle oben.
flowchart TD
accTitle: Eine feste Wartezeit scheitert, wenn die Antwort länger braucht, das Warten auf eine Bedingung nicht
A["Klick auf Bestellen"] --> W1["feste Wartezeit: 500 ms"]
A --> W2["warten, bis die Abholnummer sichtbar ist"]
W1 --> Q{"Server schneller<br/>als 500 ms?"}
Q -- "ja" --> G1["grün"]
Q -- "nein" --> R1["rot: flaky"]
W2 --> G2["grün, sobald die Nummer da ist"]
Auflösung
(a) Der Rabattcode-Test prüft eine Regel (Groß-/Kleinschreibung), keinen ganzen Flow. Er gehört als Unit-Test auf die Fachlogik, dort ist er schneller und zeigt die Fehlerstelle direkt. (b) Die feste Wartezeit: Braucht der Server einmal länger als 500 ms, prüft der Test zu früh. Statt auf die Uhr wartet der Test auf die Bedingung, etwa darauf, dass das erwartete Element sichtbar ist; Playwright macht das mit Auto-Waiting von selbst.
Qualitätssicherung im Prozess
Abschnitt betitelt „Qualitätssicherung im Prozess“Tests entfalten ihren Wert erst, wenn sie automatisch laufen und Fehler blockieren.
- Tests in der Pipeline. Bei jedem Push läuft die Suite; ein roter Test verhindert den Merge. Erst diese Blockade macht Tests verbindlich - eine Suite, die man umgehen kann, wird umgangen.
- Coverage als Indikator, nicht als Ziel. Die Testabdeckung (welcher Anteil des Codes von Tests durchlaufen wird) zeigt, wo blinde Flecken sind. Sie zum Ziel zu machen ist ein Fehler: 100 Prozent Abdeckung mit belanglosen Tests sichern nichts, und man kann jede Zahl erreichen, ohne je ein Ergebnis zu prüfen. Coverage weist auf Ungetestetes hin; ob dort ein Test nötig ist, bleibt eine Entscheidung.
- Bug zuerst als Test. Wird ein Fehler gefunden, schreibst du zuerst einen Test, der ihn reproduziert (rot), und behebst ihn dann (grün). So ist bewiesen, dass der Fehler wirklich weg ist, und er kann nicht unbemerkt zurückkehren.
flowchart LR accTitle: Ein gemeldeter Bug wird zuerst als roter Test festgehalten, dann behoben, und der Test bleibt A[Bug gemeldet] --> B[Test schreiben,<br/>der ihn zeigt: rot] B --> C[Fehler beheben: grün] C --> D[Test bleibt für immer]
Genauer erklärt: Wie erreicht man Coverage ohne Sicherheit? Ein Test ruft formatDuration(65) auf, prüft das Ergebnis aber nicht. Jede Zeile der Funktion wird durchlaufen, die Abdeckung steigt - und würde die Funktion “1:5” statt “1:05” liefern, bliebe der Test trotzdem grün. Coverage zählt, welcher Code läuft, nicht, welches Ergebnis geprüft wird. Deshalb taugt sie als Suchhilfe für ungetestete Stellen, aber nicht als Gütesiegel.
Die dokumentierte Teststrategie
Abschnitt betitelt „Die dokumentierte Teststrategie“Am Ende steht das eigentliche Ziel dieses Themenblocks: eine Teststrategie für das Jahresprojekt, die nicht im Kopf, sondern auf Papier steht. Sie beantwortet für jeden wichtigen Teil des Systems dieselben vier Fragen:
-
Welches Risiko? Was wäre schlimm, wenn dieser Teil versagt? (Falsche Abbuchung ist schlimmer als ein schiefes Layout.)
-
Welche Ebene? Unit, Integration oder E2E - passend zum Risiko und nach der Pyramide.
-
Welches Werkzeug? Begründet aus dem Framework-Umfeld gewählt.
-
Welcher Umfang? Wie viel Test rechtfertigt das Risiko? Nicht jeder Teil verdient dieselbe Sorgfalt.
flowchart LR accTitle: Die vier Fragen der Teststrategie führen vom Risiko über Ebene und Werkzeug zum Umfang R["1. Welches Risiko?"] --> E["2. Welche Ebene?"] E --> W["3. Welches Werkzeug?"] W --> U["4. Welcher Umfang?"] U --> Z["eine Zeile je Systemteil<br/>in der Strategie-Tabelle"]
Eine solche Strategie ist bewusst kurz - eine Tabelle mit einer Zeile je Systemteil genügt. Ihr Wert liegt darin, dass sie eine Entscheidung festhält: Hier testen wir gründlich, weil das Risiko hoch ist; dort testen wir wenig, weil ein Fehler billig zu beheben wäre. Ohne diese Entscheidung testet man entweder alles gleich (teuer) oder nach Gefühl (lückenhaft).
| Systemteil | Risiko | Ebene | Umfang |
|---|---|---|---|
| Bestell-Fachlogik | hoch (Geld, Mengen) | Unit | alle Regeln und Randfälle |
| API-Endpunkte | mittel (Vertrag) | Integration/API | Lebenszyklus und Fehlerfälle |
| Kernnutzerpfad | hoch (Produkt wertlos ohne) | E2E | ein bis drei Pfade |
| Layout, Darstellung | gering | keine automatischen | manuelle Sichtprüfung |
Auflösung
Coverage sagt nichts über das Risiko (Ist eine falsche Abbuchung abgedeckt oder nur das Layout?) und nichts über die Ebene (Ist der Kernnutzerpfad überhaupt als Flow getestet?). Sie sagt auch nicht, ob die durchlaufenen Zeilen überhaupt geprüft werden. Die Zahl kann eine Strategie ergänzen, aber keine Entscheidung ersetzen.
Zusatzmaterialien
Abschnitt betitelt „Zusatzmaterialien“- Kent Beck, Test-Driven Development: By Example (Buch) - die Praxis hinter Red-Green-Refactor mit durchgerechneten Beispielen; lohnt sich vor der Kata in Aufgabe 14, um den Rhythmus kleiner Schritte an einem längeren Beispiel zu sehen.
- Martin Fowler, Given When Then - kurz und präzise; hilft, Akzeptanztests aus Nutzersicht statt aus Technik-Sicht zu formulieren.
- Playwright-Dokumentation - End-to-End-Tests mit Auto-Waiting; die Einstiegsseiten zeigen Installation und den ersten Test für Aufgabe 14.
- Martin Fowler, Eradicating Non-Determinism in Tests - vertiefende Ursachenanalyse zu flaky Tests, über die Tabelle oben hinaus.
- Martin Fowler, Test Coverage - die Begründung, warum Coverage ein Hinweis und kein Ziel ist; nützlich für den Prozessteil der Strategie in Aufgabe 14.
Zum Anhören
Abschnitt betitelt „Zum Anhören“Du kannst dir diesen Text mit der Vorlesefunktion deines Browsers oder Betriebssystems kostenlos anhören, etwa mit “Laut vorlesen” in Microsoft Edge. Das eignet sich gut zum Wiederholen vor der Schularbeit.
Lernergebnisse: Was du nach diesem Kapitel können solltest
Abschnitt betitelt „Lernergebnisse: Was du nach diesem Kapitel können solltest“- Anwenden: ein Verhalten testgetrieben im Red-Green-Refactor-Zyklus entwickeln und einschätzen, wann TDD trägt und wann ein Prototyp der bessere Weg ist.
- Erklären: ATDD und die doppelte Schleife (Akzeptanztest außen, Unit-TDD innen) erklären und begründen, warum ein ausführbarer Akzeptanztest beim agentic coding zum Steuerungswerkzeug wird.
- Anwenden: End-to-End-Tests für die zentralen Nutzerpfade (Flows) schreiben und die Auswahl der Pfade begründen.
- Analysieren: Ursachen für flaky Tests erkennen und mit “auf Bedingungen warten” beheben.
- Beurteilen: eine dokumentierte Teststrategie für ein Projekt entwickeln, die Ebene und Umfang je Systemteil aus dem Risiko begründet und Pipeline, Coverage und “Bug zuerst als Test” einordnet.
Passende Übungen
Abschnitt betitelt „Passende Übungen“- Aufgabe 14 - Jahresprojekt Sprint 4 - TDD, End-to-End und Teststrategie: Kata:
cleanFileNamestreng im Red-Green-Refactor mit Commit-Präfixen (Wesentlich_überwiegend);docs/teststrategie.mddes Projekts und ein stabiler End-to-End-Test des wichtigsten Flows (Wesentlich_vollständig). PlusPlus: Fehler-Flow, Pipeline und „Bug zuerst als Test“; MandalorianPlus: eine Story in der doppelten Schleife (ATDD).
Autoren
Abschnitt betitelt „Autoren“GAM
CC-BY-SA