Stefan Lieser

Stefan Lieser

TDD vs. Test-first

Bei diesem Beitrag handelt es sich um eine überarbeitete Version. Er wurde zuvor bereits bei refactoring-legacy-code.net veröffentlicht.

Gibt es einen Unterschied zwischen TDD und Test-first?

 

Immer wieder erlebe ich Diskussionen um die Frage, wie man denn nun Software so richtig richtig testet. Die Erkenntnis, dass automatisierte Tests notwendig sind, scheint sich inzwischen durchgesetzt zu haben. Ich erlebe nicht mehr, dass Entwickler ernsthaft behaupten, automatisierte Tests wären Zeitverschwendung, zu kompliziert, in ihrem Projekt halt unmöglich oder was auch immer die Argumente früher waren. Automatisierte Tests sind offensichtlich von allen Entwicklern anerkannt, als wichtigster Beitrag zum Wert der Korrektheit.

Damit ist der erste Schritt getan. Nur: automatisierte Tests für sinnvoll halten und tatsächlich Tests regelmäßig schreiben, sind zwei Paar Schuhe. Natürlich sind automatisierte Tests im Umfeld von Legacy Code eine andere Herausforderung, als auf der grünen Wiese. Methoden und Klassen mit unklaren Zuständigkeiten, dazu noch voller Abhängigkeiten, lassen sich nicht leicht automatisiert testen. Doch auch auf der grünen Wiese tun sich Entwickler schwer. Da wird gerne über den Sinn oder Unsinn bestimmter Praktiken und deren Details diskutiert, statt es einfach zu tun. Und ganz offensichtlich fehlt ein allgemein anerkannter Prozess, in dem die Schritte beschrieben sind, die auf dem Weg von den Anforderungen zum Code durchlaufen werden müssen. „TDD“ mögen nun einige rufen. Joa, schon besser als einfach los kodieren. Konsequentes Zerlegen von Anforderungen und Entwurf sind Tätigkeiten, die ich in den allermeisten Teams nicht entdecken kann und trotzdem für essentiell halte. Aus gutem Grund habe ich gemeinsam mit meinem Kollegen Ralf Westphal mit Flow Design eine Vorgehensweise und Entwurfssprache entwickelt, mit der diese Lücke geschlossen wird.

Doch bleiben wir beim Thema automatisiertes Testen. Meine Beobachtung ist, dass es hier schon bei den Begriffen durcheinander geht. So lange nicht jedem an einer Diskussion beteiligten klar ist, welche Bedeutung Begriffe wie „Unit Test“, „TDD“ oder „Test-first“ haben, wird die Diskussion nicht produktiv sein, sondern sich in Missverständnissen verstricken. Ich möchte daher mit diesem Beitrag darstellen, welche Bedeutung die einzelnen Begriffe für mich haben. Gerne können wir dann andere Bedeutungen diskutieren, mit dem Ziel, uns einem Konsens zu nähern, wie die Begriffe gemeint sind. Danach können wir beginnen, über Sinn oder Unsinn bestimmter Details zu diskutieren.

Werte

 

Damit Software die Anforderungen des Product Owners korrekt umsetzt, muss sie getestet werden. Der Wert der Korrektheit wird also nur erreicht, wenn wir Software testen, ob nun automatisiert oder manuell. Vielleicht kommen wir in der Informatik irgendwann dahin, dass die Korrektheit auf andere Weise nachgewiesen werden kann. Derzeit geht an Tests kein Weg vorbei. Um auch dem Wert der Produktionseffizienz gerecht zu werden, müssen die Tests automatisiert werden. Daraus ergibt sich die Praktik des automatisierten Testens. Die Werte entstammen dem Wertesystem der Clean Code Developer Initiative.

Als Entwickler schreiben wir automatisierte Tests, um die Sicherheit zu erhöhen, dass die Anforderungen korrekt umgesetzt sind. Für die Automatisierung der Tests kommen Frameworks wie JUnit, NUnit, Mocha, o.ä. zum Einsatz. Ein automatisierter Test ist somit ein Stück Software, meist eine Methode, welches einen Teil der Implementation durchläuft und Annahmen über das Resultat trifft. Diese Annahmen werden automatisiert überprüft, so dass der Test Erfolg oder Misserfolg anzeigen kann. Durch die Automatisierung ist es möglich, die Tests wiederholt auszuführen, ohne dass dadurch nennenswerte Kosten entstehen. Dies ist einer der Unterschiede zum manuellen Testen. Manuelle Tests kosten sehr viel Geld und werden daher möglichst selten durchgeführt. Der zweite Unterschied ist, dass durch die Automatisierung der Tests sichergestellt ist, dass die Testfälle bei jedem Ausführen der Tests garantiert immer wieder dieselben Annahmen überprüfen. Manuelle Tests leiden häufig darunter, dass trotz Testanweisungen in schriftlicher Form, nicht alle Annahmen exakt überprüft werden. Es braucht sehr viel Disziplin und eine sehr gute Organisation, Tests manuell durchzuführen.

Automatisierter Test

 

Wird ein Testfall automatisiert, ist das Ergebnis ein automatisierter Test und nicht zwingend ein Unit Test. Auch wenn Testframeworks manchmal „Unit Test Framework“ genannt werden, ist dieser Begriff eigentlich falsch. Es müsste „Automated Test Framework“ o.ä. heißen. Die Frameworks dienen zur Automatisierung von Tests. 

Mit einem solchen Test können sowohl Unit Tests als auch Integrationstests und sogar Systemtests automatisiert werden. Alle drei sind also orthogonal zum Begriff automatisierter Test. Stattdessen beschreiben diese Begriffe, welche Ausschnitte eines Softwaresystems getestet werden. In der folgenden Abbildung ist ein Baum von Funktionseinheiten mit ihren Abhängigkeiten dargestellt.

Dieser Baum besteht aus Blättern und Knoten. Die Blätter werden bei Einhaltung des IOSP als Operationen bezeichnet, die Knoten als Integrationen.

TDD vs. Test-first - Clean Code Developer Akademie - Seminare und Trainings

Unit Test

 

Ein Unit Test ist dafür zuständig, eine Unit, oder auch Funktionseinheit, isoliert zu testen. Wird eines der Blätter getestet, handelt es sich um einen Unit Test, da die Blätter keine weiteren Abhängigkeiten haben. Ein Blatt ist bereits eine freigestellte Einheit. Bevor wir betrachten, wie ein Knoten mit einem Unit Test isoliert getestet werden kann, betrachten wir Integrationstests.

Baumstruktur Unit Test Blatt

Integrationstest

 

Testet man einen der Knoten, handelt es sich um einen Integrationstest. Der Knoten wird inklusive seiner Abhängigkeiten getestet. Man testet damit also nicht eine Einheit in Isolation, sondern inkl. ihrer Abhängigkeiten. Das Ziel der Integrationstests ist es herauszufinden, ob die Integration der Bestandteile korrekt funktioniert. In der Regel müssen bei einem Integrationstest die Abhängigkeiten mitgetestet werden, da es andernfalls nicht zur Integration kommt, wenn alle Abhängigkeiten durch Attrappen ersetzt sind.

Baumstruktur Integrationstest

Im Einzelfall kann es sinnvoll sein, einen sehr kleinen Ausschnitt der Abhängigkeiten durch Attrappen zu ersetzen, bspw. um sehr teure und/oder aufwendige Ressourcenzugriffe zu eliminieren.

Baumstruktur Integrationstest mit Attrappe

Systemtest

 

Eine spezielle Form von Integrationstest sind die Systemtests. Mit einem Systemtest wird das gesamte Softwaresystem inklusive aller Abhängigkeiten getestet. Insbesondere wird hier also durch die Ui hindurch getestet und es kommen die realen Ressourcen wie Datenbanken oder spezielle Hardware zum Einsatz. In Integrations- und Systemtests sind in ihrer reinen Form keine der Bestandteile durch Attrappen ersetzt. Wie bei Integrationstests kann es aber auch hier notwendig sein, wenige Details durch Attrappen zu ersetzen.

Baumstruktur Systemtest
Baumstruktur Systemtest mit Attrappe

Mit Interfaces und Attrappen zum Unit Test

 

Manchmal besteht der Wunsch, eine Funktionseinheit, die über Abhängigkeiten verfügt, isoliert zu testen. Die Herausforderung hier ist also, aus einem Knoten eine Unit zu machen. Der Knoten muss für einen Unit Test von seinen Abhängigkeiten befreit werden. Dazu werden Interfaces und Attrappen eingesetzt. Dadurch wird der Knoten isoliert und in einem Unit Tests kann nur der Bestandteil des Knoten getestet werden, ohne dass die Abhängigkeiten einen Einfluss ausüben. Doch hier gilt es nun aufzupassen: ein solcher Test ist kein Integrationstest mehr, da im Test nicht die realen Abhängigkeiten integriert werden, sondern Attrappen, die sich im Test möglicherweise anders verhalten, als die Originale, die sie ersetzen.

DIP 1 tdd test-first
DIP 2 tdd test-first

Die folgende Abbildung zeigt eine Knoten, dessen Abhängigkeiten alle durch Attrappen ersetzt sind. Auf diese Weise kann auch ein Knoten in einem Unit Test isoliert getestet werden. Erforderlich ist das allerdings nur, wenn im Knoten Logik liegt, die andernfalls nur mit einem Integrationstest getestet werden könnte. Befolgt man das IOSP, sind Unit Tests auf Knoten in der Regel nicht erforderlich.

Baumstruktur Unit Test Knoten 1 tdd test-first

Ressourcentest

 

Manche Kollegen bezeichnen einen Test, der auf eine externe Ressource wie das Dateisystem zugreift, nicht mehr als Unit Test. Diese Definition halte ich für falsch! Selbstverständlich kann eine Funktionseinheit gleichzeitig eine Unit sein und einen Ressourcenzugriff durchführen. Eine Methode, die einen Dateinamen erhält und den Inhalt der Datei zurück liefert, ist eine Unit, da sie keine Abhängigkeiten zu anderen Methoden der Lösung hat. Wieso sollten automatisierte Tests dieser Methode nicht Unit Test genannt werden? Hier werden von den Kollegen zwei Aspekte vermischt. Einerseits die Betrachtung der Abhängigkeitsstruktur und andererseits der Zugriff auf externe Ressourcen. Beide Aspekte sind orthogonal, also voneinander unabhängig. Ich kann einen Ressourcenzugriff in einer Methode realisieren, dann ist diese Methode eine Unit und kann mit einem Unit Tests überprüft werden. Ein Ressourcenzugriff kann aber auch mit vielen voneinander abhängigen Methoden realisiert werden, dann kann ich auf der obersten Ebene einen Integrationstest ansetzen. Ich spreche daher zur Unterscheidung von einem Ressourcentest, wenn eine Funktionseinheit getestet wird, die auf eine externe Ressource zugreift. Sowohl ein Unit Test als auch ein Integrationstest kann also gleichzeitig ein Ressourcentest sein. Zum Testen von Ressourcen finden Sie hier einen weiteren Artikel.

Ressourcentest
Ressourcentest und Integrationstest

Damit haben wir nun folgende Begriffe zur Verfügung:

  • Automatisierter Test vs. Manueller Test – Aussage über die Automatisierung der Tests
  • Unit Test vs. Integrationstest vs. Systemtest – Aussage über den Umgang mit Abhängigkeiten
  • Ressourcentest – Aussage über den Zugriff auf externe Ressourcen

TDD vs. Test-First

 

Es fehlen die beiden Begriffe TDD (Test Driven Development) und Test-first. Ich bin mir nicht sicher, ob diese Begriffe von einigen Kollegen synonym verwendet werden. Für mich gibt es einen wesentlichen Unterschied. Test-first beschreibt, dass jeweils zuerst ein automatisierter Test erstellt wird, bevor Funktionalität implementiert wird. Im Gegensatz dazu steht Test-after. Hierbei wird erst die Funktionalität implementiert und erst dann werden automatisierte Tests erstellt.

Mit Test Driven Development wird mehr beschrieben als lediglich Test-first. Auch bei TDD werden die Tests jeweils vor der Implementation erstellt. Allerdings soll darüber hinaus durch die Tests die Struktur der Software entwickelt werden. Vereinfacht gesagt beginnt man jeweils mit einem Test, implementiert gerade soviel, dass der Test grün wird und refaktorisiert dann ggf. um die Verletzung von Prinzipien zu eliminieren. Habe ich mich bspw. bei der Implementation wiederholt und dadurch das DRY Prinzip verletzt, extrahiere ich die Gemeinsamkeiten. Oder ich stelle fest, dass eine Methode mehr als eine Verantwortlichkeit hat und damit gegen das SRP verstößt. Auch hier extrahiere ich die Verantwortlichkeiten durch eine Refactoring Maßnahme um das Einhalten der Prinzipien wieder herzustellen. Jeweils im Anschluss an Refactorings können die Tests erneut ausgeführt werden und so sicherstellen, dass durch das Refactoring nichts kaputt gegangen ist.

Über die Wirksamkeit von TDD lässt sich streiten und ich habe dazu eine klare Meinung. Vor einer solchen Diskussion muss allerdings Klarheit über die Begriffe herrschen. Test-First ist Bestandteil von TDD. TDD ergänzt den Refactoring Schritt und versucht, durch diese Vorgehensweise eine Implementation zu entwickeln, die die wichtigsten Prinzipien einhält.

Unsere Seminare

course
Clean Code Developer Basics

Prinzipien und Tests – Das Seminar wendet sich an Softwareentwickler, die gerade beginnen, sich mit dem Thema Softwarequalität auseinanderzusetzen. Es werden die wichtigsten Prinzipien und Praktiken der Clean Code Developer Initiative vermittelt.

zum Seminar »
course
Clean Code Developer Advanced

Mit Flow Design von den Anforderungen zum Clean Code – Lernen Sie mit Flow Design einen Softwareentwicklungsprozess kennen, der Sie flüssig von den Anforderungen zum Clean Code führt.

zum Seminar »
course
Clean Code Developer Trainer

Seminare als Trainer durchführen – Dieses Seminar wendet sich an Softwareentwickler, die ihr Wissen über die Clean Code Developer Prinzipien und Praktiken bzw. über Flow Design als Trainer an andere weitergeben möchten.

zum Seminar »
course
Clean Code Developer CoWorking

Online CoWorking inkl. Coaching –
Wir werden häufig gefragt, was man als Entwickler tun könne, um kontinuierlich dran zu bleiben am Thema Clean Code Developer. Unsere Antwort: Treffen Sie sich regelmäßig wöchentlich online mit anderen Clean Code Developern.

zum Seminar »

Kommentar verfassen

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