zurück zum Artikel

Gwen: BDD-Framework fĂŒr lesbare und refaktorisierbare Tests in Java

Alexander Schwartz

Viele IDEs bieten gute Refactoring-Werkzeuge, jedoch nur wenige sind fĂŒr TestfĂ€lle in der domĂ€nenspezifischen Sprache Gherkin ausgestattet. Beim Einsatz des Gwen-Frameworks sind Tests lesbarer Java-Code, wodurch sich die Refactoring-Tools zum Schreiben von TestfĂ€llen nutzen lassen.

Gwen: BDD-Framework fĂŒr lesbare und refaktorisierbare Tests in Java

Viele IDEs bieten gute Refactoring-Werkzeuge, jedoch nur wenige sind fĂŒr TestfĂ€lle in der domĂ€nenspezifischen Sprache Gherkin ausgestattet. Beim Einsatz des Gwen-Frameworks sind Tests lesbarer Java-Code, wodurch sich die Refactoring-Tools zum Schreiben von TestfĂ€llen nutzen lassen.

In der Regel werden Entwickler beim Schreiben von Quellcode durch ihre Entwicklungsumgebung (IDE) unterstĂŒtzt: WĂ€hrend des Schreibens vervollstĂ€ndigt sie automatisch Methoden, zeigt deren Parameter an und bietet die Möglichkeit, per TastenkĂŒrzel Methoden und Klassen innerhalb eines Projekts umzubenennen.

Um wartbare und langlebige Software zu schreiben, muss der Quellcode verstĂ€ndlich und erweiterbar sein. Wird das Programm Schritt fĂŒr Schritt ausgebaut, ist der Quellcode bei Bedarf neu zu strukturieren, um verstĂ€ndlich und erweiterbar zu bleiben. Bei derartigen Neustrukturierungen, dem sogenannten Refactoring, werden beispielsweise Variablen umbenannt oder Quellcode in neue Methoden verschoben, um Wiederholungen zu vermeiden. Typsichere Sprachen wie Java vereinfachen den Prozess.

Die gleichen Anforderungen, die fĂŒr den Quelltext gelten, sind auch fĂŒr die TestfĂ€lle gĂŒltig. Sollte einer von ihnen nach einer CodeĂ€nderung nicht mehr laufen, muss fĂŒr Fachabteilung und Entwickler einfach und zuverlĂ€ssig erkennbar sein, von welchen Annahmen der Testfall ausgeht, was die fachliche Motivation ist und was der Test bezwecken möchte.

Um Sprachbarrieren zwischen Entwicklern und Fachexperten abzubauen, bieten sich FĂ€lle an, die das Verhalten des Systems aus Nutzersicht beschreiben. Ein Vorreiter dieses Behaviour Driven Development [1] (BDD) ist das Cucumber Test-Framework [2]. In der Sprache Gherkin werden darin Features in Szenarien aufgeteilt. Letztere sind, einer Satzschablone gleich, in der immer identischen Struktur Given/When/Then beschrieben:

Feature: Basic Arithmetic

In Order to avoid stupid mistakes
As a cautious person
I use a calculator.

Scenario: simple addition
Given a calculator I just turned on
When I add 4
And I add 5
Then the result is 9

FĂŒr jedes Feature der Anwendung werden unterschiedliche Szenarien beschrieben. In jedem sind jeweils der Ausgangszustand aus Sicht eines Nutzers (Given), danach eine oder mehrere durchgefĂŒhrte Aktionen (When) und schließlich die Nachbedingungen (Then) festgehalten. Diese Beschreibungen lassen sich je nach Entwicklungsvorgehen bereits vor der Umsetzung des Features als Akzeptanzkriterien durch die Fachabteilung formulieren. Die Struktur der Satzschablone stellt dabei sicher, dass Entwickler die Szenarien automatisieren können.

Die Features und Szenarien wertet das Cucumber Framework aus. FĂŒr jeden Schritt eines Szenarios mĂŒssen Entwickler eine Methode bereitstellen (Step Definitions), der die variablen Anteile des Schritts als Parameter ĂŒbergeben werden. So können mehrere Szenarien die Methoden verwenden und unterschiedliche Werte ĂŒbergeben:

public class CucumberSteps {
Calculator c;

@Given("^a calculator I just turned on$")
public void a_calculator_I_just_turned_on()
throws Throwable {
c = new Calculator();
}

@When("^I add (\\d+)$")
public void I_add(long number) throws Throwable {
c.add(number);
}

@Then("^the result is (\\d+)$")
public void the_result_is(long result)
throws Throwable {
assertThat(c.getState()).isEqualTo(result);
}
}

Moderne IDEs wie IntelliJ IDEA [3] zeigen durch Syntax-Highlighting fehlende Methoden an und ermöglichen das Springen vom Testschritt an die entsprechende Codestelle. Über Content Assist zeigen sie beim Schreiben von Code und Tests mögliche VervollstĂ€ndigungen an, unterscheiden bei den VorschlĂ€gen allerdings nicht zwischen Given, When und Then (Abb. 1). Insgesamt bleiben Content-Assist und Refactoring hinter den Möglichkeiten von Java-Code zurĂŒck. Ansonsten zeigt sich erst zur Laufzeit, ob regulĂ€re AusdrĂŒcke, Methoden-Annotationen und Features wie gewĂŒnscht zusammenspielen.

Content-Assist fĂŒr Gherkin in IntelliJ (Abb. 1)

Content-Assist fĂŒr Gherkin in IntelliJ (Abb. 1)

Die Entwickler des durch seinen Musikidentifikationsdienst bekannten Unternehmens Shazam Entertainment haben sich des Problems angenommen und das Micro-Framework Gwen [4] erstellt. Mit ihm lassen sich in Java TestfÀlle im Given/When/Then-Schema schreiben. Durch die Typsicherheit sind Content-Assist und Refactoring in jeder modernen IDE möglich. Gwen ergÀnzt andere Test-Frameworks und lÀsst sich einfach in bestehende Tests (zum Beispiel JUnit [5] und TestNG [6]) integrieren.

Im einfachsten Fall fĂŒgt man der Testklasse drei statische Imports Given/When/Then hinzu. Diesen Methoden ist zudem eine Referenz fĂŒr das zu testende System zu ĂŒbergeben. Ein JUnit-Test im BDD-Stil kann mit Gwen wie folgt aussehen (alle Quellen stehen im Übrigen auf GitHub [7] zur VerfĂŒgung):

import static com.shazam.gwen.Gwen.given;
import static com.shazam.gwen.Gwen.when;
import static com.shazam.gwen.Gwen.then;

/**
* In Order to avoid stupid mistakes
* As a cautious person
* I use a calculator.
*/
public class FeatureCalculator {

// The system under test (SUT)
private CalculatorSUT theCalculator =
new CalculatorSUT();

@Test
public void scenarioSimpleAddition() {
given(theCalculator).isTurnedOn();
when(theCalculator).adds(4).adds(5);
then(theCalculator).showsAsResult(9);
}

}

Vergleicht man das Format der Klasse mit dem ursprĂŒnglichen Feature im Gherkin-Format, erscheint die Klasse Ă€hnlich lesbar. Bei der Formulierung der Schritte lassen sich Parameter nur am Ende als Methodenparameter ĂŒbergeben. Um die Lesbarkeit zu erhalten, sollten Methoden idealerweise einen Parameter besitzen. Komplexe Übergabeparameter könnten Entwickler zum Beispiel via Builder-Pattern initialisieren.

Die drei statischen Methoden bauen darauf auf, dass die Klasse CalculatorSUT drei Methoden anbietet, die jeweils eine Instanz einer weiteren Klasse zurĂŒckliefern. Diese Klassen implementieren die Methoden, die fĂŒr die Given/When/Then-Schritte des Szenarios vorgesehen sind. Eine Arranger-Klasse ist fĂŒr das Bereitstellen aller Methoden rund um Given zustĂ€ndig, die Actor-Klasse fĂŒr die um When und eine Asserter-Klasse fĂŒr die um Then:

import com.shazam.gwen.collaborators.Actor;
import com.shazam.gwen.collaborators.Arranger;
import com.shazam.gwen.collaborators.Asserter;
import com.shazam.gwen.gwt.Given;
import com.shazam.gwen.gwt.Then;
import com.shazam.gwen.gwt.When;

import static org.fest.assertions.Assertions.assertThat;

public class CalculatorSUT implements
Given<CalculatorSUT.CalculatorArranger>,
When<CalculatorSUT.CalculatorActor>,
Then<CalculatorSUT.CalculatorAsserter> {

private Calculator device;

private CalculatorArranger arranger;
private CalculatorActor actor;
private CalculatorAsserter asserter;

public CalculatorSUT() {
device = new Calculator();
arranger = new CalculatorArranger();
actor = new CalculatorActor();
asserter = new CalculatorAsserter();
}

public CalculatorArranger given() {
return arranger;
}

public CalculatorActor when() {
return actor;
}

public CalculatorAsserter then() {
return asserter;
}

public class CalculatorArranger implements Arranger {

public void isTurnedOn() {
device = new Calculator();
}
}

public class CalculatorActor implements Actor {

public CalculatorActor adds(long val) {
device.add(val);
return this;
}

public CalculatorActor multipliesBy(long val) {
device.multiplyBy(val);
return this;
}

public CalculatorActor powerBy(long val) {
device.power(val);
return this;
}

}

public class CalculatorAsserter implements Asserter {

public void showsAsResult(long val) {
assertThat(device.getState()).isEqualTo(val);
}

}
}

Durch das Aufteilen der Schritte in die unterschiedlichen Klassen kann die IDE die Methoden passend zur getesteten Systemkomponente und zur Art des Schritts zur VervollstĂ€ndigung anzeigen (Abb. 2). Auch ein Springen vom Szenario in die Implementierung ist möglich. Im Gegensatz zu Gherkin lĂ€sst sich in Java eine Methode mit Refactoring umbenennen und von einer Methode in das Szenario zurĂŒckspringen.

Content-Assist fĂŒr Java in IDEA IntelliJ (Abb. 2)

Content-Assist fĂŒr Java in IDEA IntelliJ (Abb. 2)

FĂŒgt man statt einer bestehenden eine neue Methode ein, zeigt der Compiler der IDE mit einer roten Markierung einen Fehler an. Über einen Hotkey generiert die IDE auf Wunsch einen leeren Methodenrumpf in der passenden Klasse (je nach Kontext im Arranger, Actor oder Asserter).

Die TestfÀlle in Gherkin-Syntax zu Beginn des Artikels sind nun lesbarem Java-Code gewichen. Durch zusÀtzliche runde und geschweifte Klammern sieht der Testfall nun etwas technischer aus, das bewÀhrte Given/When/Then-Schema bleibt aber erhalten.

Der Vorteil von Gwen liegt zunĂ€chst bei den Entwicklern, da sie ihre gewohnte Java-Umgebung nicht verlassen mĂŒssen und eine gute Hilfestellung beim Schreiben von TestfĂ€llen bekommen. Werden die TestfĂ€lle mit der Anforderung spezifiziert, können die Entwickler sie in Java-Code mit den bewĂ€hrten Werkzeugen zur Testautomatisierung implementieren. Die UnterstĂŒtzung der IDE bietet beim Einsatz von Gwen nur die Methoden an, die zum Schritt (Given, When oder Then) und zum getestenen System passen. Effizientes Refactoring, um zum Beispiel Methoden umzubenennen, wird erst durch Java-Code möglich. Durch die Möglichkeit des Refactoring und die gute automatische VervollstĂ€ndigung kann sich im Laufe der Zeit aus den Methodennamen Schritt fĂŒr Schritt eine konsistente fachliche Sprache entwickeln.

Da im Gegensatz zu Cucumber mit Arranger, Actor und Asserter weitere strukturierende Elemente zum Einsatz kommen, sind mehr Klassen zu schreiben. Das relativiert sich allerdings in einer modernen IDE mit intelligenten Templates.

Mit Gwen ist es unwahrscheinlicher als bei Cucumber, dass die Fachseite den Code der Tests schreibt. WĂ€hrend bei dessen Verwendung ein nicht implementierter Schritt im Testergebnis als "undefined" gemeldet wird, sind die Methoden bei Gwen zumindest mit einem fail() umzusetzen.

Trotzdem kann die Fachseite wie gewohnt im Given/When/Then-Schema TestfÀlle zusammen mit den Anforderungen definieren und darauf vertrauen, diese Struktur bei den implementierten TestfÀllen wiederzufinden. Bei der Verfeinerung der TestfÀlle bietet sich ein gemeinsames Implementieren im Sinne des Pair Programming an.

Im zuvor bemĂŒhten Beispiel mit dem Taschenrechner lassen sich mehrere Aktionen hintereinander in einer Zeile via Method Chaining aufrufen, da die einzelnen Methoden die aktuelle Instanz zurĂŒckliefern.

Wenn in einer Anwendung zwischen Dialogen gewechselt wird, ist es sinnvoller, eine Referenz eines weiteren Teils des zu testenden Systems zurĂŒckzugeben. Im Verlauf kann die Testmethode diese mit Given/When/Then benutzen.

Kombiniert man Gwen mit Arquillian Graphene [8], einem Werkzeug zum Testen von Webseiten, sehen die TestfÀlle wie unten dargestellt aus. Das bewÀhrte Konzept des Page Object Pattern [9] lÀsst sich hier mit BDD kombinieren.

@RunWith(Arquillian.class)
public class StartPageFeature {

@Drone
protected WebDriver browser;

@Page
private StartPage startPage;

@Test
public void szenarioSearchWithMatches() {
given(startPage).isOpenedInBrowser();
ResultPage resultPage =
when(startPage).searchesFor("bdd");
then(resultPage).showsAResultCountOf(3);
}

}

Die Möglichkeit, Gwen mit anderen Testframeworks zu kombinieren, besteht nur deshalb, weil es keinen eigenen Test-Runner mitbringt. Shazam setzt es beispielsweise mit Robotium (zukĂŒnftig Espresso) fĂŒr Android-TestfĂ€lle ein (vgl. Beispiele von Shazam).

Damit unterscheidet sich Gwen grundsÀtzlich von allen anderen BDD-Testframeworks, die alle einen Runner enthalten.

In grĂ¶ĂŸeren Projekten werden die Arranger-, Actor- und Asserter-Klassen, die im weiter oben gezeigten lĂ€ngeren Beispiel innere Klassen sind, als eigenstĂ€ndige Klassen im gleichen Package abgelegt. Sind sie immer noch zu groß, lĂ€sst sich jeder Schritt in einer eigenen Klasse als Command implementieren. Gwen stellt hierfĂŒr die drei Interfaces Action, Arrangement und Assertion bereit. Ein ausfĂŒhrliches Beispiel [10] dazu findet sich ebenfalls bei Shazam.

Insgesamt besteht das Gwen aus nur einer Klasse mit statischen Methoden und neun Interfaces, die alle in diesem Artikel vorgestellt wurden.

Obwohl das Micro-Framework noch sehr jung ist, spricht viel fĂŒr seinen Einsatz: Es ist als Quellcode verfĂŒgbar, hat keine weiteren AbhĂ€ngigkeiten und vertrĂ€gt sich gut mit einer vorhandenen Test-Infrastruktur. Da es vergleichsweise klein und in sich abgeschlossen ist, muss man sich mit Blick auf Weiterentwicklung und Fehlerbehebung nicht auf die ursprĂŒnglichen Autoren verlassen. Zudem kommt das Framework nur bei der Entwicklung und AusfĂŒhrung der Tests zum Einsatz und stellt keinen Teil des Endprodukts dar. Es hat also keinerlei Einfluss auf das Laufzeitverhalten bestehender Anwendungen.

Gwen besticht auf den ersten Blick durch die bessere UnterstĂŒtzung der Entwickler beim Schreiben und Refactoring ihres Test-Codes. Gleichzeitig senkt es die HĂŒrde, BDD-TestfĂ€lle zu schreiben, da es sich mit bestehenden Frameworks kombinieren lĂ€sst.

Die Fachseite profitiert von wartbaren TestfĂ€llen, die sich bei Erweiterungen und Änderungen der Fachlichkeit anpassen lassen. Die TestfĂ€lle bleiben lesbar und eine RĂŒckverfolgung der implementierten Funktionen zu den TestfĂ€llen und der dort beschriebenen fachlichen Anforderung ist leichter möglich. Mit der Zeit kann sich dank Refactoring und automatischer VervollstĂ€ndigung eine gemeinsame Sprache entwickeln. Dem Einstieg in Behaviour-Driven Development steht damit nichts mehr im Wege.

Alexander Schwartz
ist Principal IT Consultant bei der msg systems ag [11] in Frankfurt. Im Laufe der Zeit arbeitete er mit unterschiedlichen Webtechniken. Er schÀtzt agile Projekte und automatisierte Tests.
(jul [12])


URL dieses Artikels:
https://www.heise.de/-2520872

Links in diesem Artikel:
[1] http://de.wikipedia.org/wiki/Behavior_Driven_Development
[2] http://cukes.info/
[3] https://www.jetbrains.com/idea/
[4] https://github.com/shazam/gwen
[5] http://junit.org/
[6] http://testng.org/
[7] https://github.com/ahus1/bdd-examples
[8] http://arquillian.org/modules/graphene-extension/
[9] https://docs.jboss.org/author/display/ARQGRA2/Page+Objects
[10] https://github.com/savvasdalkitsis/bdd-with-gwen
[11] http://www.msg-systems.com/
[12] mailto:jul@heise.de