Testgetriebene Entwicklung

aus Wikipedia, der freien Enzyklopädie
Wechseln zu: Navigation, Suche

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 Testfälle werden auch als Grey-Box-Tests bezeichnet.

Gründe für die Einführung einer testgetriebenen Entwicklung[Bearbeiten]

Nach klassischer Vorgehensweise, beispielsweise nach dem Wasserfall- oder dem V-Modell, werden Tests parallel zum und unabhängig vom zu 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]

Bei der testgetriebenen Entwicklung ist zwischen dem Testen im Kleinen (Unit-Tests) und dem Testen im Großen (Systemtests, Akzeptanztests) zu unterscheiden, wobei Becks Methode auf Unit-Tests ausgelegt ist.

Testgetriebene Entwicklung mit Unit-Tests[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:

  1. 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.
  2. Ändere/schreibe den Programmcode mit möglichst wenig Aufwand, bis nach dem anschließend angestoßenen Testdurchlauf alle Tests bestanden werden.
  3. 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]

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]

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]

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. Als Übungsmethode werden oft Katas eingesetzt.

Werkzeuge[Bearbeiten]

Die testgetriebene Entwicklung braucht vordringlich

  • ein Werkzeug zur Build-Automatisierung wie etwa CruiseControl oder Jenkins
  • 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 oder Maven 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]

Konsequenz ist nötig[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]

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 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 anzuraten. Diese Testarten können jedoch nicht alle Fehler aufdecken, darum sollten in den meisten Fällen mehrere Testarten angewendet werden.

Literatur[Bearbeiten]

Weblinks[Bearbeiten]