Bernhard Findeiss
Mittwoch, der 3. Dezember 2008

Agilität und testgetriebene Entwicklung

Testgetriebene Entwicklung ist eine Art der Programmierung, die häufig bei agilen Methoden angewandt wird. Dabei werden zuerst die in den Anforderungen an das System (User Stories) enthaltenen Abnahmekriterien in Testfälle umgewandelt, und diese dann immer vor der eigentlichen Programmfunktionalität umgesetzt.

Die Aufgabe eines Programmierers besteht dann darin, eine minimale Menge an Code zu schreiben, so dass alle Tests mit Status „positiv getestet“ durchlaufen. Ist dieses Ziel erreicht, so ist das System fertig und kann ausgeliefert werden.

Dies ist natürlich nur eine (etwas flapsige) Zusammenfassung. Im Detail ist das Vorgehen wie folgt (angelehnt an das Buch „Test-Driven Development by Example“ von Kent Beck):

  1. Schreibe einen Test: Die Umsetzung jedes neuen Features beginnt mit dem Schreiben eines Tests. Dazu wird entweder ein bereits existierender Test abgeändert, oder ein neuer Test geschrieben. Um zu wissen, was getestet werden soll, muß sich der Programmierer mit der entsprechenden User Story vertraut machen. Der dort beschriebene Anwendungsfall sollte, zusammen mit dem ebenfalls enthaltenen Abnahmekriterium, genügend Informationen beinhalten, um ein Grundgerüst für den Test zu liefern (positiver Fall, negativer Fall, Ausnahmefälle etc.). Dieses Vorgehen hat übrigens einen weiteren angenehmen Nebeneffekt: Da der Entwickler sich schon vor Beginn der Programmierung intensiv mit der jeweiligen Anforderung auseinandersetzen muß, fallen etwaige Inkonsistenzen und Unklarheiten bereits sehr früh auf. Änderungen sind daher noch relativ leicht umzusetzen.
  2. Lasse alle Tests laufen und schau, ob der neue Test fehlschlägt: Da für den neuen Test noch kein eigentlicher Code geschrieben wurde, müsste er in diesem Entwicklungsstadium eigentlich fehlschlagen. Ist das nicht der Fall, so ist der Test in seiner aktuellen Form sinnlos und muß noch einmal überarbeitet werden. Gleichzeitig soll aber damit auch überprüft werden, ob alle anderen Tests noch problemlos durchlaufen. Etwaige unerwünschte Seiteneffekte können dadurch erkannt und beseitigt werden.
  3. Schreibe etwas Code: Dies ist das, was man sonst als die eigentliche Programmierarbeit gesehen hat. Die zu entwickelnde Software wird mit gerade soviel neuem Code versehen, daß der Test durchläuft. Es ist dabei okay, wenn dieser Code noch „unelegant“ ist, da er in späteren Schritten eh noch verbessert werden wird. Auf keinen Fall aber sollte man vom Minimalitätsprinzip abweichen. Die Gefahr, neue Funktionalität einzuführen, für die kein Test existiert, wäre zu groß.
  4. Lasse alle Tests erfolgreich durchlaufen: Da der Code für unseren neuen Test nun existiert sollten jetzt alle Tests erfolgreich durchlaufen. Man kann somit relativ sicher sagen, daß der Code auch jetzt noch allen Anforderungen genügt. Dies ist auch eine wichtige Voraussetzung für den nächsten Schritt.
  5. Code aufräumen (Refactoring): Jetzt ist es an der Zeit, den Code aufzuräumen. Dies betrifft natürlich zum Einen den gerade neu hinzugefügten Code, der sich evtl. noch etwas besser/eleganter/klarer etc. schreiben lässt. Andererseits gilt es aber auch, das Gesamtsystem zu untersuchen (z.B. auf doppelte Codestellen) um zu prüfen, ob sich durch die neue Funktionalität an anderer Stelle des Programms noch eine Verbesserung erreichen lässt. Dank der vollständigen Testabdeckung kann der Programmierer dabei zur jeder Zeit überprüfen, ob auch der neue Code noch allen Anforderungen entspricht. Laufen alle Tests durch, so ist das Risiko, durch den Umbau des Codes etwas „kaputtgemacht“ zu haben, überschaubar.
  6. Starte von vorne: Wenn alle bisherigen Schritte erfolgreich waren, so kann die aktuelle Anforderung als fertig betrachtet werden. Der Entwickler kann nun die nächste in Angriff nehmen.

Somit wird nun auch klarer, warum testgetriebene Entwicklung bei agilen Vorgehensweisen so wichtig ist: Unsicherheit, etwa in den Anforderungen, ist ein expliziter Bestandteil aller agilen Modelle. Im Klartext bedeutet dies, daß das Programm häufigen Änderungen unterliegen wird. Gerade bei größeren Systemen beinhaltet dies jedoch teils erhebliche Risiken, da man nicht immer abschätzen kann, wie sich eine Änderung an irgendeiner Stelle auf das Gesamtsystem auswirkt. Durch eine gute Testabdeckung kann man dieses Risiko jedoch wieder in den Griff kriegen. Die Wahrscheinlichkeit, immer noch allen Anforderungen zu genügen, ist daher auch im Falle häufiger Änderungen noch relativ hoch.

Ein kleiner Wermutstropfen jedoch bleibt: Die hier beschriebene Vorgehensweise eignet sich am besten für objektorientierte Programmiersprachen und moderne Tools (z.B. integrierte Entwicklungsumgebungen mit Unterstüzung für Refactoring u. Unit-Tests). Zumindest jedoch benötigt man die Unterstützung von Unit-Tests. Diese gibt es zwar für die meisten gebräuchlichen Programmiersprachen (in Wikipedia existiert eine kleine Übersicht). Bei älteren oder exotischen Programmiersprachen könnte es allerdings schwierig werden.

1 Kommentar zu “Agilität und testgetriebene Entwicklung”

  1. IF-Blog » Blog Archiv » Scrum und der “hyperproduktive Zustand” (Dienstag, der 3. Februar 2009)

    […] Zu diesem Thema habe ich schon zu einem früheren Zeitpunkt mal einen Artikel geschrieben (siehe hier), weswegen ich hier nicht nochmal alles wiederholen möchte. Die Kernaussage ist jedoch, daß sich […]

Kommentar verfassen

*