Behaviour Driven Development mit Cucumber
- von Autor
Steffen Burkowitz, DHBW Student Angewandte Informatik bei sidion
Die Entwicklung von Software ist meistens mit einem viel zu kurzen Zeitrahmen verknüpft. Je näher die Deadline rückt, desto angespannter wird die Situation im Projekt. Sollten die Entwickler dann auch noch ihre wertvolle Zeit damit verbringen müssen, für jede Anforderung einen Test zu schreiben, wird die Zeit noch knapper.
Wäre es nicht schön, wenn ein Projekt schön entspannt ist? Einen Schritt in diese Richtung geht der Einsatz von Behaviour Driven Development mit dem Cucumber Framework. Wie dies funktioniert, wird im folgenden Artikel gezeigt.
Was ist BDD
Bei Behaviour Driven Development, kurz auch BDD handelt es sich um einen Softwareentwicklungsansatz. Der Ansatz basiert grundlegend auf Test Driven Development (TDD), fügt allerdings noch ein paar Aspekte hinzu.
Nutzt man TDD startet der Entwicklungsansatz mit dem Schreiben der Tests für eine Funktion, bevor der Code für diese geschrieben worden ist. Die Tests werden anhand der vorliegenden Anforderung ausgearbeitet und rufen Programmfunktionen auf, die zu dem Zeitpunkt noch nicht vorhanden sein müssen. Erst im zweiten Schritt werden alle fehlenden Funktionen erstellt bzw. angepasst, bis alle Tests erfolgreich sind. Anschließend kann der Code refaktorisiert und vereinfacht werden, um einen übersichtlicheren und besseren Quellcode zu erstellen. So können zum Beispiel eventuelle Doppelungen im Quellcode entfernt werden. Sollte durch das Refaktorisieren ein Test wieder fehlschlagen, muss der Code erneut angepasst werden.
Einer der von BDD hinzugefügten Ansätze ist die Verwendung einer domain-specific language (DSL). Diese wird pro Projekt erstellt und sollte die im Projekt verwendete ubiquitäre (allgemein verwendete, allgegenwärtige) Sprache darstellen. Sowohl die Softwareentwickler als auch die fachlichen Experten sollten die verwendeten Begriffe und Konstrukte eindeutig verstehen. Die fachlichen Experten sollen die Anforderungen mithilfe der ubiquitären Sprache (unserer DSL) als Szenarios beschreiben. Konkret benutzen sie dafür die Syntax der (im Fall von Cucumber) Gherkin Notation, um eine Anforderung als ein Szenario oder sofern benötigt mit mehreren Szenarien zu beschreiben.
Die Gherkin Notation benutzt dabei zur Beschreibung eines Szenarios drei Blöcke, aus denen sich ein flüssig lesbarer Text ergibt. Somit ähnelt die Struktur dem (klassischen) Aufbau von User Stories. Bei Gherkin lässt sich der Aufbau der Blöcke folgendermaßen beschreiben: Given [initial context], when [event occurs], then [ensure some outcomes]. Zuerst wird die Ausgangssituation beschrieben (Given), bevor die Aktion der Anforderung durchgeführt wird (When). Das Ergebnis der Aktion muss dann dem beschriebenen Zustand (Then) entsprechen.
Grundsätzlich ist BDD somit eine Zusammenarbeit von den fachlichen Experten und den Softwareentwicklern, um Anforderungen testgetrieben entwickeln zu können. Zusätzlich lassen sich die Tests direkt aus den Szenarios ableiten.
Wie funktioniert Cucumber?
Damit aus dem sprachlich formulierten Szenario ein Test wird, müssen die einzelnen beschriebenen Schritte mit einem Codefragment verknüpft werden. Genau dies ist die Aufgabe von Cucumber, einem der meistverbreiteten Frameworks für BDD derzeit. Ursprünglich unter Ruby entwickelt, ist es heute für viele weitere Programmiersprachen wie Java, Kotlin, C++, .NET und Python verfügbar.
Hinweis: In diesem Artikel wird immer Bezug auf die Cucumber-JVM (Java und Kotlin) Version genommen.
Bei Cucumber werden die Szenarios in sogenannten Feature Files verwaltet. Ein Feature File soll dabei exakt eine Anforderung abdecken und kann mehrere Szenarios enthalten. Der Aufbau dieser Datei ist dabei simpel gehalten.
Die Datei beginnt mit dem einleitenden Feature Keyword. Danach folgen die einzelnen Szenarios, welche entweder ein konkretes Beispiel sein können oder ein Scenario Template. Bei einem Scenario Template befinden sich in der Beschreibung Platzhalter für Variablen. Bei der Ausführung wird für diese jeweils ein Wert aus der zusätzlich angegebenen Beispieltabelle entnommen.
Sowohl das normale Szenario als auch das Scenario Template setzen sich aus einzelnen Steps zusammen. Diese Schritte werden jeweils von einem der Schlüsselwörter Given, When oder Then eingeleitet. Um einen flüssigeren Text zu ergeben, existieren auch die Schlüsselwörter And und But, die jeweils die Funktion des Schlüsselwortes in der Zeile davor übernehmen. Ebenso kann man eine der Übersetzungen der Schlüsselwörter verwenden, um Gherkin noch mehr an die ubiquitäre Sprache im Projekt anzupassen.
Der Hauptteil eines Steps ist innerhalb eines Projektes frei wählbar und sollte die ubiquitäre Sprache verwenden. Dadurch wird sichergestellt, dass alle Beteiligten die Anforderung eindeutig verstehen, denn ein Entwickler muss für jeden Step zuerst von Hand eine Stepdefinition anlegen. Cucumber bietet die Möglichkeit, mithilfe von Pattern Matching (reguläre Ausdrücke) den Step im Feature File mit einer Funktion im Programm zu verknüpfen. Innerhalb dieser Funktion muss der Entwickler den fachlich gewollten Schritt als Testschritt implementieren.
In einem Projekt baut sich mit jeder weiteren Anforderung eine Sammlung an Stepdefinitionen an. Der größte Vorteil daran ist, dass diese Stepdefinitionen in anderen Feature Files wiederverwendet werden. Sollte die Sammlung vollständig genug sein, kann ein fachlicher Experte direkt, nur durch das Schreiben der Anforderung, einen Softwaretest generieren. Ein Entwickler muss nicht mehr selbst überlegen, was getestet werden soll bei dieser Anforderung.
Allerdings funktioniert dies nur, wenn die Sammlung der Stepdefinitionen vollständig genug ist. Zu Beginn eines Projektes ist dies nicht der Fall und muss erst aufwendig angelegt werden. Es ergibt sich eine Aufwandsverlagerung für Testing an den Anfang eines Projektes, während der Aufwand dafür langfristig gesehen sehr klein wird.
Eine Idee, um den Aufwand zum Projektanfang etwas abzumildern ist, schon auf eine Grundsammlung an Stepdefinitionen zurückzugreifen. In dieser Grundsammlung sollten sehr allgemeine Stepdefinitionen sein, die sich sehr gut wiederverwenden lassen. Dies könnten zum Beispiel allgemeine Datenbankabfragen sein oder auch das Kopieren von Dateien. Die Projektentwickler müssen dann nur noch die projektspezifischen Schritte implementieren.
Jeder der Testschritte sollte eigenständig ausführbar sein. Bereitet ein Testschritt ein Ergebnis vor, so kann dieses zwischengespeichert werden. Dazu bietet es sich an, dies in einem World Objekt zu tun. Dieses ist ein simples Objekt mit allen eventuell benötigten Variablen. Pro Szenario wird ein neues Objekt instanziiert und so eventuelle Seiteneffekte von mehreren Tests in Folge minimiert.
Fazit
Behaviour Driven Development ist eine effektive Methode, um eine gute Testabdeckung bei einem länger laufenden Projekt zu erreichen. Der Aufwand für das Erstellen der Tests verlagert sich an den Beginn, kann aber durch das Benutzen von Stepsammlungen abgemildert werden. Zusätzlich ermöglich BDD eine direkte Kommunikation der Anforderungen von fachlichen Experten zu den Entwicklern, ohne erneut von einer Person interpretiert werden zu müssen. Dies hat allerdings auch den Nachteil, dass die fachlichen Experten sich auch auf BDD einlassen müssen. Cucumber lässt sich allerdings auch etwas entfremden und kann so als Tool zum Erstellen von Tests verwendet werden. Dabei würde ein Entwickler die Feature Files selbst schreiben und nicht der fachliche Experte.
Für mehr Informationen verweise ich an dieser Stelle auf die offizielle Dokumentation von Cucumber: https://cucumber.io/docs/cucumber/.