Einführung in Acceptance Test-Driven Development (ATDD)
Automatisiertes Testen bildet einen festen Bestandteil jedes modernen Entwicklungsprozesses. Entwickler schreiben Tests mit dem Ziel, die Funktionalität einer Anwendung sicherzustellen. Diese Tests werden meist in einer Programmiersprache erstellt. Daraus ergibt sich zwangsläufig eine Lücke zwischen den eigentlichen Anforderungen (wie sie ein Kunde formulieren würde) und dem Testcode, der die Anforderungen prüft (wie ihn der Entwickler schreibt).
Acceptance Test-Driven Development (ATDD) schließt diese Lücke, indem Anforderungen weitestgehend in natürlicher Sprache formuliert werden, aber gleichzeitig ihre Ausführbarkeit sichergestellt wird. Ziel von ATDD ist es, verständliche, wartbare und gleichzeitig ausführbare Testspezifikationen zu schreiben. Dieser erste Teil unserer Serie zu ATDD führt in die Methodik und das allgemeine Vorgehen ein. Teil zwei wird sich dem konkreten Vergleich einzelner Tools widmen.
Obwohl die Testautomatisierung durch den Einsatz von Frameworks (wie z.B. JUnit oder TestNG) und Bibliotheken unterstützt wird, stellen sich viele die Frage, warum der Aufwand immer noch so hoch ist. Zur Klärung dieser Frage ist es hilfreich, einen Schritt zurück zu treten und sich darüber klar zu werden, warum Anwendungen getestet werden sollen und woher die Inhalte für die Tests stammen.
Der Sinn eines jeden Tests ist es, zu prüfen, ob die Anforderungen an die Anwendung, welche vom Kunden kommuniziert wurden, tatsächlich von der Implementierung umgesetzt werden. Die Testautomatisierung dient lediglich der kontinuierlichen Sicherstellung dieser Korrektheit. Dies ist insbesondere wichtig, da der Code sich während der Entwicklung ständig ändert und dadurch oft unvorhergesehene Effekte auftreten, welche bereits existierende Funktionalität beeinflussen. Die Prüfung der fachlichen Anforderung stellt also die wesentliche Information eines Testfalls dar, nicht dessen technischer Ablauf um sie zu prüfen. In der Praxis steht aber oft genau der technische Ablauf und nicht die fachliche Anforderung im Zentrum der Bemühungen. Der Testcode spezifiziert lediglich den Ablauf, die eigentlichen Anforderungen sind nur indirekt, wenn überhaupt erkennbar.
Das Ziel von Acceptance Test-Driven Development (oder kurz ATDD) ist es, genau diese Diskrepanz zwischen fachlichen Anforderungen (wie sie ein Kunde formulieren würde) auf der einen Seite und dem technischen Ablauf der Überprüfung der Anforderungen (wie sie ein Entwickler in einem Test manifestiert) auf der anderen Seite zu überwinden.
Der Einsatz von ATDD sowie verwandten Ansätzen wie beispielsweise „Specification by example“ oder „Behavior-Driven Development“ intensiviert den Austausch über die Anforderungen zwischen allen Akteuren der Anwendungsentwicklung (Kunden, Fachexperten, Programmierer und Tester). Dies dient nicht allein der verbesserten Kommunikation, sondern soll auch zwangsläufig einen direkten Bezug zur technischen Umsetzung herstellen. Anders ausgedrückt: Der Einsatz von ATDD führt dazu, dass die Anforderungen so erfasst werden, dass diese später direkt für die Qualitätssicherung, d. h. den Test der Software, genutzt werden können.
Anforderungen gemeinsam erfassen
Das mit ATDD verbundene Vorgehen erhöht die Vollständigkeit der Anforderungen. Das Wissen aller an der Anwendungsentwicklung beteiligten Akteure soll geschickt ausgenutzt werden. Hat der Fachexperte typischerweise ein sehr ausgeprägtes Domänenverständnis, so fehlt ihm wahrscheinlich der Blick für Grenzfälle (da diese in der Realität selten auftauchen). Der Entwickler erkennt solche Fälle jedoch zwangsläufig, da er im Verlauf der Implementierung festlegen muss, wie sich die Anwendung verhalten soll. Der Tester wiederum achtet darauf, dass alle notwendigen Schritte, z. B. zur Abarbeitung eines Szenarios, besprochen und festgelegt werden, da sie genau diese Schritte später formalisieren müssen.
Da die natürliche Sprache das Kommunikationsmittel darstellt, welches alle Beteiligten mit oder ohne technischen Hintergrund verstehen, erscheint die Beschreibung aller relevanten Merkmale einer Anwendung in Klartext, d. h. in natürlicher Sprache, an dieser Stelle das einfachste Mittel zu sein. Um zu vermeiden, dass der verfasste Text Mehrdeutigkeiten enthält und später die Vorstellungen im Rahmen der Realisierung wieder auseinander laufen, empfiehlt ATDD den Einsatz von Szenarien (aber auch Use Cases oder User Stories) im Zusammenspiel mit konkreten Eingaben und erwarteten Ausgaben. Die Funktionalität einer Anwendung wird somit anhand von anschaulichen Beispielen beschrieben, daher auch der Name „Specification by Example“. Besonders im agilen Umfeld sind die Beteiligten bereits mit dieser Form der Spezifikation vertraut, was einen Einstieg in ATDD entsprechend erleichtert und Brücken zu den agilen Tools und Methoden ermöglicht.
Wie sieht nun ein solches Szenario genau aus? Soll beispielsweise die Funktion eines Buchungssystems für Flüge beschrieben werden, so wäre die konkrete Eingabe der Name eines Passagiers sowie die Flugnummer. Die erwartete Ausgabe könnte das erstellte Flugticket sein, wobei dessen Ausstellung von den noch verfügbaren Sitzplätzen und der Sitzplatzkapazität der Maschine abhängt. Vereinfacht ausgedrückt könnte die Spezifikation eines solchen Szenarios demnach lauten:
„Versucht der Passagier Max Mustermann einen Platz auf dem Flug LH-1234 zu buchen, so erhält er ein gültiges Ticket“.
Je nach Testumgebung muss noch festgelegt werden, welcher Flugzeugtyp auf dem Flug zum Einsatz kommt und wie viele Passagiere bereits eingebucht wurden. Dies kann auf ähnliche Art und Weise ausgedrückt werden:
„Der Flug LH-1234 wird mit einer Maschine vom Typ Boing 737 durchgeführt. Es sind noch keine Passagiere auf dem Flug gebucht“.
Die Vorteile dieser Form der Spezifikationen liegen auf der Hand. Sie sind zum einen für alle Beteiligten einfach zu erstellen, da man ein Szenario direkt in natürlicher Sprache beschreiben kann. Zum anderen bleiben sie für alle Akteure verständlich und lesbar. Dies ist an sich keine neue Erkenntnis; die meisten Anforderungsdokumente werden auf genau diesem Weg (z. B. mit Microsoft Word) erstellt. Allerdings endet hier der Lebenszyklus einer Anforderung zumeist. Fachexperten, Programmierer und Tester gehen wieder getrennte Wege, das erstellte Anforderungsdokument wird nicht direkt weiter verwendet, sondern dient primär als Gedankenstütze für alle Beteiligten. Um die erarbeiteten Spezifikationen direkt weiter zu nutzen, müsste man sie ausführen, d. h. direkt als Testfälle nutzen können. An genau dieser Stelle setzen ATDD bzw. die entsprechenden Werkzeuge an. Sie ermöglichen es, die natürlichsprachlichen Szenariobeschreibungen auszuführen und direkt zum Test der Anwendung zu nutzen. Abb. 2 illustriert dieses Spannungsfeld.
Anforderungen ausführbar machen
Um die Beschreibung des oben skizzierten Szenarios (z.B. „Versucht der Passagier Max Mustermann einen Platz auf dem Flug LH-1234 zu buchen, so erhält er ein gültiges Ticket“) auszuführen, müssen wir dem Satz eine Bedeutung zuordnen. Das heißt, dass wir festlegen müssen welcher (Test-)Code dem Satz entspricht. Ein Ansatz dies umzusetzen ist es, jede Szenariobeschreibung direkt mit ein paar Zeilen Testcode zu verknüpfen (d. h. sie zu annotieren). Dieses Vorgehen stellt einen ersten wichtigen Schritt in die richtige Richtung dar, da Testcode und Testspezifikation nun direkt miteinander verbunden sind. Es hätte allerdings auch zur Folge, dass für jedes Szenario der Testcode erneut zu schreiben wäre und die Übersetzung manuell zu erfolgen hätte. Dies ist mit hohem Aufwand verbunden, kann zu Fehlern führen und ermöglicht keine Wiederverwendung des erstellten Testcodes über mehrere Szenarien hinweg.
Viele ATDD Tools treten dieser Problematik mit einem pragmatischen aber sehr wirkungsvollen Ansatz entgegen. Statt für jedes Szenario den Testcode erneut zu schreiben, wird die Übersetzung mit Hilfe von Übersetzungsmustern beschrieben. Diese können anschließend automatisch ausgeführt werden. Ein Übersetzungsmuster besteht dabei immer aus einem Muster für die Testspezifikation und einem Codefragment für den zugehörigen Testcode (dem sogenannten Support oder Glue Code). Damit der zugehörige Support Code ausgeführt werden kann, muss das Muster für die Testspezifikation zur Szenariobeschreibung passen.
Wenden wir uns wieder unserem Beispiel der Flugbuchung zu, so könnte beispielsweise das folgende Muster für die Testspezifikation definiert werden:
„Versucht der Passagier #NAME einen Platz auf dem Flug #FLUGNUMMER zu buchen, so erhält er ein gültiges Ticket“.
Die Platzhalter #NAME und #FLUGNUMMER lassen sich in der konkreten Spezifikationen durch beliebige Werte ersetzen, wodurch der Support Code parametrisierbar und wiederverwendbar wird. Aufgrund der Verwendung von variablen Platzhaltern können mit einem Muster auch mehrere Szenariobeschreibungen übersetzt werden. Des Weiteren können komplexe Testszenarien aus mehreren Sätzen aufgebaut sein, die dann mit unterschiedlichen Übersetzungsmustern in Testcode überführt werden.
Der Übersetzungsschritt wird nun je nach ATDD-Werkzeug entweder interpretativ oder mit Hilfe eines Codegenerators umgesetzt. Im ersten Fall liest das Werkzeug die Spezifikation ein, gleicht die Muster ab und führt dann direkt den Support Code der passenden Muster nacheinander aus (z. B. durch Reflection-Aufrufe). Kommt ein Codegenerator zum Einsatz, so wird der Support Code der passenden Muster zu einem Testfall zusammengesetzt. Der Testfall kann im einfachsten Fall ein normaler JUnit Test Case sein.
Was haben wir gelernt?
Diese erste Einführung in die Prinzipien von Acceptance Test-Driven Development verdeutlicht bereits, dass es mittels eines pragmatischen Ansatzes möglich ist, die Spezifikation von Anforderungen verständlicher und wartbarer zu gestalten und dabei gleichzeitig ausführbare Tests zu erhalten. Die Nutzung der natürlichen Sprache ermöglicht den Einbezug von Fachexperten in die Anforderungsspezifikation und macht die Ergebnisse der Anforderungsanalyse im Rahmen der Entwicklung unmittelbar nutzbar. Somit gelingt es, die Lücke zwischen einer Anforderung, wie sie ein Kunde formuliert, und einem Test, der diese Anforderung automatisiert überprüft, drastisch zu verkleinern. ATDD ist damit ein wichtiger nächster Schritt hin zum effizienteren Testen von Anwendungen.
Der zweite Teil unserer ATDD-Reihe befasst sich mit dem Thema Toolunterstützung. Wir vergleichen verschiedene Werkzeuge, welche sich technologisch aber auch konzeptionell unterscheiden, jedoch alle die Umsetzung von ATDD im Eclipse Umfeld ermöglichen.