Warum Test-Driven Development

Im Leben kommt man immer irgendwann an einen Punkt an, an dem man sich denkt: Wie kann ich mich verbessern?

Unser Leben kann teilweise mit einer Zusammensetzung vieler Microservices verglichen werden, auf die ich voraussichtlich in einem anderen Post eingehen werde. In (sehr) kurz; ein Microservice ist wie der Name schon sagt ein kleiner (Definitionssache) Service, der unabhängig vom Rest funktioniert. Ein kleines Beispiel; ein Baum funktioniert auch, wenn kein Mensch damit interagiert, obwohl wir alle Teil unseres komplexen Ökosystems sind. Der Mensch nutzt den Service, den ein Baum bietet indirekt. Jeder Baum interagiert mit unserer Atmosphäre, indem CO2 aufgenommen und O2 ausgegeben wird. Wir Menschen und die Pflanzenwelt nutzen unter anderem die Atmosphäre als Interface, um miteinander zu kommunizieren.

Natürlich ist die Welt bei weitem nicht so simple, aber das sollte nur als kleiner Anreiz zum Nachdenken dienen – das komplexe Zusammenspiel aus Objekten in unserer Umgebung ist um einiges vielschichtiger.

Es geht doch hier um Test-Driven Development, warum reden wir über Bäume?

Eine simple Antwort wäre: Dinge, die von einem fehlerhaften Objekt geplant werden, können selbst auch nur fehlerhaft sein. Ein Mensch, der niemals perfekt ist, kann keine perfekte Sache produzieren. Jedoch kann ein Mensch sicher gehen, dass Software, die man ausliefert, den Anforderungen entspricht. Den Anforderungen zu entsprechen, ist mein persönlich höhstes Software-Gebot – niemals darf mehr oder weniger gemacht werden, als verlangt.

Stellen wir uns mal kurz vor; Wir sollen ein Programm für eine Bank schreiben. Diese Bank möchte ihre Daten aus einer alten Datenbankstruktur migrieren und wir sollen diese Migration möglich machen. Was könnte passieren, wenn wir nicht gut genug testen?

increment.com hat in ihrer 10. Ausgabe einen interessanten Artikel über genau dieses Problem geschrieben. Eine Bank stand genau vor dieser Aufgabe und hat 2500 Personen Jahre investiert, um dann auf die Nase zu fallen. Die Daten waren nach der Migration korrupt und Kunden haben nach ihrem Einkauf fehlerhafte Einträge bemerkt, beim Online-Login ist man nach dem erfolgreichen Login in den Account fremder Personen gekommen und vieles mehr – definitiv lesenswert.

Von Experten wird angenommen, dass dieses Problem entstanden ist, weil man nicht genug getestet hat. Sicher war es auch nicht wirklich passend alle Datensätze auf einmal migrieren zu wollen. Viele IT-Giganten migrieren immer nur schrittweise – diese IT-Giganten haben aber auch oft mehr Expertise in diesen Bereichen oder treffen auf mehr Verständnis bei der Zeitplanung als IT-Teams aus dem Finanzsektor. Dieses Beispiel wird wohl noch lange Zeit als Anekdote genutzt, um schrittweise große Transformationen zu begründen.

Sobald man einen Software-Kraken in die Welt gesetzt hat, muss man ihn auch bändigen. Viele verschiedene Module, Microservices und vielleicht sogar verschiedene Programmiersprachen innerhalb eines solchen Projekts, machen die Aufgabe natürlich nicht leichter & „schreib doch Tests“ ist nicht und kann nicht die Antwort auf alle Probleme sein. Wenn ein riesiges Programm schon geschrieben ist, wird es außerdem unglaublich schwer im nachhinein hinreichend Tests zu schreiben.

Pair Programming in Kombination mit zur Aufgabe passenden Management-Methoden wie z. B. agilen Herangehensweisen, ständige Förderung und Weiterbildung des Kollegiums & vieles mehr, sind wichtige Bestandteile einer funktionierenden und prosperierenden IT-Umgebung.

Da TDD ein Baustein zu guter Software ist, sollten wir darüber sprechen.

Glücklicherweise ist es oft so, dass der Name schon preisgibt worum es genau geht: Test gesteuerte Programmierung. Die Programmierung einer Software wird durch Tests gesteuert. Man definiert zuerst die Tests, die an den Anforderungen anlehnend geschrieben werden und implementiert dann die Funktion, die getestet werden soll.

Persönlich nutze ich die Methodik des Red-Green-Refactor – die Idee ist, dass eine Person für jede Verantwortung einen Hut aufsetzt. Wenn sie die Tests schreibt, hat sie den Testing-Hut auf. Sobald die Person beginnt die Anforderung entsprechend der geschriebenen Tests zu implementieren hat sie den Programming-Hut auf und beim Refactoring nutzt sie den Refactoring-Hut. Martin Fowler nimmt in seiner Beschreibung zwei Hüte als Beispiel, ich habe drei genommen, um auf eine wichtige Idee in der Softwareentwicklung aufmerksam zu machen: das single responsibility principle (sinngemäß das Prinzip der eindeutigen Verantwortlichkeiten).

Red-Green-Refactor, versucht die Verantwortlichkeiten zu separieren: zuerst schreibt man Tests in Übereinstimmung mit den Anforderungen, dann implementiert man die Anforderungen in Übereinstimmung mit den Tests und zuletzt verbessert man den Code.

Viele stehen oft vor zwei Problemen:

  1. Was soll genau getestet werden?
  2. Management behauptet Tests wären eine Verschwendung an Resourcen

Was soll genau getestet werden?

Das war und ist für mich ein großes Problem. Was genau soll ich testen, wie granular sollten die Tests sein? David Heinemeier Hansson (Erfinder von Ruby, auch bekannt als DHH) findet es ebenfalls schwer Tests für etwas zu schreiben, was eigentlich noch gar nicht existiert. Es gibt eine interessante Diskussion in der ThoughtWorks Podcast Series und einen guten Blog-Post von Martin Fowler darüber. Schaut euch das gerne an und bildet euch eine eigene Meinung zu dem Thema.

Weil es mir auch immer sehr schwer fiel Tests für Dinge zu schreiben, die ich noch gar nicht kenne, waren meine ersten Test so allgemein geschrieben, dass ich prinzipiell das gesamte Programm hätte implementieren müssen, um diesen Test auf Grün zu bekommen. Wenn man so an das Problem herangeht, bringt TDD eigentlich auch keinen wirklich Vorteil.

Grundsätzlich muss man planerisch vorgehen, je nach Wichtigkeit und Größe des Projekts mit UML vordefinieren und im Team über die Implementierung diskutieren und dann erst umsetzen. Wenn man vorher genug Zeit für die Planung hatte, wird man leichter Tests schreiben können. Die Interfaces der Module & Services sind schon besprochen und müssen nur noch ihren Weg in den Code finden.

Kent Beck, der Author von Test-Driven Development by Example beschreibt, dass man nicht einzelne Methoden testen soll, sondern Funktionsweisen. Einzelne Methoden oder Funktionen tragen oft einen schwer pflegbaren Beitrag zur Erfüllung der Anforderung bei. Aber die Anforderung selbst kann (und sollte) getestet werden und dessen Tests sind wartbar, da die Anforderung geschrieben sowie klar definiert ist.

Ian Cooper beschreibt in seinem wirklich empfehlenswerten Vortrag die Pitfalls des TDD und wie man ihnen ausweichen kann. TLDR; TDD macht Software robust und spart sehr viel Zeit im After-Release, wenn man es richtig umsetzt.

Management behauptet Tests wären eine Verschwendung an Resourcen

Ein Trugschluss, den man in allen Bereichen des Extreme Programming sieht. Personen, die intuitiv darüber entscheiden müssten, ob Pair-Programming genutzt werden sollte, werden sich oft dagegen entscheiden. Warum sollten zwei Personen an einer Aufgabe arbeiten? Warum soll neben dem eigentlichen Code, der die Anforderung erfüllt, auch noch weiterer Code geschrieben werden – die Anforderung ist doch schon erfüllt? Es sind doch 2 Personen Jahre eingeplant, nicht 4 (oder ähnliches). Aber wie schon in meinem Post über Pair Programming beschrieben, ist das ein Trugschluss.

Wenn Tests korrekt und ausführlich geschrieben sind, erhöht das automatisch die Wartbarkeit und Qualität des Produkts. Bei fehlerhaften Änderungen, wird der Fehler um einiges früher erkannt und in Zusammenarbeit mit Programmen wie Jenkins schon direkt beim Push auf die Repository gesichtet. Es braucht keine langen Pull-Request-Reviews mehr, um zu erkennen, dass die neue Funktion oder Änderung eine vorher erfüllte Anforderung kaputt gemacht hat. Man sieht sofort, dass etwas nicht stimmt, ohne dass ein Mensch Zeit für ein Code-Review aufwenden musste.

Durch hohe Code-Qualität sollten viel weniger Bugs im Produktionscode landen – damit gibt es weniger Probleme, weniger Tickets und mehr Zeit sich auf das wesentliche zu konzentrieren.

Ein Kunde, der sich nicht meldet, ist ein zufriedener Kunde.

‒ Mein Software Engineering Professor

Wenn Kunden immer und immer wieder Probleme und Bugs melden müssen, frisst das natürlich auch die Zeit der Kunden. Sie müssen dann auf ein Update warten und verlieren dadurch potenziell Geld. Irgendwann werden diese Kunden sich dann für einen anderen Anbieter entscheiden, einen Anbieter, der vielleicht hohen Wert auf exzellente (was auch immer das sein soll) Softwarequalität legt, ein Ort an dem TDD gelebt und geliebt wird vielleicht.

Vielleicht könnt ihr diesen Ort in euren Unternehmen schaffen?


Zuletzt bearbeitet am 17.12.2019