Einführung in Test-Driven Development

Zielgruppe: alle. Dieser Text setzt keine Vorkenntnisse voraus und legt das Vokabular fest, auf dem die folgenden Seiten (für Entwickler, für Architekten) aufbauen.

Herkunft

Test-Driven Development (TDD) wurde von Kent Beck Ende der 1990er/Anfang der 2000er im Rahmen von Extreme Programming (XP) popularisiert (Test-Driven Development: By Example, 2002). Die Kernidee ist älter (die Praxis, Tests vor dem Code zu schreiben, findet sich schon in frühen NASA-Projekten), aber Beck hat sie in einen wiederholbaren, disziplinierten Zyklus gegossen, der bis heute Industriestandard in vielen Teams ist.

Der Zyklus: Red — Green — Refactor

    ┌───────────┐        ┌───────────┐        ┌───────────┐
    │    RED    │  --->  │   GREEN   │  --->  │ REFACTOR  │  --->  (zurueck zu RED)
    │ Test, der │        │ Minimaler │        │ Struktur  │
    │ (noch)    │        │ Code, der │        │ verbessern│
    │ fehlschl. │        │ gruen wird│        │ Tests     │
    │           │        │           │        │ bleiben   │
    │           │        │           │        │ gruen     │
    └───────────┘        └───────────┘        └───────────┘
  1. Red — Schreibe einen neuen, kleinen Test für ein Verhalten, das noch nicht existiert. Führe ihn aus und beobachte, dass er fehlschlägt (Compile-Fehler oder Assertion-Fehler). Das ist kein Zwischenschritt zum Überspringen: Ein Test, den du nie rot gesehen hast, könnte aus falschen Gründen grün sein (z. B. weil er gar nichts prüft).
  2. Green — Schreibe die minimale Produktionsänderung, die den Test grün macht. Nicht mehr. Kein "während ich schon dabei bin"-Code für spätere Anforderungen (YAGNI).
  3. Refactor — Jetzt, mit einem grünen Sicherheitsnetz, verbessere die Struktur (Duplikation entfernen, Namen klären, Methode extrahieren) ohne das Verhalten zu ändern. Tests bleiben die ganze Zeit grün. Wenn ein Test rot wird, ist das kein Refactoring mehr, sondern eine Verhaltensänderung — dann gehört sie in einen eigenen Red-Green-Schritt.

In diesem Projekt ist dieser Dreiklang nicht nur eine Empfehlung, sondern sichtbar in der Commit-Historie: Jeder Schritt ist sein eigener Commit, mit dem Präfix [RED], [GREEN] oder refactor:. Siehe Commit-Historie als Lernpfad, um das selbst nachzuvollziehen.

Warum vor dem Code testen (und nicht danach)?

Ein Test, der nach dem Code geschrieben wird, bestätigt tendenziell, was der Code bereits tut (inklusive seiner Bugs). Ein Test, der vor dem Code geschrieben wird, zwingt dazu, das gewünschte Verhalten aus Sicht eines Aufrufers zu formulieren, bevor eine Implementierung existiert, die diese Sicht verzerren könnte. Das hat zwei Effekte:

Testarten und der Unterschied zwischen "Unit" und "Isoliert"

Test-Doubles (Ersatzobjekte)

Ein häufiges Missverständnis ist, alle Ersatzobjekte "Mocks" zu nennen. Nach Gerard Meszaros (xUnit Test Patterns) unterscheidet man:

Test Double Verhalten Beispiel in diesem Projekt
Dummy Wird nur übergeben, nie benutzt (Signatur-Füller) ein Platzhalter-Parameter, der von einer Methode ignoriert wird
Stub Liefert fest einprogrammierte Antworten ein CreditScoringPort-Stub, der immer "APPROVED" liefert
Fake Echte, aber vereinfachte Implementierung ein In-Memory-AccountRepository statt einer echten Datenbank
Spy Zeichnet Aufrufe auf, zur späteren Prüfung Prüfen, dass save() genau einmal mit den erwarteten Werten aufgerufen wurde
Mock Wie Spy, aber mit vorab definierten Erwartungen, die selbst fehlschlagen können Mockito verify(repository).save(any())

In diesem Projekt kommt in den Application-/Use-Case-Tests überwiegend Mockito (Mock/Stub) für Ports zum Einsatz; in den Domain-Tests werden bewusst keine Test-Doubles gebraucht, weil dort mit echten Value Objects gearbeitet wird — ein Hinweis darauf, dass die Domain-Schicht selbst keine "schwierigen" Abhängigkeiten hat (siehe TDD für Architekten).

Was wird NICHT per TDD getestet?

TDD treibt Verhalten. Nicht alles im Repository ist Verhalten:

Weiterführend

⌂ Cockpit