Testgetriebene Entwicklung
Philosophie
Kernprinzip: Tests sollten das Verhalten über öffentliche Schnittstellen prüfen, nicht über Implementierungsdetails. Code kann sich vollständig ändern; Tests sollten das nicht.
Gute Tests sind integrativ: Sie üben reale Codepfade über öffentliche APIs aus. Sie beschreiben was das System tut, nicht wie es das tut. Ein guter Test liest sich wie eine Spezifikation – „Benutzer kann mit gültigem Warenkorb auschecken“ sagt Ihnen genau, welche Fähigkeit existiert. Diese Tests überleben Refactorings, da sie sich nicht um die interne Struktur kümmern.
Schlechte Tests sind an die Implementierung gekoppelt. Sie mocken interne Abhängigkeiten, testen private Methoden oder prüfen über externe Mittel (z. B. direkte Datenbankabfragen statt über die Schnittstelle). Warnsignal: Ihr Test bricht beim Refactoring, obwohl sich das Verhalten nicht geändert hat. Wenn Sie eine interne Funktion umbenennen und Tests fehlschlagen, haben diese Tests die Implementierung und nicht das Verhalten getestet.
Siehe tests.md für Beispiele und mocking.md für Richtlinien zum Mocking.
Anti-Pattern: Horizontale Slices
Schreiben Sie NICHT alle Tests zuerst, dann die gesamte Implementierung. Dies ist „horizontales Slicen“ – die Annahme, ROT bedeute „alle Tests schreiben“ und GRÜN bedeute „allen Code schreiben.“
Dies erzeugt schlechte Tests:
- In großer Menge geschriebene Tests testen vorgestelltes Verhalten, nicht tatsächliches Verhalten
- Sie testen am Ende die Form der Dinge (Datenstrukturen, Funktionssignaturen) statt des benutzerseitigen Verhaltens
- Tests werden unempfindlich gegenüber echten Änderungen – sie bestehen, wenn das Verhalten bricht, und schlagen fehl, wenn das Verhalten in Ordnung ist
- Sie überholen Ihre eigenen Erkenntnisse und verpflichten sich zu einer Teststruktur, bevor Sie die Implementierung verstehen
Richtiger Ansatz: Vertikale Slices mittels Tracer Bullets. Ein Test → eine Implementierung → Wiederholung. Jeder Test reagiert auf das, was Sie aus dem vorherigen Zyklus gelernt haben. Da Sie den Code gerade erst geschrieben haben, wissen Sie genau, welches Verhalten wichtig ist und wie Sie es prüfen können.
FALSCH (horizontal):
ROT: test1, test2, test3, test4, test5
GRÜN: impl1, impl2, impl3, impl4, impl5
RICHTIG (vertikal):
ROT→GRÜN: test1→impl1
ROT→GRÜN: test2→impl2
ROT→GRÜN: test3→impl3
...
Arbeitsablauf
1. Planung
Bevor Sie Code schreiben:
- Mit dem Benutzer abstimmen, welche Schnittstellenänderungen nötig sind
- Mit dem Benutzer abstimmen, welche Verhaltensweisen getestet werden sollen (priorisieren)
- Chancen für tiefe Module identifizieren (kleine Schnittstelle, tiefe Implementierung)
- Schnittstellen für Testbarkeit entwerfen
- Die zu testenden Verhaltensweisen auflisten (nicht Implementierungsschritte)
- Zustimmung des Benutzers zum Plan einholen
Fragen Sie: „Wie sollte die öffentliche Schnittstelle aussehen? Welche Verhaltensweisen sind am wichtigsten zu testen?“
Sie können nicht alles testen. Stimmen Sie genau mit dem Benutzer ab, welche Verhaltensweisen am meisten zählen. Konzentrieren Sie die Testarbeit auf kritische Pfade und komplexe Logik, nicht auf jeden möglichen Grenzfall.
2. Tracer Bullet
Schreiben Sie EINEN Test, der EINE Sache über das System bestätigt:
ROT: Test für erstes Verhalten schreiben → Test schlägt fehl
GRÜN: Minimalen Code schreiben, um zu bestehen → Test besteht
Dies ist Ihr Tracer Bullet – beweist, dass der Pfad End-to-End funktioniert.
3. Inkrementelle Schleife
Für jedes verbleibende Verhalten:
ROT: Nächsten Test schreiben → schlägt fehl
GRÜN: Minimaler Code, um zu bestehen → besteht
Regeln:
- Ein Test nach dem anderen
- Nur genug Code, um den aktuellen Test zu bestehen
- Zukünftige Tests nicht vorwegnehmen
- Tests auf beobachtbares Verhalten konzentrieren
4. Refactoring
Nachdem alle Tests bestanden sind, nach Refactoring-Kandidaten suchen:
- Duplikate extrahieren
- Module vertiefen (Komplexität hinter einfache Schnittstellen verlagern)
- SOLID-Prinzipien anwenden, wo natürlich
- Überlegen, was neuer Code über bestehenden Code verrät
- Tests nach jedem Refactoring-Schritt ausführen
Nie im ROT-Zustand refactorn. Zuerst zu GRÜN gelangen.
Checkliste pro Zyklus
[ ] Test beschreibt Verhalten, nicht Implementierung
[ ] Test verwendet nur öffentliche Schnittstelle
[ ] Test würde internes Refactoring überleben
[ ] Code ist minimal für diesen Test
[ ] Keine spekulativen Funktionen hinzugefügt