Wenn wir zu einem Kunden kommen, wird uns oft gesagt, dass das Entwicklungsteam TDD betreibt. Oft schreibt ein Team jedoch Unit-Tests, aber es macht kein TDD. Das ist ein wichtiger Unterschied. Unit-Tests sind eine nützliche Sache. Unit-Tests sagen jedoch nichts darüber aus, wie man nützliche Tests erstellt, die neben Ihrem Code bestehen können. Auf der anderen Seite ist TDD eine wichtige Praxis, um das Design Ihres Codes zu verbessern. Das sind sehr unterschiedliche Dinge.
TDD vs. Einheitstests
TDD steht für Testgetriebene Entwicklung. Es ist ein Verb, etwas, das Sie tun. TDD hat mit Entwicklung zu tun. Der Akt des Entwerfens und Schreibens von Software. Unit Test ist ein Substantiv. Es ist ein Artefakt der Softwareentwicklung. Zufälligerweise ist eines der wichtigsten Werkzeuge, die in der Praxis von TDD verwendet werden, ein Unit-Test-Framework. Daher ist es vielleicht nicht verwunderlich, dass die Leute verwirrt sind. Ein Unit-Test-Framework (wie jUnit, ScalaTest, Jasmine) ermöglicht es Ihnen, kleine Codestücke schnell und effizient auszuführen. Ich erwähne hier absichtlich nicht "Test". Wir haben also drei verschiedene Dinge:
- TDD: ein Entwurfsprozess
- Einheitstest: ein feinkörniger Testfall
- Unit-Test-Framework: eine Bibliothek und zusätzliche Tools zum Ausführen kleiner Codeabschnitte
Wenn Sie Code mit TDD schreiben, folgen Sie einem "Rot-Grün-Refactor"-Zyklus.
Der TDD-Zyklus
Zuerst schreiben Sie einen Test - da ist es wieder, dieses lästige Wort! Dann führen Sie den Test aus und sehen, dass er fehlschlägt. Dies ist der "rote" Zustand. Wenn der Test fehlschlägt, zeigen die meisten Frameworks den Fehler in Rot an, daher der Name. Den Test zu diesem Zeitpunkt auszuführen, mag seltsam erscheinen, aber damit können wir "den Test testen". Sie führen den Test aus, um zu überprüfen, ob er so fehlschlägt, wie Sie es erwartet haben. Wenn dies nicht der Fall ist, haben Sie irgendwo einen Fehler gemacht.
Als Nächstes schreiben Sie gerade so viel Code, dass der Test erfolgreich ist, und führen den Test erneut aus, um dies zu beweisen - "Grün"! Es gibt einige Feinheiten, Richtlinien, die Ihnen helfen, Ihren Code sauber zu halten. Tun Sie das Minimum, um den Test zu bestehen, auch wenn dieses Minimum naiv erscheint. Schließlich überarbeiten Sie sowohl den Code als auch den Test, um sie so sauber, einfach und lesbar wie möglich zu machen. Führen Sie den Test dann sicherheitshalber noch einmal aus, um sicherzustellen, dass Sie beim Aufräumen nichts kaputt gemacht haben. Im "roten" Zustand schreiben Sie also einen Test. Im "grünen" Zustand haben Sie gerade genug Code implementiert, um den Test zu bestehen, und im "Refactor"-Zustand räumen Sie Ihren Code auf, um für die nächste Iteration bereit zu sein. Dieser Rot-Grün-Refactor-Gedanke ist das Herzstück von TDD. Wenn Sie sich nicht daran halten, machen Sie kein TDD! Wenn Sie die Tests schreiben, nachdem Sie den Code geschrieben haben, dann ist das kein TDD! Der Grund, warum diese Unterscheidungen wichtig sind, liegt darin, dass hier der wesentliche Wert von TDD liegt, der weit über den Wert von Unit-Tests hinausgeht. Worin besteht also dieser "wesentliche Wert"? TDD ermöglicht es uns, qualitativ hochwertigeren Code zu erstellen. Aber was macht denn nun "hochwertige Qualität" bei Code aus? Ich würde sagen, dass qualitativ hochwertiger Code modular und lose gekoppelt ist, eine hohe Kohäsion aufweist, eine gute Trennung der Belange gewährleistet und Informationen verbirgt. Vielleicht fallen Ihnen noch andere Eigenschaften von hochwertigem Code ein, aber diese Attribute gehören auf jeden Fall zu den definierenden Merkmalen. Welche Faktoren helfen uns, hochwertigen Code zu erstellen? Vor TDD waren die einzigen Triebkräfte die Erfahrung, die Fähigkeiten und das Engagement des einzelnen Softwareentwicklers. Lassen Sie uns einen Moment über den mechanischen Prozess von TDD nachdenken. Wir schreiben einen Test, der ein gewünschtes Verhalten unseres Systems festlegt. Das tun wir, bevor wir den Code geschrieben haben, um die Verhaltensziele des Tests zu erfüllen. Das bedeutet, dass der Test nicht eng an die Implementierung gekoppelt werden kann, weil es noch keine Implementierung gibt. Außerdem haben wir so die Möglichkeit, über die Funktionalität nachzudenken, bevor wir uns Gedanken über die Implementierung machen. Wenn wir einen Test schreiben, um ein bestimmtes Verhalten des Systems zu bestätigen, müssten wir ziemlich dumm sein, um einen Test zu schreiben, der dieses Verhalten nicht bestätigen kann - d.h. es sollte ein beobachtbares Ergebnis geben. Dieser "Outside-in"-Ansatz für das Design treibt den Code in einige wohldefinierte Richtungen. Code, der im Sinne von TDD "testbar" ist, ist modular, lose gekoppelt, hat eine hohe Kohäsion, eine gute Trennung der Belange und weist eine Informationsverschleierung auf. Kommt Ihnen das bekannt vor? Zusätzlich zu den Fähigkeiten und der Erfahrung eines Softwareentwicklers haben wir jetzt also einen Prozess, der Druck auf uns ausübt, qualitativ hochwertigeren Code zu entwerfen. TDD wirkt wie ein Verstärker für die Fähigkeiten eines jeden Softwareentwicklers. Das ist die Magie von TDD. Wie sieht es dann mit Unit-Tests aus?
Unit-Tests haben ihren Platz. Sie sind in der Regel etwas grobkörniger als diejenigen, die mit TDD geschrieben wurden. Sie können nützlich sein, aber die meisten Unternehmen, die viele Unit-Tests durchführen, sehen einige gemeinsame Probleme. Tests, die nach dem zu testenden Code geschrieben werden, sind in der Regel sehr viel enger an diesen gekoppelt. Das führt dazu, dass Software, die gut getestet wurde, oft schwer zu ändern ist, denn um sie zu ändern, müssen Sie auch die Tests ändern. TDD führt dazu, dass Sie Tests erstellen, die von Natur aus lockerer an den zu testenden Code gekoppelt sind, und hilft so, dieses Problem zu mildern.
Wenn Sie die Tests zuerst schreiben, haben Sie die Gelegenheit, über den Problembereich in einer nicht eindeutigen Sprache (der Programmiersprache) nachzudenken und sich Gedanken über die Schnittstelle zu machen, die vom Standpunkt des Kunden aus offengelegt werden muss. Dabei handelt es sich eigentlich gar nicht um Tests, sondern um"ausführbare Spezifikationen" für das Verhalten unseres Codes. Wenn wir sowohl den zu testenden Code als auch den Testcode überarbeiten, können wir diese lose Kopplung beibehalten. Es bedeutet auch, dass wir sicherstellen können, dass unsere "ausführbaren Spezifikationen" so klar und verständlich sind, wie wir sie machen können, um die Absicht unseres Designs zu verdeutlichen. Der Wert dieser "Spezifikationen" ist enorm. Ein nützlicher Nebeneffekt ist, dass sie als Unit-Tests (Substantiv) existieren, so dass, obwohl der Schwerpunkt von TDD nicht auf dem Testen liegt, wir als Nebeneffekt großartige Tests erhalten. Wir sagen sekundär, weil der Nutzen für die Qualität des Designs den Nutzen selbst einer guten Suite von Regressionstests bei weitem überwiegt. Was ist schließlich nötig, damit die Unit-Tests wartbar bleiben? Nachdem die Tests geschrieben worden sind, müssen sie während der gesamten Lebensdauer des Produkts gepflegt werden. Wenn sich die Tests nur auf die technische Implementierung der Anwendung beziehen (also eng an die Implementierung gekoppelt sind), sind sie zum Scheitern verurteilt, wenn sich der Code ändert. Wenn dagegen Unit-Tests, die im Rahmen von TDD als Spezifikationen erstellt werden, das Verhalten beschreiben (funktional, das Was ), schlagen die Tests nur dann fehl, wenn sich die Funktion ändert.TDD ist der beste Weg, um die Qualität Ihres Codes zu verbessern. Möchten Sie mehr über TDD erfahren? Sehen Sie sich die TDD-Schulung von Dave Farley auf Xebia Academy/ an.
Verfasst von

Arjan Molenaar
Contact




