Beliebte Suchanfragen

Cloud Native

DevOps

IT-Security

Agile Methoden

Java

|
//

Automatisierte Akzeptanz-Tests mit JBehave

25.3.2011 | 12 Minuten Lesezeit

Einleitung

Im Open Source Bereich haben agile Teams mittlerweile die Auswahl zwischen einer Vielzahl von Werkzeugen für die Automatisierung von Akzeptanz-Tests. Dabei gibt es eine ganze Reihe unterschiedlicher Konzepte. FitNesse setzt für die Organisation der Testfälle z.B. auf ein integriertes Wiki, wohingegen das Robot Framework auf Keyword-getriebene Testentwicklung setzt, um hier einmal zwei der bekannteren Vertreter dieser Gattung zu nennen. Diese Werkzeuge bieten einen sehr großen Funktionsumfang, erfordern aber auch ein Stück weit mehr Einarbeitungszeit.

JBehave wirkt hier im Vergleich auf den ersten Blick (und auch auf den zweiten Blick) sehr schlank. Da gibt es auf der einen Seite die Testbeschreibungen im BDD-Format und auf der anderen Seite die Implementierung der Testfunktionalität in Java. Beides lässt sich leicht miteinander verbinden und dann mittels Maven ausführen.

Aufgrund der Länge dieses Artikels hier ein kurzer Überblick über die im Folgenden behandelten Themen:

  • Maven-Konfiguration: Erläutert die notewendige Maven-Konfiguration für die Implementierung und Ausführung von Tests mit JBehave.
  • Testbeschreibung: Zeigt wie Tests in JBehave mit Hilfe des BDD-Formats geschrieben werden.
  • Testimplementierung: Beschreibt die notwendigen Implementierungs-Schritte (in Java), um eine Testbeschreibung mit Leben zu füllen.
  • Reporting: Aufbereitung der Testresultate durch JBehave.
  • Zusammensetzen der Einzelteile: Maven, Testbeschreibung und Testimplementierung werden über bestimmte Namenskonventionen und Verzeichnisse zusammengefügt.
  • Fazit: Wie gut lassen sich Akzeptanz-Tests mit JBehave automatisieren? Für wen eignet sich das Werkzeug?
  • Download: Hier können alle Dateien aus dem Beispiel als ZIP-Datei heruntergeladen werden.

Logbuch Nachtrag vom 16.06.2012: Mein Kollege Andreas hat einen sehr ausführlichen Artikel über die diversen Konfigurationsmöglichkeiten von JBehave geschrieben , der sicherlich hilfreich ist nach dem Start :).

Maven-Konfiguration

Für Projekte im Java-Umfeld hat JBehave definitiv den großen Vorteil, dass eine Einbindung in die bereits vorhandene Entwicklungs-Umgebung des Teams sehr nahtlos funktioniert. Insbesondere wenn Maven bereits als Build-System im Einsatz ist. Bei anderen Werkzeugen funktioniert dies natürlich auch. Es sind hierbei jedoch oft kleinere oder größere Verrenkungen notwendig. Dies ist besonders dann der Fall, wenn die Werkzeuge initial in einer anderen Programmiersprache entwickelt wurden (und werden) und erst nachträglich um eine Unterstützung für Java erweitert werden.

Die folgende Maven-Konfiguration ist ausreichend, um mit der Entwicklung neuer Akzeptanz-Tests in JBehave zu starten und darüber hinaus später auch die notwendigen Artefakte herunterzuladen, welche für das Reporting benötigt werden:

1<?xml version="1.0" encoding="UTF-8"?>
2 
3<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
5 
6  <url>http://maven.apache.org</url>
7  <modelVersion>4.0.0</modelVersion>
8  <name>JBehaveDemo</name>
9 
10  <groupId>de.codecentric</groupId>
11  <artifactId>jbehave-demo</artifactId>
12  <packaging>jar</packaging>
13  <version>1.0-SNAPSHOT</version>
14 
15  <dependencies>
16    <dependency>
17      <groupId>org.jbehave</groupId>
18      <artifactId>jbehave-core</artifactId>
19      <version>3.1.2</version>
20    </dependency>
21    <dependency>
22      <groupId>junit</groupId>
23      <artifactId>junit</artifactId>
24      <version>4.4</version>
25    </dependency>
26  </dependencies>
27 
28  <build>
29    <plugins>
30 
31      <plugin>
32        <groupId>org.jbehave</groupId>
33        <artifactId>jbehave-maven-plugin</artifactId>
34        <version>3.1.2</version>
35        <executions>
36          <execution>
37            <id>run-stories-as-embeddables</id>
38            <phase>integration-test</phase>
39            <configuration>
40              <includes>
41                <include>**/*Scenarios.java</include>
42              </includes>
43              <ignoreFailureInStories>true</ignoreFailureInStories>
44              <ignoreFailureInView>false</ignoreFailureInView>
45           </configuration>
46           <goals>
47              <goal>run-stories-as-embeddables</goal>
48           </goals>
49         </execution>
50       </executions>
51     </plugin>
52 
53     <plugin> 
54       <groupId>org.apache.maven.plugins</groupId> 
55       <artifactId>maven-dependency-plugin</artifactId> 
56       <executions> 
57         <execution> 
58            <id>unpack-jbehave-site-resources</id>
59            <phase>generate-resources</phase> 
60            <goals> 
61               <goal>unpack</goal> 
62            </goals> 
63            <configuration> 
64               <overwriteReleases>false</overwriteReleases> 
65               <overwriteSnapshots>true</overwriteSnapshots> 
66               <artifactItems> 
67                  <artifactItem> 
68                     <groupId>org.jbehave.site</groupId> 
69                     <artifactId>jbehave-site-resources</artifactId> 
70                     <version>3.1.1</version> 
71                     <type>zip</type>
72                     <outputDirectory> ${project.build.directory}/jbehave/view</outputDirectory> 
73                   </artifactItem> 
74                </artifactItems> 
75            </configuration> 
76         </execution> 
77         <execution> 
78            <id>unpack-jbehave-reports-resources</id>
79            <phase>generate-resources</phase> 
80            <goals> 
81               <goal>unpack</goal> 
82            </goals> 
83            <configuration> 
84              <overwriteReleases>false</overwriteReleases> 
85              <overwriteSnapshots>true</overwriteSnapshots> 
86              <artifactItems> 
87                 <artifactItem> 
88                   <groupId>org.jbehave</groupId> 
89                   <artifactId>jbehave-core</artifactId> 
90                   <version>3.1.2</version> 
91                   <outputDirectory>${project.build.directory}/jbehave/view</outputDirectory> 
92                   <includes>**\/*.css,**\/*.ftl,**\/*.js</includes> 
93                 </artifactItem> 
94               </artifactItems> 
95             </configuration> 
96           </execution> 
97         </executions> 
98       </plugin> 			
99     </plugins>
100 
101  </build>
102</project>

Mit dieser Maven-Konfiguration werden nicht nur alle benötigten JAR-Dateien direkt heruntergeladen, sondern die Tests können auch direkt mittels „mvn integration-test“ ausgeführt werden. Hierzu benötigen wir aber natürlich noch eine Testbeschreibung und die zugehörige Implementierung.

Testbeschreibung

Testbeschreibungen werden in JBehave mit Hilfe der weit verbreiteten Given/When/Then-Syntax (BDD-Format) geschrieben. Dieses Format sieht wie folgt aus:

Given:Eine statische Vorbedingung
And:Eine weitere statische Vorbedingung
And:
When:Das zu testende Verhalten
And:Das weitere zu testende Verhalten
And:
Then:Das Ergebnis des Verhaltens unter den gegebenen Vorbedingungen
And:

Das BDD-Format hat den Vorteil, dass es auch von fachlichen Mitarbeitern gut gelesen werden kann. Dies ermöglicht ein gemeinsames Verständnis über die Tests zwischen dem Entwicklungsteam und dem Fachbereich/Kunden.

Das Schreiben von automatisierten Akzeptanz-Tests ist eine Aufgabe, die idealerweise in enger Zusammenarbeit zwischen fachlichen Experten und dem agilen Entwicklungsteam geschieht. Dies garantiert, dass alle notwendigen Kompetenzen vorhanden sind und ggf. erste Ergebnisse einer Test-Umsetzung schnell gemeinsam geprüft werden können. Eine für alle Beteiligten verständliche Beschreibung der Tests ist dabei natürlich eine Grundvoraussetzung.

In JBehave werden die Tests in sogenannten Story-Dateien beschrieben. Dabei ist es eine sinnvolle Konvention, dass in einer Story-Datei die Tests für eine User-Story beschrieben werden. Bereits für die Implementierung wird darauf geachtet, dass eine einzelne User-Story nicht zu umfangreich wird. Dies wird im Normalfall auch dazu führen, dass die Anzahl der Testfälle – in JBehave Scenarios genannt – überschaubar bleibt. Dies wird durch die sehr klare Struktur der Story-Dateien in JBehave zusätzlich unterstützt.

Um unser Beispiel möglichst einfach zu halten testen wir die vorhandene Stack-Implementierung von Java. Dies ermöglicht ein gutes Verständnis des Anwendungsfalls und auch der später benötigte Testcode bleibt übersichtlich, da es hier ja hauptsächlich um die Konzepte und Zusammenhänge gehen soll.

1Narrative:
2In order to develop an application that requires a stack efficiently
3As a development team
4I would like to use an interface and implementation in Java directly
5 
6Scenario:  Basic functionality of a Stack
7 
8Given an empty stack
9When the string Java is added
10And the string C++ is added
11And the last element is removed again
12Then the resulting element should be Java
13 
14Scenario:  Stack search
15 
16Given an empty stack
17When the string Java is added
18And the string C++ is added
19And the string PHP is added
20And the element Java is searched for
21Then the position returned should be 3

Am Anfang einer Story-Datei ist es möglich die getestete User-Story noch einmal zu beschreiben. Dies erfolgt nach dem Schlüsselwort Narrative:. Dabei bietet es sich natürlich an die Formulierung aus einer – hoffentlich bereits vorhandenen – User-Story zu übernehmen. Da dieser Text jedoch für den Test vollständig ignoriert wird, kann hier letzten Endes beliebiger Text stehen.

Gerade bei Tests im BDD-Format ist es sehr sinnvoll, dass diese in der eigenen Muttersprache geschrieben werden. In obigem Beispiel sind die Tests in Englisch geschrieben worden, da dies der Standard in JBehave ist. Es ist jedoch möglich durch die Implementierung einer zusätzlichen Klasse auf der Java-Seite die Schlüsselwörter auch in anderen Sprachen zu nutzen, z.B. Gegeben/Wenn/Dann.

Danach können beliebig viele Szenarien (=Testfälle) in BDD-Notation angegeben werden. Diese werden mit dem Schlüsselwort Scenario: eingeleitet. Danach folgt direkt in derselben Zeile eine kurze textuelle Beschreibung des Testfalls. Dann erfolgt die eigentliche Definition des Tests.

Testimplementierung

Das Mapping der Testbeschreibung auf die entsprechenden Testmethoden ist denkbar einfach. Jede Zeile in der Testbeschreibung (in JBehave sind dies Schritte/Steps) entspricht einer Methode in einer Testklasse, welche mit einer entsprechenden JBehave-Annotation markiert ist. Die Übergabe der Parameter erfolgt durch eine Namenskonvention ($variable) in den Annotationen. Diese müssen dann auch als Parameter in der Methode definiert sein.

1package de.codecentric.jbehave;
2 
3import java.util.Stack;
4import org.jbehave.core.annotations.*;
5import org.jbehave.core.embedder.Embedder;
6import junit.framework.Assert;
7 
8public class StackStories extends Embedder{
9 
10    private Stack testStack;
11    private String searchElement;
12 
13    @Given("an empty stack")
14    public void anEmptyStack() {
15        testStack = new Stack();
16    }
17 
18    @When("the string $element is added")
19    public void anElementIsAdded(String element) {
20        testStack.push(element);
21    }
22 
23    @When("the last element is removed again")
24    public void removeLastElement() {
25        testStack.pop();
26    }
27 
28    @When("the element $element is searched for")
29    public void searchForElement(String element) {
30        searchElement = element;
31    }
32 
33    @Then("the resulting element should be $result")
34    public void theResultingElementShouldBe(String result) {
35        Assert.assertEquals(testStack.pop(), result);
36    }
37 
38    @Then("the position returned should be $pos")
39    public void thePositionReturnedShouldBe(int pos) {
40        Assert.assertEquals(testStack.search(searchElement), pos);
41    }
42}

Die oben angegebene Klasse beinhaltet die sogenannten Schritte (Steps) aus der Testbeschreibung. Damit die Tests aus einer Story-Datei ausgeführt werden können wird noch eine weitere Klasse benötigt. Diese stellt über eine Namenskonvention die Verbindung zu der Story-Datei her und definiert des weiteren wie z.B. das Reporting erfolgen soll:

1package de.codecentric.jbehave;
2 
3import java.net.MalformedURLException;
4import java.net.URL;
5import java.util.List;
6 
7import org.jbehave.core.configuration.Configuration;
8import org.jbehave.core.configuration.MostUsefulConfiguration;
9import org.jbehave.core.io.LoadFromRelativeFile;
10import org.jbehave.core.junit.JUnitStory;
11import org.jbehave.core.reporters.StoryReporterBuilder;
12import org.jbehave.core.reporters.StoryReporterBuilder.Format;
13import org.jbehave.core.steps.CandidateSteps;
14import org.jbehave.core.steps.InstanceStepsFactory;
15import org.junit.Test;
16 
17public class StackScenarios extends JUnitStory {
18 
19    @Override
20    public Configuration configuration() {
21        URL storyURL = null;
22        try {
23            // This requires you to start Maven from the project directory
24            storyURL = new URL("file://" + System.getProperty("user.dir")
25                    + "/src/main/resources/stories/");
26        } catch (MalformedURLException e) {
27            e.printStackTrace();
28        }
29        return new MostUsefulConfiguration().useStoryLoader(
30                new LoadFromRelativeFile(storyURL)).useStoryReporterBuilder(
31                new StoryReporterBuilder().withFormats(Format.HTML));
32    }
33 
34    @Override
35    public List candidateSteps() {
36        return new InstanceStepsFactory(configuration(), new StackSteps())
37                .createCandidateSteps();
38    }
39 
40    @Override
41    @Test
42    public void run() {
43        try {
44            super.run();
45        } catch (Throwable e) {
46            e.printStackTrace();
47        }
48    }
49}

Die API von JBehave bietet noch einige weitere Möglichkeiten. Die obigen Klassen sind aber im Prinzip ausreichend um mit dem Testen zu starten. Ein Kritikpunkt hier ist definitiv die sehr schlechte JavaDoc-Dokumentation der API-Klassen.

Ein positiver Punkt ist, dass es nicht notwendig ist für jeden Schritt in der Testbeschreibung auch sofort eine Implementierung in Form einer passenden Methode bereit zu stellen. JBehave kann damit umgehen, dass die Implementierung der Schritte noch fehlt und markiert bei der Ausführung diese Schritte dann per Default als „Pending“ im Report. Auf diese Art und Weise ist es möglich die Testbeschreibungen bereits fertig zu stellen ohne auch sofort eine Implementierung (sei es auch nur in einer Dummy-Form) anzufertigen. Das Verhalten von JBehave in dieser Situation ist jedoch konfigurierbar und wenn es Sinn macht, kann eine fehlende Implementierung auch sofort als Fehler gewertet werden.

Natürlich ist es äußerst hilfreich wenn die Tests auch aus einer IDE (z. B. Eclipse) heraus ausgeführt werden können. Dies funktioniert bei JBehave sehr einfach, denn diese unscheinbare Methode in obiger Klasse:

1...
2    @Override
3    @Test
4    public void run() {
5        try {
6            super.run();
7        } catch (Throwable e) {
8            e.printStackTrace();
9        }
10    }
11...

macht es möglich die Tests direkt mittels JUnit auszuführen. Auf diese Möglichkeit bin ich eher zufällig beim Herumprobieren gestoßen, da ich dies nicht so explizit in der Dokumentation gefunden habe (oder es schlicht übersehen habe). So können die Test bei Bedarf z. B. sehr einfach im Debugger ausgeführt werden.

Reporting

Der folgende Screenshot zeigt den von JBehave erzeugten Report für die Ausführung der Tests:

Der Informationsgehalt ist nicht überragend aber ausreichend. Insbesondere ist das in dieser Art Report wichtige Erstellungsdatum in der Fußzeile vorhanden . Die beiden Links am Ende führen einmal zu einer Seite mit dem Inhalt der Story-Datei und zu einer textuellen Ansicht der Statistiken. Die gesamte Konfiguration des Reportings bietet noch weitere Möglichkeiten, aber da es hier erstmal um einen Überblick geht, soll dies für den Augenblick reichen.

Der Report selber findet sich nach der Testausführung im Eclipse-Workspace unter target/jbehave/view und heißt reports.html. Damit die Reports ansprechend aussehen, werden noch eine ganze Reihe Style-Dateien benötigt. Wir haben in der POM-Datei aber ja schon alle Einträge gemacht, um diese komfortabel mittels:

mvn generate-resources

herunterzuladen. Hiernach sollten im target/jbehave/view-Verzeichnis eine Reihe von Unterverzeichnissen auftauchen (ftl, images, …).

Zusammensetzen der Einzelteile

Für die Implementierung der Akzeptanz-Tests gibt es drei relevante Teile:

  • Maven-Konfiguration: Diese ist im Prinzip statisch, nachdem das Ganze einmal funktioniert.
  • Testbeschreibungen: Hier kommen für jede neue User-Story neue Story-Dateien hinzu. Ggf. werden bestehenden Szenario-Beschreibungen geändert oder erweitert.
  • Testimplementierung: Die Java-Klasse zum ansteuern der Tests ist eher statisch und für das Einbinden weiterer Stories würde hier wohl eine Basisklasse viel Sinn machen. Die Implementierung der eigentlichen Testschritte erfolgt parallel zur Entwicklung der Testbeschreibungen. Hier liegt die eigentliche Logik der Tests.

Es gibt einige Namenskonventionen, welche aufeinander abgestimmt werden müssen. So definiert der folgende Abschnitt aus der POM-Datei, welche Java-Klassen als Scenarios ausgeführt werden:

1...
2          <execution>
3            <id>run-stories-as-embeddables</id>
4            <phase>integration-test</phase>
5            <configuration>
6              <includes>
7                <include>**/*Scenarios.java</include>
8              </includes>
9              <ignoreFailureInStories>true</ignoreFailureInStories>
10              <ignoreFailureInView>false</ignoreFailureInView>
11           </configuration>
12           <goals>
13              <goal>run-stories-as-embeddables</goal>
14           </goals>
15         </execution>
16   ...

Anhand des Namens der „Scenario-Klasse“ – in diesem Fall StackScenarios.java – wird dann die passende Story-Datei gefunden. Diese muss den entsprechenden Namen haben, darf sich aber mit Unterstrichen und bei der Groß-/Kleinschreibung unterscheiden. In unserem Beispiel hat die Story-Datei den Namen stack_scenarios.story.

JBehave bietet verschiedene Möglichkeiten für die Suche nach den Story-Dateien. In unserem Beispiel geschieht dies einfach über einen absoluten Pfad, welcher das aktuelle Verzeichnis beinhaltet. Daher müssen die Tests aus dem Projekt-Verzeichnis ausgeführt werden. Dem Namen der Story-Datei wird als Pfad noch das Java-Package in dem sich die Scenario-Klasse befindet vorangestellt. Dies muss beim Speicherort der Story-Dateien bedacht werden.

In der Scenario-Klasse wird dann in der folgenden Methode definiert, in welcher Klasse (oder Klassen) sich die Schritt-Definitionen finden:

1...
2    @Override
3    public List candidateSteps() {
4        return new InstanceStepsFactory(configuration(), new StackSteps())
5                .createCandidateSteps();
6    }
7...

Die entsprechenden Klassen müssen über den Klassenpfad erreichbar sein, wobei man sich wohl schon anstrengen müsste, damit dies nicht der Fall ist.

Fazit

Bei der Länge dieses Artikels könnte der Verdacht aufkommen, dass es mit der in der Einleitung versprochenen Leichtgewichtigkeit doch nicht soweit her ist. Dies stimmt aber nicht, da das Beispiel sicherlich ca. 80%-90% der benötigten Rahmen-Implementierung beinhaltet, um mit einer Akzeptanz-Testsuite zu starten. Der Aufwand für die Implementierung spezieller Testfunktionalität hängt natürlich stark von deren Komplexität ab. Dies ist aber unabhängig vom eingesetzten Werkzeug immer der Fall. Ein Vorteil ist, dass Java-Entwickler mit JBehave vermutlich gut abschätzen können, was bereits durch bestehende Java-Bibliotheken gut unterstützt wird und wo mehr Aufwand in das Schreiben des Testcodes fließen muss.

Der Aufbau der Story-Dateien ist simpel und sauber und auch gibt es hier noch weitere Möglichkeiten wie z.B. die Nutzung von Test-Tabellen, um gewisse Szenarien mehrfach mit verschiedenen Testwerten zu durchlaufen. Auch ist es möglich durch Meta-Anweisungen in den Story-Dateien das Reporting zu beeinflussen, wenn dies gewünscht ist. Das Reporting wirkt auf den ersten Blick ausreichend und ist laut Dokumentation über weitere Konfiguration noch erweiterbar.

Die einfache Integration mit Maven und Eclipse ist eine große Stärke von JBehave und spricht sicherlich insbesondere Java-Entwickler sehr an. Die einfache Ausführung der Tests mittels JUnit (z. B. fürs Debugging) ist hier eine weiterer Pluspunkt.

Die Integration in eine CI-Umgebung (Kontinuierliche Integration) ist dank Maven problemlos möglich. Das Testen von Web-Anwendungen wird über eine eigene JBehave Web-Komponente unterstützt, welche auf Selenium aufsetzt oder man benutzt direkt die Java-Implementierung von Selenium.

Mein persönliches Fazit: Der Einstieg in JBehave macht Spaß und bietet recht wenig Frust-Momente (hauptsächlich beim Blick in die API-Dokumentation). Auch wenn ich nicht der größte Maven-Fan bin, so ist die Integration mit Maven hier schon sehr gut gelungen und hilfreich. Den Einsatz dieses Werkzeugs in einem echten Projekt könnte ich mir gut vorstellen. Natürlich gibt es noch einige weitere Dinge zu entdecken (Unterstützung anderer Sprachen in den Story-Dateien und Web-Testing wären für mich dabei die wichtigsten), welche hoffentlich in einem weiteren Blog-Beitrag noch genauer betrachtet werden können.

Download

Hier können alle benötigten Dateien für das Beispiel aus diesem Artikel als ZIP-Datei herunter geladen werden, um es z.B. in Eclipse zu importieren.

cc_JBehaveSample.zip

Das Projekt muss dann mit dem folgenden Kommando gebaut werden:

mvn compile

Die benötigten Style-Dateien für das Reporting können einmalig mittels des folgenden Kommandos erzeugt werden:

mvn generate-resources

Danach können die Tests wie folgt ausgeführt werden:

mvn integration-test

Alle Kommandos müssen aus dem Projekt-Verzeichnis ausgeführt werden in dem auch die pom.xml-Datei liegt.

|

Beitrag teilen

Gefällt mir

0

//

Weitere Artikel in diesem Themenbereich

Entdecke spannende weiterführende Themen und lass dich von der codecentric Welt inspirieren.

//

Gemeinsam bessere Projekte umsetzen.

Wir helfen deinem Unternehmen.

Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.

Hilf uns, noch besser zu werden.

Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.