BDD mit Cucumber

Behavior Driven Development (BDD), auch Acceptance Test Driven Development (ATDD) genannt, bringt die testgetriebene Entwicklung näher an den Anwender.

Test Driven Development (TDD) richtet sich an Programmierer. Die Idee dabei, anstatt wie leider oft solange Quelltext zu schreiben bis die gesamte Funktionalität realisiert ist, und dann zu testen wird die Reihenfolge umgedreht. Zuerst werden die Funktionalitäten als Unittests formuliert und dann nur solange Quellcode geschrieben bis gerade diese Unittests erfüllt sind. Dabei wird iterativ vorgegangen. Das heißt es wird immer nur eine Anforderung bzw. ein Unittest auf einmal zusammen mit der Implementierung der dazugehörigen Funktionalität geschrieben. Erst wenn dieser Unittest erfolgreich durchläuft, wird der Unitttest für die nächste Funktionalität geschrieben.

Die Akzeptanztestgetriebene Entwicklung fordert die Erstellung der Tests durch den Anwender. Da der Anwender in der Regel kein Programmierer ist, wird als Sprache für die Tests statt Quelltext eine (möglichst) natürliche Sprache benötigt. Eine Testspezifikation kann dann zum Beispiel wie folgt aussehen:

Addition
  • Ich addiere die Zahlen 50 und 70.
  • Das Ergebnis ist 120.

Dieser Text kann je nach Testframework als Word-Dokument, HTML-Dokument oder reine Textdatei formuliert werden. Ein Programmierer schreibt dann den notwendigen Glue-Code der mit Hilfe der API des Testframeworks diese Testspezifikation mit den zu testenden System verbindet.

Bei der Verhaltensgetriebenen Entwicklung funktioniert das genauso, mit dem Unterschied, dass die Szenarios weniger frei formuliert werden. Die Formulierung folgt immer dem Gegeben-Wenn-Dann-Schema. Da obige Beispiel sieht dann so aus:

Addition
  • Gebenen sei dass ich die Zahl 50 eingegeben habe
  • Und dass ich die Zahl 70 eingegeben habe
  • Wenn ich Addieren drücke
  • Dann wird als Ergebnis 120 angezeigt

Diese Art der Formulierung ist in diesem einfachen Beispiel komplizierter und länger. Dennoch kann das starre Schema bei komplexen oder umfangreichen Anforderungen übersichtlicher oder besser organisiert wirken. Dank der Freiheit der „reinen“ ATDD-Frameworks kann natürlich auch dort das Gegeben-Wenn-Dann-Schema genutzt werden.

Cucumber

Als Stellvertreter für ein BDD-Framework möchte ich hier Cucumber vorstellen. Cucumber verwendet die Sprache Gherkin um Anforderungen zu formulieren. Das obige Beispiel, welches von der Startseite von cucumber.io stammt, sind darin so aus:

Addition in Gherkin formuliert

#language: de

Funktionalität: Addition
  Als ein mathematischer Idiot
  Möchte ich die Summe zweier Zahlen gesagt bekommen
  Um dumme Fehler zu vermeiden

  Szenario: Zwei Zahlen addieren
    Gegeben sei ich habe 50 im Taschenrechner eingegeben
    Und ich habe 70 im Taschenrechner eingegeben
    Wenn ich Addieren drücke
    Dann sollte das Ergebnis 120 auf dem Bildschirm erscheinen

Übersetzungen

Da die Testspezifikation in der Sprache der Anwender formuliert wird, muss auch Cucumber mehrere Sprachen sprechen. Die Schlüsselworte sind daher in derzeit 60 verschiedenen Sprachen nutzbar. Neben (fast) allen internationalen Sprachen, stehen auch Exoten wie Piratensprache und Klingonisch zur Auswahl. Wem normale Testspezifikationen zu langweilig sind, kann es ja mal mit „Dead men tell no tales“ statt „Beispiel“ versuchen …

Die gebräuchlicheren Sprachen dürften aber Englisch und Deutsch sein. Da die meiste Dokumentation im Internet auf Englisch ist und damit auch die englischen Schlüsselworte verwendet, hier die Gegenüberstellung zu den deutschen Schlüsselworten.

Englische und Deutsche Schlüsselworte von Gherkin
Englisch Deutsch Zweck
And Und Verknüpfung von Vorbedingung, Annahme oder erwartenden Ergebnis
Background Grundlage Vorbedingung für mehrere Szenarien
But aber identisch mit And/Und
Examples Beispiele Tabelle mit Beispieldaten
Feature Funktionalität Titel einer Funktionalität
Given Angenommen, Gegeben sei, Gegeben seien Formulierung von Vorbedingungen
Scenario Szenario Definiert ein Szenario
Scenario Outline Szenariogrundriss Template für ein Szenario in Verbindung mit einer Beispieltabelle
Then Dann Formulierung von erwartetes Ergebnissen
When Wenn Formulierung von Annahmen

Maven und Cucumber einrichten

Die Einrichtung von Maven für die Nutzung von Cucumber und das Ausführen der Testspezifikation mit JUnit ist im Beipsielprojekt bei GitHub dokumentiert. Dort ist das gesamte Projekt in allen Schritten wie ich sie im Folgenden beschreibe bis zum erfolgreichen Schluss in der Git-History nachvollziehbar.

Erster Lauf von Cucumber

Führen wir Cucumber zum ersten Mal aus, erhalten wir als Ergebnis einen Hinweis darauf, das alle auszuführende Schritte des Tests noch undefiniert sind. Logisch, da wir den dafür notwendigen Glue-Code noch nicht implementiert haben. Aber Cucumber gibt nicht nur diesen Hinweis aus, sondern schlägt auch eine Implementierung vor.

Das Ergebnis des ersten Laufs von Cucumber
Das Ergebnis des ersten Laufs von Cucumber

Zweiter Lauf von Cucumber

Für den zweiten Lauf übernehmen wir die von Cucumber vorgeschlagene Implementierung der Testschritte. Auch diesmal gibt es noch kein positives Ergebnis, da die vorgeschlagene Implementierung der Testschritte jeweils nur eine Ausnahme werfen. Die Darstellung ist jedoch anders. Cucumber sagt uns das der erste Schritt noch nicht implementiert ist (pending) und ignoriert die restlichen Schritte.

Der Vorteil gegenüber dem ersten Lauf ist, das im Ergebnis zu sehen ist, welche Aufgaben bereits erledigt sind. Bei vielen einzelnen Dokumenten mit Testspezifikationen und oder Szenarien, kann man so gut sehen, wie der Stand der Entwicklung ist.

Das Ergebnis des zweiten Laufs von Cucumber
Das Ergebnis des zweiten Laufs von Cucumber

Dritter Lauf von Cucumber

Für den dritten Lauf implementieren wir die Schritte der Testspezifikation gegen die Schnittstelle, wie wir sie uns wünschen. Für diese Schnittstelle existiert zu diesem Zeitpunkt noch keine Implementierung, so dass hier nur eine Stub-Implementierung existiert. Im Ergebnis sind nun alle vorbereitenden Schritte erfolgreich, aber das Ergebnis ist nun ein Fehlschlag. Ein Fehlschlag, weil das erwartetet Ergebnis nicht mit dem tatsächlichen übereinstimmt.

Besteht die Schnittstelle des System under Test (SUT) nicht wie hier aus einer konkreten Klasse, sondern aus einer abstrakten Klasse (Interface), dann kann ein Mocking-Framework, die noch nicht implementierten Systemteile ersetzen.

Das Ergebnis des dritten Laufs von Cucumber
Das Ergebnis des dritten Laufs von Cucumber

Vierter Lauf von Cucumber

Für den vierten und letzten Lauf wird das System, also die Addition in unseren Taschenrechner, implementiert. Wenn wir dabei keinen Fehler gemacht haben, ist der Lauf nun erfolgreich.

Das Ergebnis des vierten Laufs von Cucumber
Das Ergebnis des vierten Laufs von Cucumber

Links

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.