Benutzer:Naraesk/Testgetriebene Entwicklung
Testgetriebene Entwicklung (auch testgesteuerte Programmierung, engl. test first development oder test-driven development (TDD)) 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.
Gründe für die Einführung einer testgetriebenen Entwicklung
[Bearbeiten | Quelltext bearbeiten]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
[Bearbeiten | Quelltext bearbeiten]Bei der testgetriebenen Entwicklung ist zu unterscheiden zwischen dem Testen im Kleinen (Unit-Tests) und dem Testen im Großen (Systemtests, Akzeptanztests), wobei Becks Methode auf Unit-Tests ausgelegt ist.
Testgetriebene Entwicklung mit Unit-Tests
[Bearbeiten | Quelltext bearbeiten]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 Teilstück an 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 Code-Konventionen 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 gewünschte Funktionalität erreicht oder der bekannte Fehler bereinigt ist und dem Entwickler keine sinnvollen weiteren Tests mehr einfallen, die vielleicht noch scheitern könnten. Die so behandelte programmtechnische Einheit (Unit) wird dann als (vorerst) fertig angesehen. Die gemeinsam mit ihr geschaffenen Tests bleiben erhalten, um auch zukünftige Umsetzungen daraufhin testen zu können, ob das erwünschte Verhalten fortbesteht.
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
[Bearbeiten | Quelltext bearbeiten]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
[Bearbeiten | Quelltext bearbeiten]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 Refactoring, 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.
- Ein testgetriebenes Vorgehen führt in der Tendenz zu Programmcode, der stärker modularisiert ist sowie leichter zu ändern und zu erweitern. Denn aus Entwicklersicht entsteht dabei das geplante System aus kleinen Arbeitseinheiten, die unabhängig geschrieben und getestet, aber erst später integriert werden. Die korrespondierenden Softwareeinheiten (Klassen, Module, …) werden damit kleiner, spezifischer, ihre Kopplung wird lockerer und ihre Schnittstellen schlichter. Nutzt man auch Mock-Objekte, zwingt dies ebenfalls dazu, Abhängigkeitsstrukturen einfach zu halten, weil sonst der dabei essentielle schnelle und umstandslose Austausch von Modulen für Test und für Produktionscode nicht möglich wäre.
Einsatzgebiete
[Bearbeiten | Quelltext bearbeiten]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
[Bearbeiten | Quelltext bearbeiten]Die testgetriebene Entwicklung braucht vordringlich
- 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
[Bearbeiten | Quelltext bearbeiten]Konsequenz ist nötig
[Bearbeiten | Quelltext bearbeiten]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
[Bearbeiten | Quelltext bearbeiten]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 Schnittstellenkommunikation 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 solche Fehler zu finden, sind Integrationstests und Systemtests erforderlich.
Literatur
[Bearbeiten | Quelltext bearbeiten]- Kent Beck: Test Driven Development by Example, Addison-Wesley Verlag, ISBN 0-321-14653-0
- Johannes Link: Softwaretests mit JUnit – Techniken der testgetriebenen Entwicklung, ISBN 3-89864-325-5
- Frank Westphal: Testgetriebene Entwicklung mit JUnit und FIT, ISBN 3-89864-220-8
- Klaus Meffert: JUnit Profi-Tipps – Software erfolgreich testen, ISBN 3-935042-76-0
- Lasse Koskela: Test driven: TDD and Acceptance TDD for Java developers, ISBN 1-932394-85-0
Weblinks
[Bearbeiten | Quelltext bearbeiten]- 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
- Video-Crashkurs 'Malen nach Zahlen': Test Driven Development Einführung für Software-Entwickler (Deutsch)