- Test first development
-
Als testgetriebene Entwicklung (auch testgesteuerte Programmierung, engl. test first development oder test-driven development (TDD), manchmal auch scherzhaft Extreme Testing) ist eine Methode, die häufig bei der agilen Entwicklung von Computerprogrammen eingesetzt wird. Bei der testgetriebenen Entwicklung erstellt der Programmierer Software-Tests konsequent vor den zu testenden Komponenten. Die dazu erstellten Unit-Tests sind Grey-Box-Tests statt klassischer White-Box-Tests.
Inhaltsverzeichnis
Gründe für die Einführung einer testgetriebenen Entwicklung
Nach klassischer Vorgehensweise, beispielsweise nach dem Wasserfall- oder dem V-Modell, werden Tests parallel zum und unabhängig vom testenden System entwickelt oder sogar nach ihm. Dies führt oft dazu, dass nicht die gewünschte und erforderliche Testabdeckung erzielt wird. Mögliche Gründe dafür sind unter anderem:
- Fehlende oder mangelnde Testbarkeit des Systems (monolithisch, Nutzung von Fremdkomponenten, …).
- Verbot der Investition in nicht-funktionale Programmteile seitens der Unternehmensführung. („Arbeit, von der man später im Programm nichts sieht, ist vergeudet.“)
- Erstellung von Tests unter Zeitdruck.
- Nachlässigkeit und mangelnde Disziplin der Programmierer bei der Testerstellung.
Ein weiterer Nachteil klassischer White-Box-Tests ist, dass der Entwickler das zu testende System und seine Eigenheiten selbst kennt und dadurch aus Betriebsblindheit unversehens "um Fehler herum" testet.
Die Methode der testgetriebenen Entwicklung versucht den Gründen für eine nicht ausreichende Testabdeckung und einigen Nachteilen der White-Box-Tests entgegenzuwirken.
Vorgehensweise
Bei der testgetriebenen Entwicklung ist zu unterscheiden zwischen dem Testen im Kleinen (Unit-Tests) und dem Testen im Großen (Systemtests, Akzeptanztests), wobei Beck's Methode auf Unit-Tests ausgelegt ist.
Testgetriebene Entwicklung mit Unit-Tests
Unit-Tests und mit ihnen getestete Units werden stets parallel entwickelt. Die eigentliche Programmierung erfolgt in kleinen und wiederholten Mikroiterationen. Eine solche Iteration, die nur wenige Minuten dauern sollte, hat drei Hauptteile:
- Schreibe Tests für das erwünschte fehlerfreie Verhalten, für schon bekannte Fehlschläge oder für das nächste Bröckelchen Funktionalität, das neu implementiert werden soll. Diese Tests werden vom bestehenden Programmcode erst einmal nicht erfüllt bzw. es gibt diesen noch gar nicht.
- Ändere/schreibe den Programmcode mit möglichst wenig Aufwand, bis nach dem anschließend angestoßenen Testdurchlauf alle Tests bestanden werden.
- Räume dann im Code auf (Refactoring): Entferne Wiederholungen (Code-Duplizierung), abstrahiere wo nötig, richte ihn nach den verbindlichen Kodekonventionen aus etc. Natürlich wieder mit abschließendem Testen. Ziel des Aufräumens ist es, den Code schlicht und verständlich zu machen.
Diese drei Schritte werden so lange wiederholt, bis die inzwischen geschaffenen Tests alle bestanden werden und dem Entwickler keine sinnvollen weiteren mehr einfallen, die vielleicht noch scheitern könnten. Die so behandelte programmtechnische Einheit (Unit) wird dann als (vorerst) fertig angesehen.
Die konsequente Befolgung dieser Vorgehensweise läuft auf evolutionären Entwurf hinaus, weil die ständige Änderung die Weiterentwicklung eines Systems in den Vordergrund rückt.
Da der einzelne Unit-Test sowohl Züge eines White-Box-Tests als auch eines Black-Box-Tests aufweist, bezeichnet man ihn auch als Grey-Box-Test.
Testgetriebene Entwicklung mit Systemtests
Systemtests werden immer vor dem System selbst entwickelt oder doch wenigstens spezifiziert. Aufgabe der Systementwicklung ist bei testgetriebener Entwicklung nicht mehr, wie klassisch, schriftlich formulierte Anforderungen zu erfüllen, sondern spezifizierte Systemtests zu bestehen.
Gemeinsamkeiten zwischen Testgetriebener Entwicklung mit Systemtests und Unit-Tests
Bei beiden Arten von Tests wird eine möglichst vollständige Testautomatisierung angestrebt. Für testgetriebene Entwicklung müssen alle Tests einfach („per Knopfdruck“) und möglichst schnell ausgeführt werden können. Für Unit-Tests bedeutet das eine Dauer von wenigen Sekunden, für Systemtests von maximal einigen Minuten, bzw nur in Ausnahmen länger.
Die großen Vorzüge der testgetriebenen Methodik gegenüber der klassischen sind:
- Man hat eine triviale Metrik für die Erfüllung der Anforderungen: die Tests werden bestanden oder nicht.
- Das Refaktorisieren, also das Aufräumen im Code, führt zu weniger Fehlern; weil man dabei in kleinen Schritten vorgeht und stets entlang bestandener Tests, entstehen dabei wenige neue, und sie sind besser lokalisierbar.
- Weil einfach und ohne großen Zeitaufwand getestet werden kann, arbeiten die Programmierer die meiste Zeit an einem korrekten System und also mit Zutrauen und konzentriert auf die aktuelle Teilaufgabe hin. (Keine „Durchquerung der Wüste“, kein „Alles hängt mit allem zusammen“)
- Der Bestand an Unit-Tests dokumentiert das System zugleich. Man erzeugt nämlich zugleich eine „ausführbare Spezifikation“ – was das Softwaresystem leisten soll, liegt in Form sowohl lesbarer wie auch jederzeit lauffähiger Tests vor.
Einsatzgebiete
Testgetriebene Entwicklung ist wesentlicher Bestandteil des Extreme Programming (XP) und anderer agiler Methoden. Auch außerhalb dieser ist sie anzutreffen, häufig in Verbindung mit der Paarprogrammierung .
Werkzeuge
Die testgetriebene Entwicklung braucht vordinglich
- ein Werkzeug zur Build-Automatisierung wie etwa CruiseControl
- einen Rahmen und ein Werkzeug zu Testentwicklung und -automatisierung,
damit die Iterationen schnell und unkompliziert durchlaufen werden können.
Bei der Java-Entwicklung kommen dafür meist Ant und JUnit zum Einsatz. Für die meisten anderen Programmiersprachen existieren ähnliche Werkzeuge, wie z. B. für PHP PHPUnit.
Für komplexe Systeme müssen mehrere Teilkomponenten unabhängig voneinander entwickelt werden und es finden dazu auch noch Fremdkomponenten Verwendung, etwa ein Datenbanksystem zwecks persistenter Datenhaltung. Die korrekte Zusammenarbeit und Funktion der Komponenten im System muss dann auch getestet werden. Um nun die Einzelkomponenten dabei separat testen zu können, die doch aber zu ihrer korrekten Funktion wesentlich von anderen Komponenten abhängen, verwendet man Mock-Objekte als deren Stellvertreter. Die Mock-Objekte ersetzen und simulieren im Test die benötigten anderen Komponenten in einer Weise, die der Tester ihnen einprogrammiert.
Ein Werkzeug für Akzeptanztests und Systemtests ist beispielsweise Framework for Integrated Test. Eine beliebte FIT-Variante ist Fitnesse, ein Wiki-Server mit integrierter Testerstellungs- und Testausführungsumgebung.
Kritik
Konsequenz ist nötig
Auch die Methode der testgetriebenen Entwicklung kann falsch eingesetzt werden und dann scheitern. Programmierern, die noch keine Erfahrung dabei besitzen, erscheint sie manchmal schwierig oder gar unmöglich. Sie fragen sich, wie man etwas testen soll, das doch noch gar nicht vorhanden ist. Auswirkung kann sein, dass sie die Prinzipien dieser Methode vernachlässigen, was insbesondere bei agilen Methoden wie dem Extreme Programming Schwierigkeiten beim Entwicklungsprozess oder sogar dessen Zusammenbruch zur Folge haben kann. Ohne ausreichende Unit-Tests wird keine ausreichende Testabdeckung für das Refactoring und die gewünschte Qualität erreicht. Dem kann man mit Paarprogrammierung und Schulung entgegenwirken.
Kein Ersatz für Systemtests
Auch diese stark auf Tests setzende Art der Programmierung kann nicht jeden Fehler aufdecken, insbesondere nicht Fehler, die programmextern entstehen: Timingfehler wie Thread-Deadlocks, Fehler bei der RS232-Kommunikation usw. Ebenfalls kann es an der nötigen Testabdeckung mangeln, wenn nicht alle potentiellen Eingaben einer Funktion getestet werden können. Dies ist etwa der Fall, wenn etwa die Eingabe aus sehr vielen Einzeldaten besteht; dann kann aufwandshalber nicht mehr jede mögliche Kombination von Eingabedaten getestet werden. Fehler bei Benutzungsoberfächen sind schlecht testbar, weil natürlich kein automatisierter, prüfender „Testblick“ auf die Folge der Bildschirmdarstellungen und die Verständlichkeit von Darstellung und Textelementen möglich ist. Um anderswie noch Fehler zu finden, sind Integrationstests und Systemtests erforderlich.
Literatur
- Kent Beck: Test Driven Development by Example, Addison-Wesley Verlag, ISBN 0321146530
- Johannes Link: Softwaretests mit JUnit - Techniken der testgetriebenen Entwicklung, ISBN 3898643255
- Frank Westphal: Testgetriebene Entwicklung mit JUnit und FIT, ISBN 3898642208
- Klaus Meffert: JUnit Profi-Tipps - Software erfolgreich testen, ISBN 3935042760
- Lasse Koskela: Test driven : TDD and Acceptance TDD for Java developers, ISBN 1932394850
Weblinks
- JUnit, Framework zur testgetriebenen Entwicklung in Java (Englisch)
- TDD Community (Englisch)
- Einführende Artikel
- Bessere Kodequalität und Kosteneinsparungen durch Einheittests/Modultests
- Eine kurze Einführung in die Prinzipien der Test gesteuerten Programmierung
- Fitnesse
- Framework for Integrated Tests
- EMOS Framework Open Source Code Library für Funktionale Softwaretests mit Winrunner
- Test Driven Development in .NET example for TDD in Visual Studio and .NET including WatiN test framework for web applications
Wikimedia Foundation.