Partner von:

Nachteile von Cucumber in der Praxis

Auf den ersten Blick macht Cucumber einen guten Eindruck. Erste kleine Beispiele lassen sich in der Regel auch schnell schreiben. Sobald allerdings die Zahl der Szenarien größer wird, erhöht sich mehr und mehr der Aufwand, die existierenden Szenarien zu warten und neue Szenarien zu erstellen. Der Aufwand entsteht durch zwei wesentliche Merkmale von Cucumber: Erstens müssen die Szenarien in einfachen Text-Dateien geschrieben werden und zweitens müssen passende reguläre Ausdrücke gefunden werden. Dadurch, dass Szenarien als Text-Dateien geschrieben werden müssen, sind Programmierer extrem eingeschränkt. Es können zum Beispiel keine Abstraktionsmechanismen verwendet werden, um z.B. Duplikation in Szenarien zu vermeiden. Sämtliche Daten eines Szenarios müssen dazu noch in der Regel als Text hartcodiert werden. Schlussendlich können auch nicht die gewohnten Features wie zum Beispiel Refaktorisierungswerkzeuge verwendet werden, die die Entwicklungsumgebung normalerweise bereitstellt.

Reguläre Ausdrücke haben das Problem, dass bei steigender Zahl auch die Wahrscheinlichkeit steigt, dass zwei reguläre Ausdrücke auf den gleichen Schritt passen. Insbesondere wenn man, um Duplizierung zu vermeiden, die Schritte stark parametrisiert. Reguläre Ausdrücke können oft auch recht kompliziert werden, was den Erstellungs- und Wartungsaufwand zusätzlich erhöht.

Wie oben schon beschrieben, sind die Erstellungs- und Wartungskosten von automatisierten Tests sehr hoch. Wenn diese Aufwände nun durch zusätzliche Hürden eines Frameworks weiter steigen, übersteigen diese meist den für den Kunden akzeptablen Rahmen. Selbst wenn die Kosten vom Kunden akzeptiert werden, sind es häufig die Software-Entwickler selbst, die das Framework wegen des hohen Zusatzaufwandes ablehnen. Dies kann dann dazu führen, dass das Framework nicht oder nur selten verwendet wird und stattdessen weiter normale Tests geschrieben werden, mit den schon oben erwähnten Nachteilen.

JGiven

In einem großen Java-Projekt wollten wir als TNG verhaltensgetriebene Software-Entwicklung einführen. Wir stellten recht schnell fest, dass Cucumber für uns wegen der besagten Nachteile nicht infrage kommt. Alle anderen Frameworks im Java-Umfeld hatten ähnliche Nachteile, benötigten teilweise andere Programmiersprachen wie zum Beispiel Groovy oder Scala oder hatten nicht die für uns notwendigen Features. Wir entschieden uns daraufhin ein eigenes Framework zu schreiben. Herausgekommen dabei ist JGiven: ein frei verfügbares, entwicklerfreundliches Open-Source-Framework zur verhaltensgetriebenen Softwareentwicklung in Java.

Ziele

Folgende Ziele wollten wir mit dem neuen Framework erreichen:

  • Es soll entwicklerfreundlich sein, das heißt Entwickler sollten es gerne benutzen und es nicht wegen eines erhöhten Aufwandes ablehnen
  • Die bekannte Given-When-Then-Notation von typischen BDD-Szenarien sollte beibehalten werden
  • Test-Code soll einfach wiederverwendet werden können, um Test-Code-Duplizierung zu vermeiden
  • Fachexperten sollen die Szenarien ohne Programmierkenntnisse lesen können
  • Es muss keine Programmiersprache außer Java benötigt werden

Ein erstes Szenario in JGiven

In JGiven werden Szenarien direkt als Java-Code geschrieben. Das Beispiel von oben sieht in JGiven
folgendermaßen aus:

Quelle: TNG

Das Beispiel zeigt eine JUnit-Testmethode, erkennbar an der @Test-Annotation, in der das JGiven-Szenario definiert ist. Neben JUnit kann auch TestNG für JGiven verwendet werden. Der Methodenname selbst ist die Beschreibung des Szenarios in Snake_Case-Schreibweise. Innerhalb der Testmethode werden dann die einzelnen Schritte des Szenarios in der Gegeben-Wenn-Dann-Notation geschrieben, jeweils wieder in Snake_Case.

Snake_Case

Für Java-Entwickler etwas ungewöhnlich und streng genommen auch gegen die Java-Code-Konventionen ist die Verwendung von Snake_Case in Methodennamen. Snake_Case ist essentiell für JGiven, da dadurch die korrekte Groß-Kleinschreibung verwendet werden kann, was entscheidend für die Lesbarkeit der generierten Berichte ist. In der Praxis hat sich die parallele Verwendung von CamelCase für normalen Java-Code und Snake_Case für Test-Szenarien als problemlos herausgestellt. Falls die Verwendung von Snake_Case wegen Projekt-Vorgaben unmöglich sein sollte, kann die korrekte Schreibweise mit der @As-Annotation angegeben werden. Dies ist auch nützlich, wenn Sonderzeichen im Bericht erscheinen sollen, die nicht in Java-Methodennamen erlaubt sind. Es kann auch CamelCase verwendet werden, wodurch allerdings die Groß- und Kleinschreibung von JGiven nicht immer korrekt erkannt werden kann.

Stage-Klassen

Wie in Cucumber ist es natürlich auch in JGiven nötig, die Implementierung der Schritte des Szenarios zu definieren. Die Klassen, in denen die Schritte definiert sind, heißen in JGiven Stage-Klassen. Ein Szenario in JGiven besteht in der Regel aus drei Stage-Klassen. Entsprechend der Gegeben-Wenn-Dann-Notation gibt es eine Klasse für die Gegeben-Schritte, eine für die Wenn-Schritte und eine für die Dann-Schritte. Diese Modularisierung der Szenarien hat insbesondere für die Wiederverwendung von Test-Code große Vorteile. Szenarien können so nach dem Baukastenprinzip aus den Stage-Klassen zusammengesetzt werden. Bewährt hat sich auch die Verwendung von Vererbung innerhalb der Stage-Klassen, bei denen speziellere, seltener gebrauchte Stages von allgemeineren, öfter gebrauchten Stages erben. Ein Szenario ist nicht auf drei Stage-Klassen beschränkt, sondern kann aus beliebig vielen Stages bestehen. Zusätzliche Stages können einfach mit der @ScenarioStage-Annotation in die Test-Klasse injiziert werden. Für das Beispiel benötigen wir drei Stage-Klassen: GegebenKaffeeautomat, WennKaffeeautomat und DannKaffeeautomat. Damit die Stage-Klassen in unserem Test verwendet werden können, muss die Test-Klasse von der von JGiven bereitgestellten SzenarioTest-Klasse erben und dessen Typ-Parameter entsprechend setzen:

Quelle: TNG

Wie sehen nun die Implementierungen der Stage-Klassen aus? Dazu schauen wir uns die GegebenKaffeAutomat-Klasse näher an:

Quelle: TNG

Das Beispiel zeigt mehrere wichtige Konzepte. Das Erste ist, dass Stage-Klassen in der Regel von einer von JGiven vordefinierten Klasse erben, in unserem Fall der Stufe-Klasse (in englischen Szenarien entspricht das der Stage-Klasse). Des Weiteren folgen Stage-Klassen dem Fluent-Interface-Pattern, d.h. jede Methode gibt als Rückgabewert das aufgerufene Objekt wieder zurück. Damit das Fluent-Interface-Pattern auch unter Vererbung korrekt funktioniert, wird außerdem ein Typ-Parameter an die Super-Klasse durchgereicht, der dem eigenen Typ der Klasse entspricht. Die self()-Methode liefert nun diesen Typ wieder zurück. Was die Annotation @ProvidedScenarioState bedeutet, wird nun im Folgenden erläutert.

Zustandstransfer

Ein typisches Szenario läuft in der Regel folgendermaßen ab: In der Gegeben-Stage wird ein bestimmter Zustand hergestellt. Auf diesen Zustand wird dann in der Wenn-Stage zugegriffen, eine Aktion ausgeführt und ein Ergebniszustand produziert. Schließlich wird in der Dann-Stage der Ergebniszustand ausgewertet. Um nun Zustände zwischen Stages zu transportieren, hat JGiven einen Mechanismus, der automatisch Felder von Stage-Klassen ausliest und in die folgende Stage schreibt. Felder, die mit @ScenarioState, @ProvidedScenarioState oder @ExpectedScenarioState annotiert werden, werden dabei berücksichtigt. Man kann den Mechanismus mit Dependency-Injection vergleichen, mit dem Unterschied, dass Werte nicht nur injiziert, sondern auch extrahiert werden. In unserem Beispiel wollen wir die Kaffeeautomat-Instanz für die folgenden Stages verfügbar machen und annotieren deswegen das entsprechende Feld mit @ProvidedScenarioState. Nachfolgende Stages können sich nun die Kaffeeautomat-Instanz injizieren lassen, indem sie ein entsprechendes Feld deklarieren und es mit @ExpectedScenarioState annotieren. Der Zustandstransfermechanismus von JGiven ist entscheidend für die Modularität. Dadurch, dass eine Stage nur deklariert welchen Zustand sie benötigt und nicht von welcher Stage der Zustand kommen muss, können beliebige Stages miteinander kombiniert werden, solange die jeweilig benötigten Zustände von den vorherigen Stages bereitgestellt werden.

Tags

Dem aufmerksamen Leser ist sicher aufgefallen, dass der Beispieltest mit @Story("COFFEE-1") annotiert ist. Dies ist keine vordefinierte JGiven-Annotation, sondern eine für das Beispiel definierte Annotation. Die @Story-Annotation ist selbst wiederum mit @IsTag annotiert, wodurch JGiven es als Tag erkennt. Tags erscheinen im generierten HTML-Bericht und ermöglichen es, Szenarien nach beliebigen Kriterien zu gruppieren, unabhängig von der Test-Klasse, in der sie definiert sind.

nach oben

Stipendiaten und Alumni von e-fellows.net können kostenlos oder ermäßigt zahlreiche Online-Kurse belegen oder an Seminaren teilnehmen.

Verwandte Artikel

Hol dir Karriere-Infos,

Jobs und Events

regelmäßig in dein Postfach

Kommentare (0)

Zum Kommentieren bitte einloggen.

Das könnte dich auch interessieren