TDD – Test Driven Development – Qualitativ hochwertige Software von Anfang an

Test-Driven Development (TDD = Testgetriebene Entwicklung) ist eine Methode, die häufig bei der agilen Entwicklung von Anwendungen eingesetzt wird. Bei der testgetriebenen Entwicklung erstellt der Programmierer Softwaretests vor den zu testenden Komponenten.

Übrigens ist BDD aus der testgetriebenen Entwicklung hervorgegangen. Über BDD bzw. Behavior Driven Development habe ich bereits gestern geschrieben.

Klassisch und fast schon nostalgisch hat man und praktiziert man die Entwicklung heute noch nach dem Wasserfall- oder dem V-Modell-Prinzip. Man entwickelt die Tests parallel zum und unabhängig vom zu testenden System oder sogar erst nachdem die Anwendung „fertiggestellt“ wurde. Aufgrund dieser Tatsache resultiert meist daraus, dass der Code schwer testbar ist und somit der Aufwand für die Tests verhältnismäßig hoch ausfällt. Darüber hinaus kommt es auch vor, dass die Tests nicht die gewünschten oder erforderlichen Testabdeckungen und Ergebnisse liefern, die man sich erhofft.

Dies kann unter anderem daran liegen, dass die fehlende oder mangelnde Testbarkeit des Systems auf die Nutzung von Fremdkomponenten zurückzuführen ist. Auch die Verweigerung einer Investition in nicht-funktionale Programmteile seitens der Entscheider bzw. Unternehmensführung kann ein Grund dafür sein. So im Sinne von, „Arbeit, von der man später im Programm nichts sieht, seien vergeudete Ressourcen.“ Die Erstellung von Tests unter Zeitdruck, rein um die gewünschte Testabdeckung zu erzielen ist ebenfalls ein Grund dafür. Nicht selten, ist es aber auch die Nachlässigkeit und mangelnde Disziplin der Entwickler bei der Testerstellung. An dieser Stelle sein auch das White-Box-Testing zu erwähnen, den ich aber in einem zukünftigen Beitrag thematisieren werde.

Die Methode der testgetriebenen Entwicklung versucht den Nachteilen entgegenzuwirken und dabei auch ein auf die Aufgabenstellung der Software besser angepasstes und wartbareres Softwaredesign zu liefern.

Alles in allem ist es eine Tatsache, dass man bei der Anwenung von testgetriebener Entwicklung im Schnitt bis zu 45 Prozent aller Fehler erkennen bzw. vermeiden kann. Im Vergleich dazu, werden beim reinen Einsatz von Unittests im Schnitt nur bis zu 30 Prozent der Fehler erkannt.

Wie funktioniert TDD?

Bei der testgetriebenen Entwicklung ist zwischen dem Testen im Großen (Integrationstests, Systemtests, Akzeptanztests) und dem Kleinen Modultests (Unit Tests) zu unterscheiden.

Testgetriebene Entwicklung mit Unit-Tests (Stichwort Tests First bzw. Middle-Out-TDD)

Man schreibt Unit-Tests in der Regel vor der eigentlichen Entwicklung der Anwendung. Es ist nicht festgelegt, ob der Entwickler, der die Implementierung vornimmt, auch die Unit-Tests erstellt. Es ist erlaubt, dass mehrere fehlschlagende Unit-Tests gleichzeitig existieren. Die Umsetzung des von einem Unit-Test geforderten Verhaltens in der Anwendung kann zeitlich verschoben werden.

Die Methode Tests First kann als Vorstufe der testgetriebenen Entwicklung betrachtet werden.

TDD nach Kent Beck

Man entwickelt Unit-Tests und die mit ihnen getesteten Units stets parallel. Die eigentliche Entwicklung erfolgt in kleinen, wiederholten Mikroiterationen. Eine solche Iteration, die nur wenige Minuten dauern sollte, hat drei Hauptteile, die man im englischen schlicht als Red, Green und Refactor bezeichnet.

  1. Red: Schreibe einen Test, der ein neues zu programmierendes Verhalten (die Funktionalität) prüfen soll. Dabei fängt man mit dem einfachsten Beispiel an. Ist die Funktion schon älter, kann dies auch ein bekannter Fehler oder eine neu zu implementierende Funktionalität sein. Dieser Test wird vom vorhandenen Programmcode erst einmal nicht erfüllt und muss folglich fehlschlagen.
  2. Green: Ändere den Programmcode mit möglichst wenig Aufwand ab und ergänze ihn, bis er nach dem anschließend angestoßenen Testdurchlauf alle Tests besteht.
  3. Räume dann im Code auf (Refactoring): Entferne Wiederholungen (Duplizierten Code), abstrahiere wo nötig, richte ihn nach den verbindlichen Code-Konventionen aus. In dieser Phase darf kein neues Verhalten eingeführt werden. Nach jeder Änderung werden die Tests ausgeführt. Der Fehlschlag der Tests verbietet es, die offenbar fehlerhafte Änderung in den bereits genutzten Code zu übernehmen. Ziel des Aufräumens ist es, den Code schlicht, elegant und verständlich zu machen.

Diese drei Schritte wiederholt man so lange und so oft, bis die bekannten Fehler bereinigt sind, der Code die gewünschte Funktionalität liefert und dem Entwickler keine sinnvollen weiteren Tests mehr einfallen, die vielleicht noch scheitern könnten. Die so behandelte programmtechnische Einheit (Unit) wird dann als einstweilen fertig angesehen. Die gemeinsam mit ihr geschaffenen Tests werden beibehalten, damit auch nach künftigen Iterationen und Änderungen getestet werden kann, ob die schon erreichten Aspekte des Verhaltens weiterhin erfüllt werden.

Damit die – auch Transformationen genannten – Änderungen in Schritt 2 zum Ziel führen, muss jede Änderung zu einer allgemeineren Lösung führen. Sie darf also nicht etwa nur den aktuellen Testfall auf Kosten anderer behandeln. Tests, die immer mehr ins Detail gehen, treiben den Code so zu einer immer allgemeineren Lösung. Die Beachtung der Transformationsprioritäten führt dabei regelmäßig zu effizienteren Algorithmen und damit Anwendungen. Die konsequente Befolgung dieser Vorgehensweise ist eine evolutionäre Entwurfsmethode, indem jede der einzelnen Änderungen bzw. Iterationen das System von Natur aus weiterentwickelt.

Testgetriebene Entwicklung mit System- oder Akzeptanztests (Stichwort Outside-In-TDD)

Wie bereits erwähnt, entwickelt man Systemtests bei der testgetriebenen Entwicklung immer vor dem System selbst oder aber man erstellt zumindest das Konzept dafür. Aufgabe der Systementwicklung ist bei testgetriebener Entwicklung nicht mehr, wie klassisch, schriftlich formulierte Anforderungen zu erfüllen, sondern spezifizierte Systemtests zu bestehen.

Akzeptanztestgetriebene Entwicklung (ATDD)

ATDD ist zwar mit testgetriebener Entwicklung verwandt, unterscheidet sich jedoch in der Vorgehensweise von testgetriebener Entwicklung. Akzeptanztestgetriebene Entwicklung ist ein Kommunikationswerkzeug zwischen dem Kunden bzw. den Anwendern, den Entwicklern und den Testern. Es soll sicherstellen, dass die Anforderungen gut beschrieben sind. Akzeptanztestgetriebene Entwicklung verlangt keine Automatisierung der Testfälle, wenngleich diese für das Regressionstesten hilfreich sind. Die Tests bei akzeptanztestgetriebener Entwicklung müssen dafür auch für Nicht-Entwickler lesbar sein. Die Tests der testgetriebenen Entwicklung können in vielen Fällen aus den Tests der akzeptanztestgetriebenen Entwicklung abgeleitet werden.

Schreibe einen Kommentar

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.