oose
Willkommen bei deinem Kompetenznavigator oose
DeutschDeutsch

TDD or not TDD - Ist das die richtige Frage?

Blog offline

Dieser Artikel stammt aus unserem Blog, der nicht mehr betreut wird. Für Neuigkeiten zu oose und interessante Inhalte zu unseren Themen, folgt uns gerne auf LinkedIn.

"Test-first fundamentalism is like abstinence-only sex ed: An unrealistic, ineffective morality campaign for self-loathing and shaming."

(Test-First-Fundamentalismus ist wie ein Aufklärungsunterricht, der sexuelle Abstinenz lehrt: eine realitätsferne und unwirksame Moral-Kampagne von Selbsthass und Beschämung.)

Mit dieser Aussage eröffnete der dänische Softwareentwickler David Heinemeier Hansson (@dhh), Autor des populären Open-Source-Frameworks Ruby on Rails, am 23. April 2014 unter dem Titel "TDD is dead. Long live testing." einen im Internet vielbeachteten und kontrovers diskutierten Blogartikel.

In seinem Artikel erläutert DHH, dass es durchaus Zeiten gab, wo auch er Test-Driven Development (TDD) bzw. einen Test First Ansatz praktiziert hat, und diese Herangehensweise ihm durchaus die Augen geöffnet hätte. Er beschreibt, dass er Test First zunächst wie eine Einladung zu einer schönen, neuen Entwicklerwelt empfunden habe. Eine Möglichkeit um vor allem in solchen Bereichen der Softwareentwicklung das Testen zu etablieren, wo das vorher noch gar kein Thema gewesen sei.

"The test-first part was a wonderful set of training wheels that taught me how to think about testing at a deeper level, but also some I quickly left behind. (...) Enough. No more. My name is David, and I do not write software test-first. I refuse to apologize for that any more, much less hide it."

David beklagt vor allem den Fundamentalismus unter den TDD-Befürwortern. Und er befürwortet eine Verschiebung beim Testen: weniger Unit Tests, dafür mehr Tests auf der Systemebene bzw. auf Akzeptanztest-Ebene. Mit Ruby on Rails und einem entsprechenden Test-Framework für Webanwendungen, wie Capybara, sei das ja gut machbar.

Die Reaktionen auf diesen Blogartikel im WWW und in den sozialen Netzwerken, vor allem auf Twitter, waren bemerkenswert.

Unter dem Titel "Monogamous TDD" reagierte auch Robert C. Martin (a.k.a. Uncle Bob, @unclebobmartin) auf DHH's Beitrag und erläuterte noch einmal die Gründe, warum man Unit-Testing und TDD praktizieren sollte, und warum man mit Tests auf Systemebene, mit einer Datenbank im Hintergrund und über die GUI, gewisse Ziele schlichtweg nicht erreichen kann.

Ich möchte an dieser Stelle einmal meine Sichtweise zu Unit Tests, zu Test-Driven Development (TDD) bzw. Test-First, und zu der von DHH angestoßenen Diskussion darlegen.

TDD ist keine Religion

Zunächst einmal ist Test-Driven Development natürlich keine Religion, und auch keine Wunderwaffe. Die Verwendung von TDD kann auf keinen Fall die Entstehung von Software mit bestimmten, positiven Eigenschaften, wie gutes Design und hohe Qualität garantieren. Von daher wäre Fundamentalismus im Zusammenhang mit dieser Methode auch unangemessen.

Test-Driven Development ist lediglich ein Werkzeug, nicht mehr und nicht weniger.

tdd_cycleSo wie jeder Handwerker einen Werkzeugkasten mit verschiedenen Werkzeugen sein Eigen nennt, aus dem er je nach Problemstellung das am besten geeignete auswählen kann, so kennt auch ein Software Craftsman ein Repertoire an Tools und Techniken für seine tägliche Arbeit. Und wie so oft gilt auch hier: "One Size Doesn't Fit All."

Und, ja: TDD ist auch kein einfach zu erlernendes Werkzeug. Es erfordert viel Übung. TDD stellt die traditionelle Herangehensweise an die Softwareentwicklung auf den Kopf. Für viele Entwickler, die das erste Mal mit dieser Methode konfrontiert werden, ist die auf den ersten Blick paradox erscheinende Herangehensweise des Test First durchaus ein Paradigmenwechsel.

Und was ich für besonders wichtig halte: TDD sollte nicht mit "Plain Old Unit Testing" gleichgesetzt werden. Obwohl in Test Driven Development der Begriff "Test" vorkommt, und man in der Regel ein Unit-Test-Framework verwendet um es zu praktizieren, ist TDD primär keine Maßnahme zur Qualitätssicherung. Das bei der Durchführung von TDD auch automatisch 100% Unit-Test-Abdeckung entsteht, ist im Sinne der Softwarequalität natürlich sehr zu begrüßen, wird aber nur als angenehmer Begleiteffekt gesehen.

Wann verwende ich TDD, ...

Für mich leistet TDD auf der Mikroebene, also beim Schreiben von Code, das, was ein iterativ-inkrementelles Vorgehen, z.B. mit Hilfe eines Frameworks wie Scrum, auf der Prozessebene leistet.

Ich verwende das Werkzeug TDD immer dann, wenn Komplexität ins Spiel kommt. Wenn ich beispielsweise das Gefühl habe, dass ich ein Stück Software (z.B. eine Klasse in objekt-orientierten Sprachen) nicht geradlinig herunterschreiben kann, überlege ich mir erst im Detail, welche Anforderungen dieses Stück Software aus der Perspektive ihrer Verwender erfüllen muss. Diese Anforderungen kann ich dann schrittweise in Form von Unit Tests erfassen, und schreibe dann in ebenso kleinen Schritten den Produktionscode hinzu.

Bei diesem Vorgehen designe ich auch die API der zu erstellenden Softwareeinheit. Nicht, dass man nicht schon vorher zumindest einmal über einen Entwurf nachgedacht haben sollte. Auch ist ein TDD-Ansatz selbstverständlich keine Garantie für gutes Design. Nach meiner Erfahrung zeigt sich aber, dass man zumindest ein Feedback bekommt, wenn sich etwas in Richtung schlechtes Design entwickelt.

So fördert TDD meines Erachtens einen viel bewussteren Umgang mit Abhängigkeiten. Während des inkrementellen Durchlaufens wird man ja früher oder später an die Punkte herangeführt, an denen man Kollaborateure (z.B. andere Klassen, oder gar externe Systeme) für die Erfüllung bestimmter Aufgaben benötigt. Eigentlich möchte ich ja jetzt das andere Modul verwenden, aber muss diese Abhängigkeit jetzt wirklich sein? Wenn ja: wie gehe ich mit ihr um, wie schaffe ich trotz allem eine möglichst lose Kopplung, wie mocke ich es für den Test weg, usw.

Bei jedem Durchgang wird bei Bedarf ein Refactoring durchgeführt, was ja durch den hohen Grad an Absicherung mit Unit Tests gnadenlos möglich ist, ohne dabei wieder etwas kaputt zu machen. Dabei werden sowohl die Tests, als auch der Produktivcode gleichermaßen aufgeräumt, z.B. Redundanzen eliminiert, große Methoden in kleinere zerlegt, usw.

Zusammengefasst: TDD minimiert für mich das Risiko bei komplexen Problemstellungen eine suboptimale Lösung zu erstellen, oder sogar zu scheitern, indem ich mich mit Hilfe dieses Ansatzes in sehr kleinen Schritten (Inkrementen) durch den Lösungsweg leiten lasse. Dabei habe ich durch in sekundenschnelle ausführbare Tests eine sehr kurze Feedbackschleife, die mir Sicherheit gibt, das Richtige zu tun.

...und wann nicht?

Natürlich gibt es auch Situationen, in denen TDD entweder nicht das richtige Werkzeug ist, oder wo es einem übertrieben vorkommt.

So hilft TDD wenig bei Architekturentscheidungen auf hoher Abstraktionsebene. Die Fragestellung, in welche groben, fachlichen Bausteine beispielsweise ein System zerlegt wird, oder ob, und wenn ja, welche Frameworks und Bibliotheken verwendet werden, erfordert eher die bewusste Auseinandersetzung mit den Qualitätsanforderungen an das zu entwickelnde Systems, sowie den gegebenen Rahmenbedingungen.

Kann man HTML, XML oder CSS Test-getrieben entwickeln? Ja, das ist durchaus denkbar, aber eher unüblich. Normalerweise wird man etwas HTML und CSS schreiben, und dann auf den Bildschirm schauen, und so lange daran herum fummeln bis es so aussieht, wie es aussehen soll. Mit anderen Worten: hier ist die Wahrnehmung des Menschen vor dem Bildschirm der Test. Ob etwas "schön" aussieht ist ja bekanntlich auch Geschmackssache.

Auch der Code, den ich zur Testunterstützung benötige, wird in der Regel nicht Test-getrieben entwickelt. Darunter fällt z.B. Code, der zur Erzeugung von Testdaten verwendet wird. Stichworte sind hier z.B. Object Mother und Builder. Auch selbstgemachte Assertions (z.B. fluent assertions) fallen in diese Kategorie. Für diesen Code werden keine expliziten Tests benötigt, denn er wird implizit durch die existierenden Unit Tests, in denen er verwendet wird, mitgeprüft.

Auch gibt es manchmal Code, den man so geradlinig und schnell niederschreiben kann, und der keine Komplexität besitzt, dass es übertrieben erscheint hierfür TDD zu verwenden, z.B. Glue Code, der lediglich zwei Komponenten zusammenfügen soll.

"For anything that is more complex than just a few lines of code, software craftsmen can test-drive code as fast as other developers can write code without tests, if not faster." (Sandro Mancuso, Software Craftsmanship, Leanpub)

Fazit

Vielleicht ist es so, dass David Heinemeier Hansson einen akzeptablen, anderen Ansatz gefunden hat, um ausgehend von den Anforderungen an das System, von außen nach innen, Software in hoher Qualität zu entwickeln und gegen die Anforderungen zu verifizieren. Falls es so sein sollte, dann ist das völlig in Ordnung und erklärt seinen Standpunkt.

Ich will auch gar nicht behaupten dass Qualitätssicherungsmaßnahmen auf einer höheren Ebene, wie Integrations- oder Systemtests, nicht auch noch gemacht werden müssen. Im Gegenteil, diese Tests sind unverzichtbar, und natürlich kommen hier auch dann Datenbanken und externe Systeme ins Spiel, die bei den elementaren Unit-Tests durch Test Doubles (Stubs oder Mocks) ersetzt werden.

Doch diese großen Tests sind meines Erachtens völlig ungeeignet für ein schnelles Feedback. Während des Entwickelns möchte ich in Sekundenschnelle wissen, ob der Code, den ich gerade geschrieben habe, auch fehlerfrei funktioniert. Dabei möchte ich aber keine Datenbanken oder andere Systeme mit im Spiel haben. Derartige Systeme sind zumeist langsam, und können auch andere, unangenehme Seiteneffekte haben, z.B. die geforderte Unabhängigkeit der Tests unterlaufen.

Auf der anderen Seite scheint mir Davids Meinung auch stark von dem Kontext geprägt zu sein, in dem er sich bewegt. Sein Ruby on Rails ist ein Open Source Web Application Framework. Web-Anwendungen machen aber nur einen sehr geringen Anteil aller Softwareanwendungen aus, die weltweit entwickelt werden.

Wie steht es um die zig Millionen Embedded Systems, mit denen wir Menschen auf dieser Welt tagtäglich in Berührung kommen, die wir aber gar nicht bewusst wahrnehmen? Besteigen wir einen Fahrstuhl, begeben wir uns in die Hände von Software. Moderne Fahrzeuge haben eine hohe Anzahl von kleinen, miteinander vernetzten Softwaresystemen, die teilweise auch Aufgaben der funktionalen Sicherheit übernehmen. Medizinische Geräte, Steuerungen von Produktionssystemen in der Industrie, Flugzeuge, Klimaanlagen, usw. ... egal was wir tun, wir kommen ständig in Kontakt mit Software.

Vor diesem Hintergrund halte ich Unit Testing, und TDD, für zwei unverzichtbare Tools in meinem Werkzeugkasten, die ich nicht mehr missen möchte.

Diskussion auf Google Hangouts

Als Folge der durch David Heinemeier Hansson angestoßenen Diskussion wurde unter dem fragenden Titel "Is TDD dead?" ein Hangout veranstaltet, bei dem David, Martin Fowler (@martinfowler) und Kent Beck (@KentBeck) sich über TDD und Unit Testing ausgetauscht haben. Das erste Mal wurde am vergangenen Freitag (09. Mai 2014) diskutiert; die Folge wurde aufgezeichnet und kann u.a. auf YouTube angeschaut werden. So wie es derzeit aussieht wird es Fortsetzungen geben.