Vom Testen getrieben

Hallo Spaß Coder,

nachdem wir in den letzten Artikeln unserer Serie schon einiges zum Thema Testautomatisierung im allgemeinen und Unit-Tests im speziellen gelernt haben, möchten wir heute auf eine besondere Technik automatisierter Tests eingehen.

 

Test-Driven-Development

Beim Test-Driven-Development (abgekürzt TDD) geht es darum, den automatisierten Test in den Mittelpunkt der Entwicklung zu stellen. Entstanden ist die Idee von Kent Beck, der insbesondere Unit-Tests damit im Blick hatte. Das Ziel dabei ist es, den Code in kleinen Iterationen zu entwickeln, wobei der Code bei jedem Zyklus folgende Stufen durchläuft:

  1. Implementieren des Akzeptanztests in Form eines Unit-Tests, der fehlschlägt
  2. Implementieren der kleinstmöglichen Lösung im Produktionscode
  3. Refaktorisieren des Produktionscodes

Diese Zyklen werden so lange mit allen Anforderungen wiederholt, bis die gewünschte Funktion vollständig implementiert ist.

Kent Beck nennt diesen Zyklus RedGreenRefactor. Zuerst wird ein Unit-Test geschrieben der fehlschlägt, also in der IDE rot dargestellt wird. Anschließend wird die Funktion soweit implementiert, dass der Test nicht mehr fehlschlägt und somit in der IDE grün dargestellt wird. Ist die Implementierung des Produktionscodes abgeschlossen, wird ein Refactoring durchgeführt, um auch die Qualität des Codes hoch zu halten.

Voraussetzung für den Zyklus ist, dass die wesentlichen Akzeptanztests bereits vor dem Start der Entwicklung formuliert sind.

 

Bewusster Perspektiven-Wechsel

Martin Fowler macht noch mal sehr deutlich, dass ich als Entwickler in diesem Zyklus zwei verschiedene Perspektiven einnehme, wenn ich Code ändere. Martin hat dies anhand von zwei Hüten beschrieben, die ich nacheinander aufsetze. Einerseits implementiere ich die Anforderung und entwickle den Algorithmus oder die Fachlogik. Eben genau so lange, bis meine Tests (wieder) grün sind. Hierbei ergänze ich eine Funktion und trage den ersten Hut. Für das Refactoring wechsle ich nun meinen Hut und schaue konzentriert auf die Codestruktur mit der expliziten Absicht, die Funktionsweise des Codes nicht zu verändern. Die Tests geben mir dabei Feedback, dieses im Blick zu halten. Solange diese grün bleiben, habe ich keine (bisher getestete) Funktionsweise verändert.

Die Idee bei dieser Symbolik ist, dass ich die beiden Hüte nie gleichzeitig aufsetzen kann und daher ganz bewusst den Hut (die Perspektive) wechsle, wenn ich im Code arbeite – entweder Funktionen ergänzen oder den Code umstrukturieren. Diese strikte Trennung unterstützt eine strukturierte Arbeitsweise, so dass ich nicht zu viele Dinge auf einmal mache.

 

Was spricht für TDD?

Durch den einfachen Zyklus von Kent Beck erreichen wir Vieles, was auf den ersten Blick nicht ersichtlich wird.

Sicherstellen von Akzeptanzkriterien, statt prüfen einer Funktion

Neben der offensichtlichen Tatsache, dass wir zu jedem unserer Anwendungsfälle nach Abschluss der Entwicklung einen Unit-Test haben, sind diese Tests auch relevant. Sie prüfen nicht eine Funktion, die wir implementiert haben. Vielmehr stellt ein so erstellter Test sicher, dass ein durch den Anforderer formuliertes Akzeptanzkriterium erfüllt wird. Dies erhöht deutlich die Qualität des Tests an sich.

 

Sauberes Interface implementieren

Wenn wir zuerst den Test schreiben, bevor wir mit der Implementierung des Produktionscodes beginnen, werfen wir als Entwickler einen ganz anderen Blick auf die Aufgabenstellung. So schauen wir beim Schreiben von Tests aus Sicht des Aufrufers auf unsere Klasse. Wir überlegen uns also: „Wie würde ich diese Klasse verwenden wollen?“ Daraus ergibt sich früh ein sauberes Interface der Methoden der Klasse, noch bevor auch nur eine Zeile Produktionscode geschrieben ist:

  • Wie sollte die Klasse heißen?
  • Welche Methoden brauche ich und wie sollten sie heißen?
  • Welche Parameter übergebe ich?
  • Wie erstelle ich die Instanz der Klasse?

All diese Fragen stellen wir uns typischerweise nicht, wenn wir gleich mit der Implementierung beginnen.

 

Fokussieren auf die Anforderung

Ein anderer Aspekt ist die Fokussierung auf die Anforderung. Fangen wir gleich mit dem Produktionscode an, vergraben wir uns oft schnell in Details zur Problemlösung. Darüber hinaus versuchen wir die Anforderung häufig gleich ganzheitlich zu lösen. Gehen wir stattdessen von den Akzeptanztests aus, erfüllen wir immer genau nur eine Anforderung nach der anderen, lösen einen Akzeptanztest nach dem anderen.

 

Refactoring in Fleisch und Blut

Das Refaktorisieren ist fester Bestandteil im o.g. Zyklus. Verfolgen wir diesen fokussieren Ansatz, haben wir jeden Teil unseres Produktionscodes nicht nur getestet, sondern hinterlassen den Code auch sofort sauber und ordentlich. Wir erstellen nicht erst einen riesigen Berg Produktionscode, den wir dann nochmal aufräumen müssen, sondern sorgen während der gesamten Implementierungsphase für sauberen, gut strukturierten Code.

Sollte es durch eine später umgesetzte Anforderung notwendig werden, vorherige Teile neu zu implementieren, ist dieser so gut strukturiert, dass er sich leicht umstellen lässt…und getestet ist er ja auch, also Angst vor der Umstellung brauchen wir auch nicht zu haben.

 

Viele kleine Belohnungen

Durch den Zyklus RedGreenRefactor erfahren wir regelmäßig Belohnungen. Der Test ist grün? Cool, wieder was geschafft! Der Code ist sauber refaktorisiert? Klasse, ein Akzeptanzkriterium umgesetzt, auf zum nächsten!

Auch wenn dieser Vorteil eher unwichtig klingt, ist er doch nicht zu vernachlässigen. Je besser wir „drauf“ sind, desto besser ist unsere Lösung und desto sauberer wird unser Code.

 

Was spricht gegen TDD?

Kosten

Auch wenn es sich in er Erzählung einfach anhört, ist eine Eingewöhnung in das Thema wichtig. Wir erläutern das später nochmal bei unseren Erfahrungen. Für ein erfolgreiches Test-Driven-Development ist also noch mehr Investition notwendig, als ohnehin schon für die Testautomatisierung.

 

Akzeptanzkriterien erforderlich

TDD funktioniert nur dann, wenn die Akzeptanzkriterien der Anforderung bekannt sind. Nur dann können wir die richtigen Tests formulieren und implementieren. Manchmal können wir uns selbst im Vorfeld welche überlegen, wenn sie nicht im Rahmen der Anforderung formuliert wurden. Immer dann aber, wenn wir gar nicht so genau wissen, was wir erreichen wollen, funktioniert TDD nicht gut. Das ist in der Regel immer dann der Fall, wenn wir was ausprobieren wollen, wenn wir experimentieren.

 

Unsere Erfahrung mit TDD

Ganz ehrlich? Test-Driven-Development fühlt sich komisch an. Einen Test schreiben für Code, den wir noch gar nicht haben? Das geht doch gar nicht! Das war meine (Torsten) erste Reaktion auf das Verfahren. Auch später, nachdem ich es einmal ausprobiert hatte, fand ich das Vorgehen irgendwie doof. Ich bin immer wieder in alte Verhaltensweisen verfallen. Lieber erst mal den Code schreiben und dann schauen, was man testen kann.

Michael hat mich dann aber bei einigen unserer Pair-Programming-Sitzungen dazu ermuntert, es doch noch mal auszuprobieren. Und ja, die oben aufgeführten Vorteile lassen sich tatsächlich erreichen. Nach einiger Übung fühlt sich das Ganze gar nicht mehr so komisch an und ist gar nicht so doof, wie es anfangs erscheint.

Vielmehr ist die Implementierung wesentlich zielgerichteter und der Code der entsteht entspricht viel mehr der Qualität die wir uns selbst gesetzt haben. Seitdem versuchen wir TDD so oft wie möglich anzuwenden.

 

Zusammenfassung

TDD hat einige weitere Vorteile gegenüber dem normalen Unit-Test. Die Testabdeckung und -qualität wird erhöht, die Schnittstelle und Struktur eures Codes ist besser, die Arbeit ist fokussierter und macht mehr Spaß.

Ihr habt ein komisches Gefühl und wisst nicht wie ihr anfangen sollt? Dann habt ihr euch wahrscheinlich noch keine ausreichenden Gedanken über die Akzeptanzkriterien der Anforderung gemacht. Oder die Anforderung ist noch zu groß.

TDD passt hervorragend zur Agilen Softwareentwicklung, in der wir mit kleinen Anforderungen in Form von User-Stories arbeiten. Zu einer vollständigen User-Story gehören auch Akzeptanzkriterien, die gemeinsam mit dem Product Owner erarbeitet werden. Dies und die generelle Haltung in kleinen Iterationen zu arbeiten ist die perfekte Basis für Test-Driven-Development.

Unser Appell an euch: Probiert es aus! Haltet durch, auch wenn es sich am Anfang seltsam anfühlt. Die unmittelbare Belohnung durch den Zyklus, die höhere Testabdeckung und der saubere Code werden euch gefallen.

 

Viel Spaß beim Anwenden.

Eure Spaß-Coder

 

Dieser Artikel basiert neben unseren Erfahrungen auf folgenden Quellen:

 

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.