Testgetriebene Entwicklung: Test-first oder Test-last?

PrintFriendly and PDF


Testgetriebene Entwicklung (Test-driven development, TDD) sieht vor, die Tests vor dem eigentlichen Produktivcode zu schreiben (Test-first). Wer sich dabei nicht wohl fühlt, kann aber auch mit Test-last Systeme gut strukturieren und durch Tests absichern.

Mein Kollege Lutz hat in seinem Artikel zu Code-Retreats vor kurzem beschrieben, wie man – auch durch testgetriebene Entwicklung – zu einem besseren Entwickler werden kann. Seine Erfahrungen finde ich sehr beeindruckend. Sie haben mir Lust gemacht, ebenfalls an einem Code-Retreat teilzunehmen! Nur von einem der Grundsätze testgetriebener Entwicklung bin ich nicht vollständig überzeugt.

Worum geht es bei testgetriebener Entwicklung?

Die testgetriebene Entwicklung hilft dabei, Systeme gut zu strukturieren und leicht verständliche Tests zu schreiben. Test-first als Entwicklungsstrategie besagt, dass man erst Tests schreibt, die das gewünschte Verhalten prüfen, bevor man den Produktivcode dazu entwickelt. Dadurch erreicht man, dass es für jeden Produktivcode überhaupt Tests gibt und der Produktivcode so strukturiert ist, dass er sich einfach testen lässt.

Um Test-first erfolgreich umsetzen zu können, muss ein System in möglichst kleine, autarke und einzeln testbare Einheiten aufgeteilt werden. Denn je größer die Einheiten sind, desto komplexer werden meistens die Tests. Aber auch wenn als erstes der Test geschrieben wird, muss der Testcode kompilieren. Dazu ist es notwendig, die äußere Struktur des Produktivcodes festzulegen, also seine öffentlichen Schnittstellen und Methoden.

Mein Knackpunkt: Komplexe Anforderungen und Lernen durch Umstrukturieren

Zu Beginn der Entwicklung einer neuen komplexen Funktion hat man die vollständigen Auswirkungen dieser Funktion vielleicht noch nicht begriffen – vor allem die Seiteneffekte und Sonderfälle fallen oft erst im Laufe der Entwicklung auf (teilweise auch erst im Gespräch mit dem Kunden während der Umsetzung). Je intensiver man sich mit der Umsetzung beschäftigt, desto besser begreift man die eigentlichen Probleme. Und erst dann, wenn man die Probleme vollständig begriffen hat, kann man die Umsetzung dafür optimal strukturieren. Ein Beispiel dafür sind umfangreiche Erweiterungen oder Änderungen am Domänenmodell einer Anwendung. Ein anderes Beispiel sind neue Bedienoberflächen mit vielen Interaktionsmöglichkeiten.

Mir geht es jedenfalls oft so, dass ich meinen Code im Laufe eines Arbeitstages mehrfach umstrukturiere, bis ich mit der Struktur zufrieden bin. Zufrieden heißt: Der Code ist eine möglichst einfache und leicht verständliche Lösung für das Problem. Die Struktur meines Codes am Ende des Tages hat dabei eventuell nicht mehr viel mit dem Code vom Anfang des Tages zu tun.

Gute und verständliche Tests zu schreiben, ist aufwändig. Gerade bei Tests für komplexe Geschäftslogik steckt in den Tests häufig mehr Aufwand als im Produktivcode. Wenn man schon vor dem Produktivcode aufwändige Tests schreibt, kann dies die Motivation verringern, die zu testenden Strukturen zu verändern. Anders gesagt: Sobald die Tests einmal geschrieben sind, verändert man die aufgerufenen Schnittstellen nur noch ungerne.

Die Gefahr besteht also darin, die Struktur des Produktivcodes nicht mehr verbessern zu wollen, weil die schon geschriebenen Tests aufwändig zu ändern sind. Natürlich ist dies ein klassisches Problem bei jedem Projekt mit hoher Testabdeckung. Aber durch den Ansatz, Tests vor dem Produktivcode zu schreiben, verstärkt es sich noch.

Mein Ansatz: Weiterhin Test-last

Bei einfachen Anforderungsänderungen nutze ich gerne mal Test-first. Bei komplexen Strukturen bleibe ich jedoch bei Test-last und arbeite so lange an der neuen Funktion, bis ich das Gefühl habe, dass ich die Probleme vollständig begriffen und sich die Struktur meiner Lösung stabilisiert hat. Dann schreibe ich meine Tests dazu.

Plädiere ich somit für Test-last als besserer TDD-Strategie? Nicht unbedingt! Denn Test-last erfordert viel Disziplin, dass die Tests wirklich geschrieben werden. Ich plädiere vor allem gegen Grundsatzdiskussionen und für viel Pragmatismus. Jeder Entwickler mag unterschiedliche Ansätze haben, wie er oder sie Tests schreibt. Hauptsache, das zu entwickelnde System ist sauber strukturiert und durch gute Tests abgesichert!

ps: Wer trotz meiner Meinung mehr über die Vorteile von Test-first wissen will, kann an dieser Stelle bald einen weiteren Artikel von Lutz lesen.