Test zuerst, dann Code

Was ist testgetriebene Entwicklung (TDD)?

TDD, Test-Driven Development

Testgetriebene Entwicklung, kurz TDD für Test-Driven Development, ist eine Arbeitsweise in der Softwareentwicklung, bei der du zu jeder neuen Funktion zuerst einen automatisierten Test schreibst, der fehlschlägt, und erst danach den Code, der ihn bestehen lässt. Der Ablauf wiederholt sich in kurzen Zyklen, die jeweils nur wenige Minuten dauern.

Kaum ändert jemand eine Zeile, bricht an anderer Stelle etwas weg. Tests zuerst zu schreiben ist die Antwort auf die Frage, wie du das früh genug merkst.

KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Zyklus
Rot, Grün, Refactoring
Taktlänge
Wenige Minuten pro Runde
Verwechselt mit
Nachträglich geschriebenen Unit-Tests
Braucht
Fachlogik, die ohne Infrastruktur aufrufbar ist

Der Zyklus aus Rot, Grün und Refactoring

Ein Durchlauf besteht aus drei Schritten, benannt nach der Farbe, die das Testwerkzeug anzeigt. Die Methode geht auf Kent Beck zurück, der sie Ende der 1990er-Jahre im Umfeld von Extreme Programming bekannt machte und Anfang der 2000er in seinem Buch „Test-Driven Development: By Example“ beschrieb.

  1. Rot

    Du schreibst einen Test für ein Verhalten, das es noch nicht gibt, und lässt ihn laufen. Er muss fehlschlagen, sonst prüft er etwas, das der Code ohnehin schon konnte.

  2. Grün

    Es entsteht genau so viel Produktivcode, dass der Test besteht, auch wenn die Lösung zunächst plump aussieht. Alle bisherigen Tests müssen dabei grün bleiben.

  3. Refactoring

    Du räumst auf, ziehst Duplikate zusammen und benennst um, ohne das Verhalten zu ändern. Nach jeder Änderung läuft die Testsuite erneut.

Die Schrittweite bestimmst du selbst. Bei einem vertrauten Problem darf ein Zyklus mehrere Zeilen umfassen, bei einer unklaren Anforderung schrumpft er auf einen einzigen Sonderfall, etwa auf eine leere Liste oder ein Datum am Monatsende. Wenn du länger als ein paar Minuten mit rotem Balken arbeitest, war der Schritt zu groß. Dann lohnt es sich, den letzten Stand zu verwerfen und kleiner neu anzusetzen.

Ein Beispiel in Python

Angenommen, ein Onlineshop soll ab 50 Euro Warenwert versandkostenfrei liefern, darunter kostet der Versand 4,90 Euro. Testgetrieben beginnst du nicht mit der Funktion, sondern mit zwei Tests, die mit pytest laufen. Beide schlagen zunächst fehl, weil es die Funktion noch nicht gibt.

# test_versand.py, Schritt Rot
from versand import versandkosten

def test_unter_grenze_kostet_versand():
    assert versandkosten(49.99) == 4.90

def test_ab_grenze_versandkostenfrei():
    assert versandkosten(50.00) == 0

# versand.py, Schritt Grün
def versandkosten(warenwert):
    return 0 if warenwert >= 50 else 4.90

Interessant ist der zweite Test: Er zwingt dich, die Grenze genau festzulegen. Gilt „ab 50 Euro“ einschließlich 50,00 Euro? Diese Frage stellt sich beim Schreiben des Tests, also bevor jemand sie im Betrieb an einer Reklamation entdeckt. Im Refactoring-Schritt würdest du die Zahlen 50 und 4,90 als benannte Konstanten herausziehen, und die Tests bestätigen, dass sich am Verhalten nichts geändert hat.

Was die Reihenfolge im Alltag bringt

Der eigentliche Gewinn liegt weniger im Netz aus Tests als im Entwurfsdruck. Wer den Test zuerst schreibt, ruft die Funktion von außen auf, bevor sie existiert, und merkt sofort, wenn eine Klasse fünf Abhängigkeiten mitschleppt oder eine Signatur unhandlich ist. Code, der schwer zu testen ist, fällt so am ersten Tag auf und nicht erst im Review.

Der zweite Effekt zeigt sich beim Ändern. Eine Suite, die in Sekunden durchläuft, macht Refactoring risikoarm, weil du nach jedem Schritt weißt, ob noch alles hält. Deshalb ist die Laufzeit kein Nebenaspekt: Sobald ein Durchlauf mehrere Minuten braucht, startet ihn niemand mehr ständig, und der Zyklus fällt in sich zusammen.

Fehler, die den Nutzen wieder auffressen

Am häufigsten begegnet dir der Test, der nie rot war. Entsteht er nach dem Code oder wird er nach einem Fehlschlag sofort zurechtgebogen, ohne dass jemand ihn hat scheitern sehen, kann er dauerhaft grün bleiben, obwohl er nichts prüft. Der rote Lauf gehört deshalb zum Verfahren und ist keine Zugabe.

Ebenso verbreitet ist der Test, der die Implementierung nachzeichnet statt das Verhalten. Prüft er, dass eine bestimmte Methode eines Mocks in einer bestimmten Reihenfolge aufgerufen wurde, bricht er bei jedem harmlosen Umbau. Solche Tests verhindern genau das, wofür der Zyklus gedacht war. Halte dich lieber an das Ergebnis, das nach außen sichtbar ist.

KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Nicht verwechseln

Testgetriebene Entwicklung und was oft damit gleichgesetzt wird

Testgetriebene Entwicklung gegen Unit-Test

Ein Unit-Test ist ein Werkzeug, testgetriebene Entwicklung eine Reihenfolge. Unit-Tests kannst du auch nachträglich schreiben, dann fehlt aber der Entwurfsdruck, weil die Struktur des Codes bereits feststeht.

Testgetriebene Entwicklung gegen Behaviour Driven Development

BDD hebt denselben Zyklus auf die fachliche Ebene und formuliert die Beispiele in einer Sprache, die Fachabteilung und Entwicklung gemeinsam lesen können, oft im Format Gegeben, Wenn, Dann. Der technische Ablauf bleibt gleich, die Beispiele sind gröber und beschreiben Verhalten statt einzelner Funktionen.

Testgetriebene Entwicklung gegen Testautomatisierung

Testautomatisierung besagt nur, dass Tests ohne Handarbeit laufen, unabhängig davon, wann sie entstanden sind. Ein Oberflächentest, der nachts über die fertige Anwendung läuft, ist automatisiert, hat mit TDD aber nichts zu tun.

KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
In der Praxis

Der Nutzen entsteht erst durch die Reihenfolge, nicht durch die Tests

In vielen Teams heißt testgetriebene Entwicklung in Wirklichkeit: Funktion fertigstellen, danach Tests nachziehen, damit die Abdeckung stimmt. Solche Tests bestätigen nur, was der Code ohnehin tut, samt der Fehler darin. Der eigentliche Gewinn liegt darin, dass du die Schnittstelle aus Sicht des Aufrufers festlegst, bevor dich eine halbfertige Implementierung in eine Richtung drängt.

Der zweite Stolperstein sind die Testebenen. Wer jeden Ablauf über Datenbank, Dateisystem und HTTP prüft, wartet Minuten auf jeden Durchlauf und bricht den Rhythmus ab. Damit der kurze Takt funktioniert, muss die Fachlogik ohne Infrastruktur aufrufbar sein, und in gewachsenen Systemen wird das oft erst nach einer sauberen Trennung der Zuständigkeiten möglich.

Und dann gibt es die Tests, die niemand mehr anfassen will: Sie prüfen interne Details statt Verhalten und schlagen bei jedem Umbau fehl, obwohl fachlich alles stimmt. Danach wird nur noch dort umgebaut, wo es keine Tests kostet, und der Rest bleibt liegen.

Testgetriebene Entwicklung lernen

Wie du Testfälle schneidest, Ergebnisse bewertest und eine Suite pflegst, die niemanden ausbremst, üben die Trainings zum Softwaretest an echtem Code.

Den Zyklus lernst du am schnellsten in einer Sprache, die du schon beherrschst, zum Beispiel in den Programmierkursen mit Python.

Häufige Fragen

Muss ich wirklich jeden Test vor dem Code schreiben?
In der reinen Lehre ja, im Alltag entscheidest du nach Risiko. Für Berechnungen, Sonderfälle und Zustandsübergänge zahlt sich die Reihenfolge fast immer aus. Bei Wegwerf-Prototypen oder beim reinen Verdrahten fremder Bibliotheken ist der Aufwand oft größer als der Ertrag.
Welche Werkzeuge brauche ich für TDD?
Nur ein Testframework für deine Sprache und einen schnellen Weg, es zu starten, zum Beispiel pytest für Python, JUnit für Java, Jest oder Vitest für JavaScript und das eingebaute go test für Go. Die meisten Entwicklungsumgebungen führen Tests per Tastenkürzel aus oder lassen sie bei jedem Speichern automatisch laufen. Spezielle TDD-Software gibt es nicht, die Methode steckt in der Reihenfolge.
Wie hoch sollte die Testabdeckung bei TDD sein?
Die Abdeckung ist eine Kontrollzahl, kein Ziel. Wenn du testgetrieben arbeitest, ergibt sich ein hoher Wert von selbst, weil keine Zeile ohne vorherigen Test entsteht. Eine feste Quote als Vorgabe führt dagegen schnell zu Tests, die Zeilen ausführen, ohne etwas zu prüfen.
Wird die Entwicklung dadurch nicht langsamer?
Der erste Durchlauf dauert länger, weil zwei Dinge entstehen statt einem. Die Zeit kommt beim Ändern zurück, sobald ein halbes Jahr später jemand eine Regel anpassen muss und die Suite in Sekunden sagt, was dabei kaputtgeht. Ohne Tests bezahlst du dieselbe Zeit später in der Fehlersuche.
Funktioniert TDD auch mit Code, den eine KI schreibt?
Gerade dann ist es nützlich. Wenn du zuerst die Tests festlegst und einen Coding-Assistenten danach den Code erzeugen lässt, hast du eine objektive Prüfung, ob der Vorschlag tut, was er soll. Die Tests selbst solltest du aber nicht ungeprüft von derselben KI übernehmen, sonst bestätigt sie sich am Ende nur selbst.

Passt thematisch dazu

Den ersten Test kannst du nur schreiben, wenn das erwartete Verhalten feststeht, und dafür braucht es eine Anforderung, aus der sich ohne Rückfrage ein Testfall bauen lässt.

Nicht jedes Verhalten gehört in einen Unit-Test, deshalb lohnt vorab die Überlegung, auf welcher Ebene eine Prüfung überhaupt hingehört.

Persönlich für dich da

Deine Ansprechpartner

Du willst das Thema nicht nur nachschlagen, sondern anwenden können? Wir beraten dich persönlich und kostenlos.

Yves Hoppe

Yves Hoppe

Weiterbildung & Beratung

Ordnet mit dir ein, welcher Kurs zu deinem Vorwissen passt.

Norbert Jansen

Norbert Jansen

Beratung & Inhouse

Plant Inhouse-Trainings, die an euren eigenen Daten und Abläufen ansetzen.

Testgetriebene Entwicklung im Kurs statt im Lexikon

Nachschlagen bringt dich bis zum Verstehen. Anwenden lernst du an echten Aufgaben.