Diese Frage stellt sich immer mal wieder, wenn in einem Projekt versucht wird, die testgetriebene Entwicklung (TDD) zu etablieren. Wie kommt’s?
TDD fordert:
- Wir definieren zuerst die Tests für unseren produktiven Code. D.h., wir schreiben erst die Unit-Tests.
- Diese Tests schlagen so lange fehl, bis der produktive Code fertig ist.
- Wir entwickeln, bis alle Tests erfolgreich durchlaufen. Dabei, so die Idee des TDD, setzen wir die Anforderungen auf die einfachste Art und Weise um.
- Weiterhin gehen wir in Mikro-Schritten vor: Wir setzen eine Funktionalität nach der anderen um. Wir beginnen mit der nächsten erst dann, wenn alle Tests für die vorherige erfolgreich durchlaufen.
- Solange ein Test fehlschlägt, darf nur an der Behebung dieser Fehler gearbeitet werden. In der Zeit wird keine neue Funktionalität entwickelt.
- Refactoring von existierendem Code darf nur begonnen werden, sofern keine Tests fehlschlagen.
Übertragen wir das Vorgehen für TDD aus der Softwareentwicklung mal auf den Bau eines Gebäudes. Das würde nach TDD folgendermaßen ablaufen.
- Wir entwerfen zunächst die Baupläne und definieren die Abnahmekriterien.
- Wir können den Bau nicht abnehmen, bis er fertiggestellt ist.
Bis hierhin wird sich niemand über das Vorgehen wundern, hoffe ich. Aber jetzt gut aufpassen.
- Wir bauen das erste Stockwerk auf die einfachste Art und Weise, bis sie den Bauplänen entspricht.
- Dann setzen wir darauf das zweite Stockwerk.
- Sofern wir merken, dass die Wände des ersten Stockwerks das zweite nicht tragen werden, benötigen wir ein Refactoring.
- Wir reißen also das erste Stockwerk noch mal komplett ab und bauen es neu, so dass es auch das zweite Stockwerk tragen kann.
- Darauf setzen wir Stockwerk Nummer zwei.
- Und so fahren wir fort, bis wir fertig sind.
Der Vorteil von TDD
Ich hoffe, es wird klar, worauf ich hinauswill: TDD kann aufgrund des Vorgehens in Mikroschritten dazu führen, dass man als Entwickler aufhört, in größeren Dimensionen zu denken, was aber in der Enterprise-Entwicklung eigentlich notwendig ist.
Das ist zumindest der Vorwurf, den ich oft höre. Anstatt in Trippelschritten zu entwickeln und den Code ständig einem Refactoring zu unterziehen, kann ich doch auch meinem gesunden Menschenverstand folgen und es gleich richtig machen.
Solange mein Verständnis der Anforderungen noch nicht ausgereift ist, werde ich häufig in die Bredouille kommen, ein Refactoring durchführen zu müssen. Und ich stimme zu: viel Refactoring bedeutet, dass ich eine instabile Code-Basis habe und dass ich häufig Tests für Code schreibe, den ich später wegwerfe. Das will niemand. So etwas kostet ja auch teure Projektzeit und kann dazu führen, dass ich wichtige Deadlines im Projekt nicht halten kann.
Was ich diesem Vorwurf dann entgegenhalte ist, folgender Gedanke:
TDD, zumindest nach meinem Verständnis, erzwingt, dass ich mir vorher viele und möglichst gute Gedanken über die Anforderungen mache. Idealerweise verbringe ich möglichst viel Zeit damit, diese mit den Stakeholdern zu klären. Anhand von möglichst vielen Beispielen gehe ich den zu implementierenden Prozess mehrfach in Gedanken durch.
Anforderungen in Form von Tests dokumentieren
Anders kann ich vorab überhaupt keine Tests schreiben, die meine Entwicklung treiben sollen. Bevor ich also die erste Zeile (Test-)Code schreibe, verbringe ich viel Zeit mit der Anforderungsanalyse. Die Tests, die ich dann schreibe, dokumentieren die Ergebnisse der Analyse. Nach TDD entwickelter Code ist nicht zwangsläufig besser als traditionell entwickelter Code. Aber er ist um Längen besser als gänzlich ungetesteter Code.
Und das ist ein weiterer und eigentlich auch schwerwiegenderer Vorteil, den man sich meiner Meinung nach mit TDD verschafft: Es zwingt dazu, sich Gedanken über die exakten Anforderungen zu machen, die das Projekt erfüllen muss. Es gibt kein anderes Vorgehen, das einen solchen Zwang beinhaltet. Im Idealfall führt es dazu, dass im Team regelmäßig Diskussionen darüber stattfinden, wie die Anforderungen des Kunden in Testfällen niedergeschrieben werden können. Es gibt kein besseres Klima, um darin das Verständnis für die Anforderungen gedeihen zu lassen.