7. Teststrategien II
Teststrategien II
Abschnitt betitelt „Teststrategien II“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.
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 R["Red: einen fehlschlagenden<br/>Test schreiben"] --> G["Green: minimal<br/>zum Laufen bringen"] G --> F["Refactor: aufraeumen,<br/>Tests bleiben gruen"] 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.
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")}`;}Der Nutzen von TDD ist dreifach: Jede Zeile Code hat einen Grund (einen Test), das Ergebnis ist automatisch testbar aufgebaut, und man denkt 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 Schueler den Artikel bestelltThen erhaelt 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?”). Man bleibt in der inneren Schleife, bis der äußere Akzeptanztest grün wird.
flowchart LR
A["Akzeptanztest schreiben<br/>(rot, Nutzersicht)"] --> B["innere TDD-Schleife:<br/>Red-Green-Refactor"]
B --> C{"Akzeptanz-<br/>test gruen?"}
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.
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.
Browser oeffnen -> zur Buffet-Seite navigieren -> Artikel auswaehlen und "Bestellen" klicken -> pruefen: Abholnummer erscheintWeil 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.
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, schreibt man zuerst einen Test, der ihn reproduziert (rot), und behebt ihn dann (grün). So ist bewiesen, dass der Fehler wirklich weg ist, und er kann nicht unbemerkt zurückkehren.
flowchart LR A[Bug gemeldet] --> B[Test schreiben,<br/>der ihn zeigt: rot] B --> C[Fehler beheben: gruen] C --> D[Test bleibt fuer immer]
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.
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 |
Lernergebnisse: Was Sie nach diesem Kapitel können sollten
Abschnitt betitelt „Lernergebnisse: Was Sie nach diesem Kapitel können sollten“Nach Abschluss dieses Kapitels sollten Schülerinnen und Schüler in der Lage sein:
- Anwenden: ein Verhalten testgetrieben im Red-Green-Refactor-Zyklus entwickeln.
- Beurteilen: 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.
- Erklären: die Rolle von Pipeline, Coverage und dem “Bug zuerst als Test”-Vorgehen in der Qualitätssicherung erklären.
- Beurteilen: eine dokumentierte Teststrategie für ein Projekt entwickeln, die Ebene und Umfang je Systemteil aus dem Risiko begründet.
Passende Übungen
Abschnitt betitelt „Passende Übungen“- Aufgabe 10 - TDD-Kata
- Aufgabe 11 - Teststrategie fürs Jahresprojekt