Testgetriebene Entwicklung

Testgetriebene Entwicklung (TDD) ist ein Sonderfall der Test-First-Programmierung, die das Element des kontinuierlichen Designs hinzufügt.

Bei der Test-First-Programmierung handelt es sich um die Erstellung automatisierter Komponententests für Produktionscode. Bevor Sie schreiben diesen Produktionscode. Anstatt Tests anschließend zu schreiben (oder, was typischer ist, diese Tests nie zu schreiben), beginnen Sie immer mit einem Komponententest. Für jeden kleinen Funktionsblock im Produktionscode erstellen und führen Sie zunächst einen kleinen (idealerweise sehr kleinen), gezielten Test aus, der spezifiziert und validiert, was der Code bewirken wird. Dieser Test wird möglicherweise zunächst nicht einmal kompiliert, da möglicherweise nicht alle erforderlichen Klassen und Methoden vorhanden sind. Dennoch fungiert es als eine Art ausführbare Spezifikation. Anschließend können Sie es mit minimalem Produktionscode kompilieren, sodass Sie es ausführen und zusehen können, wie es fehlschlägt. (Manchmal geht man davon aus, dass der Test fehlschlägt, und er besteht den Test, was eine nützliche Information ist.) Anschließend erstellen Sie genau so viel Code, dass der Test bestanden werden kann.

Diese Technik erscheint vielen Programmierern, die sie ausprobieren, zunächst seltsam. Es ist ein bisschen so, als würden Kletterer zentimeterweise eine Felswand hinaufklettern und dabei Anker in der Wand platzieren. Warum sich all diese Mühe machen? Sicherlich verlangsamt es Sie erheblich? Die Antwort ist, dass es nur dann Sinn macht, wenn Sie sich später stark und wiederholt auf diese Unit-Tests verlassen. Diejenigen, die zuerst den Test praktizieren, behaupten regelmäßig, dass diese Unit-Tests den Aufwand, der zum Schreiben dieser Tests erforderlich ist, mehr als wettmachen.

Für Test-First-Arbeiten verwenden Sie normalerweise eines der automatisierten Unit-Test-Frameworks der xUnit-Familie (JUnit für Java, NUnit für C# usw.). Mit diesen Frameworks ist das Erstellen, Ausführen, Organisieren und Verwalten großer Unit-Test-Suiten ganz einfach. (Zumindest in der Java-Welt werden sie immer besser in die besten IDEs integriert.) Das ist gut, denn wenn Sie zuerst testen, sammeln Sie viele, viele Komponententests an.

Vorteile der Test-First-Arbeit

Gründliche Sätze automatisierter Unit-Tests dienen als eine Art Netz zur Fehlererkennung. Sie bestimmen präzise und deterministisch das aktuelle Verhalten des Systems. Gute Test-First-Teams stellen fest, dass sie im gesamten Systemlebenszyklus wesentlich weniger Fehler bekommen und viel weniger Zeit für die Fehlerbehebung aufwenden. Gut geschriebene Unit-Tests dienen auch als hervorragende Designdokumentation, die per Definition immer mit dem Produktionscode synchronisiert ist. Ein etwas unerwarteter Vorteil: Viele Programmierer berichten, dass „der kleine grüne Balken“, der anzeigt, dass alle Tests sauber laufen, süchtig macht. Sobald Sie sich an diese kleinen, häufigen positiven Rückmeldungen über den Zustand Ihres Codes gewöhnt haben, ist es wirklich schwer, darauf zu verzichten. Wenn schließlich das Verhalten Ihres Codes durch viele gute Unit-Tests auf den Punkt gebracht wird, ist das viel safer für dich den Code umgestalten. Wenn durch ein Refactoring (oder eine Leistungsoptimierung oder eine andere Änderung) ein Fehler auftritt, werden Sie durch Ihre Tests schnell benachrichtigt.

Testgetriebene Entwicklung: noch weiter gehen

Testgetriebene Entwicklung (TDD) ist ein Sonderfall der Test-First-Programmierung, die das Element des kontinuierlichen Designs hinzufügt. Mit TDD wird das Systemdesign nicht durch ein Designdokument in Papierform eingeschränkt. Stattdessen überlassen Sie dem Prozess des Schreibens von Tests und Produktionscode die Steuerung des Designs. Alle paar Minuten nehmen Sie ein Refactoring vor, um es zu vereinfachen und zu verdeutlichen. Wenn Sie sich leicht eine klarere, sauberere Methode, Klasse oder ein ganzes Objektmodell vorstellen können, führen Sie ein Refactoring in diese Richtung durch und sind dabei jederzeit durch eine solide Suite von Komponententests geschützt. Die Annahme hinter TDD ist, dass Sie nicht wirklich sagen können, welches Design Ihnen am besten dient, bis Sie Ihre Arme bis zum Ellenbogen tief im Code haben. Wenn Sie erfahren, was tatsächlich funktioniert und was nicht, sind Sie in der bestmöglichen Position, diese Erkenntnisse anzuwenden, solange sie Ihnen noch frisch im Gedächtnis sind. Und all diese Aktivitäten werden durch Ihre automatisierten Unit-Tests geschützt.

Sie könnten mit einem ziemlich großen Vorentwurf beginnen, obwohl es üblicher ist, mit einigermaßen gutem Design zu beginnen einfaches Code-Design; In der extremen Programmierwelt reichen oft einige UML-Skizzen auf dem Whiteboard aus. Bei TDD ist es jedoch weniger wichtig, mit wie viel Design Sie beginnen, als wie sehr Sie zulassen, dass das Design im Laufe der Zeit von seinem Ausgangspunkt abweicht. Möglicherweise nehmen Sie keine umfassenden Änderungen an der Architektur vor, aber Sie können das Objektmodell weitgehend umgestalten, wenn Ihnen das die klügste Vorgehensweise erscheint. Einige Geschäfte haben mehr politischen Spielraum, echtes TDD umzusetzen als andere.

Testen Sie zuerst vs. Debuggen

Es ist nützlich, den Aufwand für das Schreiben von Tests im Vorfeld mit der Zeit für das Debuggen zu vergleichen. Beim Debuggen ist häufig das Durchsuchen großer Codemengen erforderlich. Durch die Testarbeit können Sie sich auf ein mundgerechtes Stück konzentrieren, bei dem weniger Dinge schief gehen können. Für Manager ist es schwierig vorherzusagen, wie lange das Debuggen tatsächlich dauern wird. Und in gewisser Hinsicht wird so viel Debugging-Aufwand verschwendet. Das Debuggen erfordert Zeitaufwand, Gerüstbau und Infrastruktur (Haltepunkte, temporäre Variablenüberwachung, Druckanweisungen), die im Wesentlichen allesamt verfügbar sind. Sobald Sie den Fehler gefunden und behoben haben, geht die gesamte Analyse im Wesentlichen verloren. Und wenn es für Sie nicht ganz verloren ist, ist es für andere Programmierer, die diesen Code pflegen oder erweitern, mit Sicherheit verloren. Bei der Test-First-Arbeit stehen die Tests für jeden zur Verfügung, und zwar für immer. Wenn ein Fehler irgendwie erneut auftritt, kann derselbe Test, der ihn einmal erkannt hat, ihn erneut beheben. Wenn ein Fehler auftritt, weil es keinen passenden Test gibt, können Sie von da an einen Test schreiben, der ihn erfasst. Auf diese Weise behaupten viele Test-First-Praktizierende, dass dies der Inbegriff dafür ist, intelligenter statt härter zu arbeiten.

Testen Sie zuerst Techniken und Werkzeuge

Es ist nicht immer trivial, für jeden Aspekt des Verhaltens eines Systems einen Unit-Test zu schreiben. Was ist mit GUIs? Was ist mit EJBs und anderen Lebewesen, deren Leben von containerbasierten Frameworks verwaltet wird? Was ist mit Datenbanken und Persistenz im Allgemeinen? Wie testen Sie, ob eine Ausnahme ordnungsgemäß ausgelöst wird? Wie testen Sie Leistungsniveaus? Wie messen Sie Testabdeckung, Testgranularität und Testqualität? Diese Fragen werden von der Test-First-Community mit einem sich ständig weiterentwickelnden Satz von Tools und Techniken beantwortet. Es wird weiterhin enormer Einfallsreichtum eingesetzt, um es möglich zu machen, jeden Aspekt des Verhaltens eines Systems mit Unit-Tests abzudecken. Beispielsweise ist es oft sinnvoll, eine Komponente eines Systems isoliert von seinen Partnern und externen Ressourcen zu testen, indem Fakes und Mock-Objekte verwendet werden. Ohne diese Mocks oder Fakes können Ihre Unit-Tests das zu testende Objekt möglicherweise nicht instanziieren. Oder im Fall externer Ressourcen wie Netzwerkverbindungen, Datenbanken oder GUIs kann die Verwendung des echten Tests diesen enorm verlangsamen, während die Verwendung einer gefälschten oder simulierten Version dafür sorgt, dass alles schnell im Speicher läuft. Und während einige Aspekte der Funktionalität immer manuelle Prüfung, sinkt der Prozentsatz, für den dies unbestreitbar zutrifft, weiterhin.