zurück zum Artikel

Integrationstests mit JBoss Arquillian 1.0

Markus Eisele

Das Testen von Container-Komponenten im Java-EE-Umfeld ist eine anstrengende Sache. Denn automatisierte Testroutinen muss man sich mühselig selbst basteln. Mit der nun freigegebenen Version 1.0.0 des JBoss-Projekts Arquillian steht nun eine erste komplette Suite für Integrationstests zur Verfügung.

Das Testen von Container-Komponenten im Java-EE-Umfeld ist eine anstrengende Sache. Denn automatisierte Testroutinen muss man sich mühselig selbst basteln. Mit der nun freigegebenen Version 1.0.0 des JBoss-Projekts Arquillian steht nun eine erste komplette Suite für Integrationstests zur Verfügung.

Verglichen mit der Aufgabe der Arquillianer [1] im Film "Men in Black" scheint die Mission des JBoss-Projekts Arquillian einfach. Man will kein Universum retten, sondern bloß einen einfachen Testrahmen bereitstellen, der alle Aufgaben des Container-Lebenszyklus und -Deployments vom Testaufbau separiert. Die Entwickler sollen eine breite Palette an Integrationstests für ihre Java-EE-Anwendungen erhalten. Besonders das isolierte Überprüfen fachlicher Komponenten war bisher teilweise unmöglich, da einfache Unit-Tests dafür vielfach nicht ausreichen. Ein Hauptgrund dafür liegt im Design der Komponenten, sie arbeiten nur selten vollständig autonom. Abhängigkeiten bestehen in der Regel zur Ablaufumgebung (Runtime) und zu anderen Komponenten.

Diese Aspekte sind für das Funktionieren eines Programmbestandteils jedoch genauso wichtig wie die fachlichen. Selbst bei sauberer Schichtentrennung benötigt man eine Kombination aus sogenannten Mocks (Schnittstellensimulationen) und Unit-Tests, um zumindest die Integrationsschnittstellen zu prüfen. Trotz aller Mühe bleibt das tatsächliche Verhalten der Komponente im Zielcontainer vollständig ungetestet. Weder Transaktionskontrolle, Dependency Injection noch andere, deklarative Dienste lassen sich ausreichend auf korrekte Funktion abklopfen. Meist bleibt es bis zuletzt offen, ob zur Laufzeit tatsächlich die gewünschten Dienste gebunden werden und die übergebenen Daten im richtigen Format ankommen. Das sicherzustellen ist allerdings ein wesentlicher Bestandteil von Entwicklungsprozessen, den man als Integrationstest bezeichnet.

Die Kunst, einen automatisierten Integrationstest zu entwerfen und regelmäßig durchzuführen, beherrschen nur wenige Entwickler, eine Tatsache, die viele Projekte instabil macht. Arquillian dämmt diese Schwierigkeiten ein. Das Framework ermöglicht Entwicklern das einfache Testen von Integrationsaspekten der in Containern ausgeführten Komponenten. Dabei ist es egal, ob es sich um einen lokalen (embedded) oder entfernten (remote/managed) Container handelt.

Mit Arquillian sind Integrationstests also ebenso einfach handhabbar wie Unit-Tests. Das gelingt, weil zum Testen der konkrete Container selber und nicht eine künstliche Laufzeitumgebung verwendet wird. Dabei ist die Geschichte von Arquillian an sich schon spannend. Angefangen hat alles 2009 als Test Harness für die CDI-Spezifikation (Context und Dependency Injection; JSR 299). Daraus hat sich das alleinstehende Arquillian-Projekt [2] entwickelt, das eine vollständige Testplattform bietet, die auch das kommende CDI 1.1 (JSR 346) wiederverwenden wird.

Arquillian verbindet als übergeordnete Instanz ein Unit-Testing-Framework (JUnit oder TestNG), ShrinkWrap (JBoss Packaging for Java) sowie ein oder mehrere Container (Java EE Container, Servlet-Container, Java SE CDI et cetera, siehe Abbildung). Im Kern stellt Arquillian eine benutzerdefinierte Testumgebung (Test Runner) für JUnit beziehungsweise TestNG zur Verfügung. Sie delegiert die Kontrolle des Testlebenszyklus an die jeweiligen lokalen oder entfernten Container. Ein Arquillian-Testfall sieht fast genauso aus wie einer unter JUnit oder TestNG. Er kümmert sich um drei Dinge: den Zielcontainer für den Testfall, das Deployment und den Transport des Archivs in den Container.

Arquillian führt verschiedene Komponenten zusammen. Im Kern stellt das Framework eine Testumgebung für JUnit bzw. TestNG bereit.

Sowohl das Steuern des Containers als auch das Deployment haben nichts Magisches an sich, das Verpacken eines einfachen Testfalls in ein vom Container akzeptiertes Format hingegen schon. Letzteres erledigt ShrinkWrap, die Komponente, die als erste im Testlebenszyklus startet. Sie legt ein geeignetes Archiv zur Laufzeit an und richtet es auf dem Zielcontainer ein. Im Anschluss stößt sie das Deployment an, und die Testfälle werden durchgeführt. Abschließend wird das Archiv wieder vom Container entfernt. Als Runtime-Umgebungen kommen mittlerweile ein gutes Dutzend Produkte infrage, darunter JBoss AS (ab Version 4.2), Oracle GlassFish (ab 3.1), Jetty (ab 6.1), Tomcat (ab 5.5), Weld SE und EE 1.0/1.1, Apache OpenWebBeans 1.0, Apache OpenEJB 3.1, WebSphere 7 und 8 sowie WebLogic Server (ab 10.3)

Startpunkt für die ersten Gehversuche mit Arquillian ist die neue Arquillian-Website [3]. Neben einer Menge von Schnelleinstiegen [4] und Dokumentationen findet man dort auch den direkten Weg zum Sourcecode, der auf GitHub [5] öffentlich verfügbar ist. Neben Maven 3 gehört auch ein JDK 1.5 oder besser neuer zu einer aktuellen Entwicklungsumgebung, wie Arquillian sie voraussetzt.

Um zu erleben, wie sich Arquillian tatsächlich anfühlt, bietet sich direkt der Start mit einem neuen Maven-Projekt an. Am einfachsten lässt sich das mit folgendem Befehl anlegen:

mvn archetype:generate -B -DarchetypeGroupId=
org.codehaus.mojo.archetypes -DarchetypeArtifactId=
webapp-javaee6 -DgroupId=de.ix.developer -DartifactId=
arq-demo -Dversion=1.0-SNAPSHOT

Er generiert ein rudimentäres Java-EE-6-Web-Profile-Projekt. Nach dem Öffnen der pom.xml ist zuerst die javax:javaee-api-Abhängigkeit gegen die Java EE 6 API von JBoss zu ersetzen. Das liegt daran, dass in dem offiziellen Paket teilweise verkürzte Methoden enthalten sind, die beim Testen zu diversen Fehlermeldungen führen können. Die von JBoss gepackte API hat diese Probleme nicht. Auch JUnit ist mindestens in der von Arquillian benötigten Version 4.8 einzutragen.

<dependency>
<groupId>org.jboss.spec</groupId>
<artifactId>jboss-javaee-6.0</artifactId>
<version>1.0.0.Final</version>
<type>pom</type>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.1</version>
<scope>test</scope>
</dependency>

Von Arquillian wird dann noch der sogenannte BOM (Bill Of Materials) in der Sektion <dependencyManagement> eingetragen.

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.jboss.arquillian</groupId>
<artifactId>arquillian-bom</artifactId>
<version>1.0.0.Final</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>

Diese stellt quasi die Versionsmatrix für alle transitiven Abhängigkeiten von Arquillian bereit. In die normalen Abhängigkeiten kommen jetzt noch weitere hinzu. Beginnend mit der JUnit-Integration

<dependency>
<groupId>org.jboss.arquillian.junit</groupId>
<artifactId>arquillian-junit-container</artifactId>
<scope>test</scope>
</dependency>

Direkt gefolgt von dem benutzten Container sowie dem zugehörigen Container-Adapter, in diesem Beispiel der Embedded GlassFish:

<dependency>
<groupId>org.jboss.arquillian.container</groupId>
<artifactId>arquillian-glassfish-embedded-3.1</artifactId>
<version>1.0.0.CR3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.main.extras</groupId>
<artifactId>glassfish-embedded-all</artifactId>
<version>3.1.2</version>
<scope>test</scope>
</dependency>

Es fällt auf, dass der arquillian-glassfish-embedded-3.1-Container noch nicht in der Version 1.0 vorliegt. Gleiches gilt auch für andere wie den WebLogic-Container. Beide sollen in den kommenden Wochen ebenfalls fertig vorliegen.

Der am häufigsten geforderte Anwendungsfall ist das Testen von Enterprise Java Beans (EJBs). Eine einfache Stateless Session Bean könnte so aussehen:

package de.ix.developer; 
import javax.ejb.LocalBean;
import javax.ejb.Stateless;

@Stateless
@LocalBean
public class HelloBean {

public String greet(String name) {
return "Hallo " + name;
}
}

Hier passiert offensichtlich nichts Magisches. Dennoch ist das Beispiel ein einfaches und gutes Beispiel, das sich in einem Container testen lässt. Also geht’s direkt zum eigentlichen Testfall. Das Maven-Projekt muss jetzt unter src die Struktur test\java\de\ix\developer anlegen. Darin nimmt ein einfacher JUnit-Test Platz:

package de.ix.developer;

import javax.ejb.EJB;
import junit.framework.Assert;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(Arquillian.class)
public class HelloBeanTest {

@EJB
HelloBean hb;

@Deployment
public static WebArchive createDeployment() {
return ShrinkWrap.create(WebArchive.class,
"simple.war").addClass(HelloBean.class)
.addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml");
}

@Test
public void sollteGruessen() {
Assert.assertEquals("Hallo Developer!", hb.greet("Developer"));
}
}

Per @EJB wird unser wenig magisches HelloBean injiziert, und die sollteGruessen()-Methode ist per Junit-Annotation @Test dazu da, das Ergebnis gegen einen festen String zu vergleichen. Besonders hierbei ist die createDeployment()-Methode. Sie ist mit @Deployment-Annotation von Arquillian versehen, und wird von der Technik im Rahmen des Test-Lifecycles aufgerufen. Sie gibt ein mit ShrinkWrap gepacktes Archiv zurück (in diesem Fall simple.war). Dieses wird im Rahmen des Arquillian-Lifecycles auf dem konfigurierten Container eingespielt. Den Test kann man via Maven oder auch aus der IDE ausführen. Das Kommando mvn test liefert eine längliche Ausgabe, der zu entnehmen ist, dass der de.ix.developer.HelloBeanTest ausgeführt wird und der GlassFish Server startet:

Information: GlassFish Server Open Source Edition 3.1.2 (java_re-private)
startup time : Embedded (653ms), startup services(373ms), total(1.026ms)

Das Ergebnis

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 

dokumentiert, dass der Test erfolgreich durchlaufen wurde. Klappt es nicht auf Anhieb, wird es noch ein wenig undurchsichtig. Arquillian ist nämlich recht schweigsam, und einige Exceptions sind nicht wirklich hilfreich bei der Fehlersuche. Besonders häufig dürfte falsches Packaging in der @Deployment-Methode einer der Gründe für Fehler sein. Auch die Maven-Abhängigkeiten sind eine Fehlerquelle. Während der Einsatz des Embedded GlassFish bei Version 3.0 und 3.1 sowie 3.1.2 gut funktioniert, ist der Einsatz der Version 3.1.1 derzeit nicht möglich. Hauptgrund ist hier allerdings nicht Arquillian, sondern das Packaging der GlassFish-Abhängigkeit. Vielfach hilft hier nur, sich ein wenig tiefer mit der Konfiguration zu beschäftigen. Bisher wurde Arquillian im Beispiel nicht weiter konfiguriert, sondern lief quasi mit den Standardeinstellungen. Die Konfigurationsdatei arquillian.xml ist dafür die entsprechende Stelle. Sie gehört in das Standardpaket im Verzeichnis src\test\resources und sollte minimal so aussehen:

<?xml version="1.0"?>
<arquillian xmlns="http://jboss.com/arquillian"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://jboss.org/schema/arquillian
http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
<engine>
<property name="deploymentExportPath">target/</property>
</engine>
</arquillian>

Alle weiteren Konfigurationen lassen sich ebenfalls im Container-Tag vornehmen werden. Für den Embedded GlassFish und alle weiteren Container sind diese in der zugehörigen und mittlerweile recht umfangreichen Projektdokumentation [6] einzusehen. Neben so einfachen Dingen wie veränderten Ports lässt sich damit auch eine sunResourcesXml angeben, um Datenquellen oder Ähnliches während des Deployments zu konfigurieren. Die im minimalen Setup angegebene Engine Property sorgt darüber hinaus dafür, dass im target-Verzeichnis des Projekts eine _DEFAULT___DEFAULT__simple.war entsteht. Diese stellt exakt das Deployment dar, das Arquillian auf dem Container umsetzt.

Das kann vor allem beim weiteren Debugging hilfreich sein. Gegebenenfalls lässt sich das Archiv nämlich ohne Probleme in einem Stand-alone-GlassFish einrichten, mit der Embedded-Version gelingt das gelegentlich nicht, auch wenn kein Programmierfehler vorliegt. Die Embedded-Version des GlassFish ist seit Version 3.0 zwar viel stabiler und funktional umfangreicher geworden, dennoch gibt es im Vergleich mit dem kompletten Server einige Unterschiede.

Ähnlich zum bereits dargestellten Vorgehen lassen sich beliebige Containerfunktionen testen. Zugegebenermaßen sind Stateless EJBs der einfachste Fall. Komplizierter wird es, wenn die Runtime-Umgebung Ressourcen wie Datenbanken oder Schnittstellen benötigt. Allerdings ibesteht hier vielfach die Herausforderung darin, den Embedded Container geeignet zu konfigurieren. Für Arquillian macht das keinen großen Unterschied. Im Gegensatz zu früheren Alpha-Versionen unterstützt der Embedded GlassFish im Zusammenspiel mit Arquillian jetzt auch die Java Persistence API (JPA). Grundsätzlich ist damit alles für Enterprise-Java-Anwendungen Relevante testbar.

Bis hierher befasste sich der Artikel mit dem funktionalen Test eines Programms. Grundsätzlich will man aber auch wissen, wie Clients (Menschen oder Programme) mit der Anwendung zurechtkommen. Dritte können mit ihr über Schnittstellen, etwa Webservices, Remote EJBs oder via HTTP kommunizieren. Dazu muss die Applikation allerlei Dinge bereitzustellen, etwa Objekt-Serialisierungen und Netzverbindungen. Für das Testen in diesem Umfeld stellt Arquillian einen zweiten Modus bereit. Dieser auch "Client Mode" genannte Ausführungsmodus unterscheidet sich nur marginal von dem beschriebenen "In-Container Mode".

Der Client-Modus lässt sich auf zwei Arten aktivieren. Entweder durch explizites Kennzeichen der Deployment-Methode mit @Deployment(testable = false), was Arquillian daran hindert, das Deployment spezifischen Informationen anzureichern, oder durch das Hinzufügen der Annotation @RunAsClient am eigentlichen @Test. Dabei lassen sich In-Container- und Client-Tests auch in einer Klasse kombinieren. Im Client Mode kann man auch Drone verwenden. Diese bisher wichtigste und größte Erweiterung von Arquillian stellt eine Abstraktion über Web-Testframeworks wie Selenium und WebDriver dar. Um sie zu verwenden, ist die <dependencyManagement>-Sektion in der pom.xml zuerst um den Drone BOM zu erweitern.

<dependency>
<groupId>org.jboss.arquillian.extension</groupId>
<artifactId>arquillian-drone-bom</artifactId>
<version>1.0.0.Final</version>
<type>pom</type>
<scope>import</scope>
</dependency>

Anschließend sind noch zusätzlich die Drone-Abhängigkeiten in der normalen <dependencies>-Sektion zu ergänzen:

<dependency>
<groupId>org.jboss.arquillian.extension</groupId>
<artifactId>arquillian-drone-impl</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.arquillian.extension</groupId>
<artifactId>arquillian-drone-selenium</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.arquillian.extension</groupId>
<artifactId>arquillian-drone-selenium-server</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-server</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.mortbay.jetty</groupId>
<artifactId>servlet-api-2.5</artifactId>
</exclusion>
</exclusions>
</dependency>

Jetzt fehlen dem bisherigen Beispiel noch entsprechende Webinhalte. Ein @Named @SessionScoped User Controller könnte dabei den Usernamen von einer JSF-Seite (login.xhtml) aufnehmen und eine Begrüßung als FacesMessage setzen, die auf der nachfolgenden JSF-Seite (home.xhtml) angezeigt wird (siehe Beispiel als Download [7]). Richtig spannend ist hierbei der eigentliche Test (UserTest):

@RunWith(Arquillian.class)
public class UserTest {

private static final String WEBAPP_SRC = "src/main/webapp";

@Deployment(testable = false)
public static WebArchive createDeployment() {
return ShrinkWrap.create(WebArchive.class,
"user.war").addClasses(HelloBean.class,
UserController.class).addAsWebResource(new File(WEBAPP_SRC,
"login.xhtml")).addAsWebResource(new File(WEBAPP_SRC,
"home.xhtml")).addAsWebInfResource(EmptyAsset.INSTANCE,
"beans.xml").addAsWebInfResource(EmptyAsset.INSTANCE,
"faces-config.xml");
}
@Drone DefaultSelenium browser;

@ArquillianResource
URL deploymentURL;

@Test
public void should_login_successfully() {
browser.open(deploymentURL + "login.jsf");
browser.type("id=loginForm:username", "Developer");
browser.click("id=loginForm:login");
browser.waitForPageToLoad("15000");
Assert.assertTrue("User should be logged in!",
browser.isElementPresent("xpath=//li[contains(text(),
'Hallo Developer!')]"));
}
}

Auf den ersten Blick ist kaum Unterschied wahrzunehmen. Auch hier gibt es eine @Deployment-Methode, die Arquillian allerdings per testable = false in den Client-Modus versetzt. Das per ShrinkWrap erstellte Deployment ist ein wenig komplexer geworden, macht aber nicht viel anders, als ein user.war zusammenpacken. Dabei werden jetzt aber HelloBean und auch der neue UserController sowie die JSF-Seiten verpackt. Um dem Test den Selenium-Treiber hinzuzufügen, benötigt es lediglich eine Zeile:

@Drone DefaultSelenium browser. 

Der Injection Point sagt Drone, dass es einen Browser-Controller vom Typ DefaultSelenium vor dem Durchführen des ersten Client-Tests erstellen und diesen in den Test injizieren soll. Nun fehlt nur noch die URL der von Arquillian bereitgestellten Anwendung. Sie wird per

@ArquillianResource URL deploymentURL;

bereitgestellt und ebenfalls in den Test injiziert. Die eigentliche Testmethode steuert jetzt lediglich den Browser. Führt man jetzt mit die Tests erneut aus, kann man beobachten, wie Container, Selenium Server und Firefox starten, die Browsersteuerung ausgeführt und der Test durchlaufen wird. Dabei lässt sich sämtliche Selenium-Funktionen nutzen. Ein

   browser.captureScreenshot("target/screenshot.png");

ist also einfach in einem Arquillian-Test verfügbar.

Wem das Aufrufen der Testfälle via Maven nicht gefällt, der kann Arquillian in Eclipse oder NetBeans einbinden. Im Client-Modus ist allerdings beim Debuggen darauf zu achten, dass die Tests in einer anderen JVM ausgeführt werden als der eigentliche Code. Somit sind GlassFish und Co. in den entsprechenden Debug-Modus zu versetzen, um den Remote Debugger der bevorzugten IDE nutzen zu können.

Neben den bisher beschriebenen Funktionen kann Arquillian im Vergleich mit den bisher vielfach in der Presse beschriebenen Alpha- und Beta-Versionen noch einiges mehr. Rund um Arquillian ist ein lebendiges Ökosystem diverser Erweiterungen entstanden. Neben Arquillian Drone sind daher noch andere Erweiterungen in der aktiven Entwicklung. Dazu gehört auch ein Android-test-Controller, eine DBUnit-Integration, ein SeamTest-Ersatz, um Seam 2 zu testen, Unterstützung für Spock und JBehave, Performance-Metriken, Code Coverage (Jacoco) und Arquillian Graphene (eine type-safe Selenium API). Hier ist mit Erscheinen der Version 1.0 mit einer deutlichen Zunahme weiterer Erweiterungen zu rechen.

Nachdem bereits die ersten Versuche mit den Alpha-Versionen Freude bereiteten, stellt die neue Version einen deutlichen Schritt vorwärts in Sachen Qualität und Stabilität dar. Nach mehreren Jahren Entwicklung und der Prämierung auf der JavaOne 2011 mit dem Duke Choice Award wurde es allerdings auch Zeit für eine produktionsreife Version. Für das integrative Testen von Java-EE-Anwendungen im Container stellt Arquillian bisher den Standard. Die Nutzung ist einfach und mit wenigen Ausnahmen lässt es sich auch einfach in vorhandenen, Maven-Projekten integrieren. Auch die Verwendung der verschiedenen Container-Adapter ist im Vergleich zu den Vorgänger-Versionen einfach geworden.

Nach wie vor ist das Zusammenstellen komplexerer Deployments eine Herausforderung, da das wenig übersichtlich und fehleranfällig ist. Es ist also Sorgfalt gefragt bei der Verwendung. Die bisher fehlenden Funktionen (bspw. @ArquillianResource) sind mittlerweile auch vorhanden und sorgen für einen abgerundeten Gesamteindruck. Die Dokumentation ist ebenfalls deutlich stärker geworden. Eine große Community [8] kümmert sich um die Pflege und Weiterentwicklung der Einzelteile. Der zusätzliche Aufwand für die Verwendung von Arquillian in Projekten dürfte sich schnell lohnen, wenn es darauf ankommt, regelmäßige und komplexe Integrationstests automatisiert durchzuführen. Vor allem bei der Verwendung mehrerer Container ist schnell eine Basis stabilisiert. Größte Herausforderung bleiben komplexe Deployments. Deren Erstellung mit ShrinkWrap gerät schnell unübersichtlich. Hier würde eine Integration in das Maven Packaging viel Arbeit ersparen.

Markus Eisele
arbeitet bei der msg systems ag in München. Er ist Java EE 7 EG und GlassFish Community Member und betreibt einen englischsprachigen Blog [9] über Java EE, GlassFish und WebLogic.
(ane [10])


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

Links in diesem Artikel:
[1] http://en.wikipedia.org/wiki/Arquillians
[2] http://www.jboss.org/arquillian.html
[3] http://arquillian.org
[4] http://arquillian.org/guides/
[5] https://github.com/arquillian/
[6] https://docs.jboss.org/author/display/ARQ/GlassFish%2B3.1%2B-%2BEmbedded
[7] ftp://ftp.heise.de/pub/ix/developer/eisele_arquillian.zip
[8] http://arquillian.org/community/contributors/
[9] http://blog.eisele.net
[10] mailto:ane@heise.de