Testen mit Arquillian – Ein erster Einblick

Von Alexander Kittelmann, GEDOPLAN IT Training.

Arquillian ist ein von JBoss (Red Hat) entwickeltes Test-Framework, speziell für Integrationstests von Java-EE-Anwendungen. Die erste Alpha-Version dieses Frameworks (Arquillian Core Platform) erschien am 10. März 2010. Bis zur ersten finalen Version dauerte es ungefähr weitere zwei Jahre, sie erschien am 10.04.2012. Zum jetzigen Zeitpunkt liegt Version 1.1.9.Final als aktuelle Version vor. Hauptverantwortlicher für Arquillian ist Aslak Knutsen.

Arquillian bietet die Möglichkeit reale Tests für Java-EE-Anwendungen zu schreiben. Mit realen Tests ist gemeint, dass die Tests in einem Container (WildFly, GlassFish, Tomcat) stattfinden, wodurch alle vom Container bereitgestellten Dienste genutzt werden können. Dadurch kann auf Mocking verzichtet werden und die Tests laufen so realitätsnah wie möglich ab. Wichtige Annotationen, Deployment-Deskriptoren und die Dependency Injection stehen somit zur Verfügung. Arquillian deckt folgende Bereiche ab:

  • Verwaltung des Lebenszyklus eines Containers
  • Nutzung verschiedener Container mit Container-Adaptern(WildFly,GlassFish, etc.)
  • Erstellung von Deployment-Artefakten
  • Durchführung des Deployments
  • Erweiterung der Testklassen um bspw. die Dependency-Injection(@EJB, @CDI, @PersistenceContext)

Zusätzlich gibt es verschiedene Erweiterungen (Extensions), um die Möglichkeiten von Arquillian zu vergrößern und beispielsweise Systemtests zu realisieren.

Dieser Artikel setzt sich mit folgenden Erweiterungen auseinander:

  • Persistence-Extension
  • Transaction-Extension
  • Drone
  • Graphene
  • Warp

In-Container-Tests vs. Tests im Clientmode

Arquillian kann in zwei Ausführungsmodi genutzt werden. Die Modi bestimmen, wo der Testcode ausgeführt wird. Im ersten Modus werden die Tests im Container ausgeführt. Hierbei wird die Testausführung, wie üblich, aus der IDE oder einem Build-Werkzeug wie Maven angestoßen. Die konkrete Testausführung wird jedoch an den Container delegiert. Die Tests in diesem Modus werden als In-Container-Tests bezeichnet. Der Vorteil dabei liegt darin, dass keine zusätzlichen Schnittstellen geschaffen werden müssen. Klassen können durch Injections genutzt werden, da die Tests im Container laufen und alle Funktionalitäten des Containers nutzen können. Dieser Modus ist für Integrationstests als White-Box-Tests vorgesehen.

Der zweite Modus nennt sich Clientmode. Es befindet sich lediglich der Anwendungscode im Container. Der Testcode nimmt die Kommunikation mit der Anwendung selbst auf und fungiert als Remote-Client. Dieser Modus kann genutzt werden um Systemtests als Black-Box-Tests zu realisieren. Die Eingaben können dabei mit Hilfe von Drone und Graphene automatisiert über die GUI ablaufen.

Verschiedene Containertypen

Remote:

Dieser Containertyp, der Remote-Container, läuft im Gegensatz zur Testausführung in einer separaten Java Virtual Machine (JVM). Diese JVM kann sich auf einer entfernten Maschine befinden. Arquillian verbindet sich dazu, mit Hilfe eines Container-Adapters, mit dem Container. Danach führt dieser das Deployment des Testarchivs durch und startet die einzelnen Tests.

Managed:

Ein Managed-Container verhält sich ähnlich wie ein Remote-Container. Jedoch übernimmt Arquillian zusätzlich das Hoch – und Runterfahren, um dies zu ermöglichen, muss der Container lokal installiert sein.

Embedded:

Ein Embedded-Container läuft in der gleichen JVM wie die Testausführung, und die Steuerung des Containers wird von Arquillian geregelt.

Bei der Testausführung sollte auf einen Remote- oder Managed-Container zurückgegriffen werden, da es bei dem Embedded-Container zu verfälschten Ergebnissen kommen kann.

ShrinkWrap

ShrinkWrap wurde dazu entwickelt, um ein Java-Objekt zu deklarieren, das ein Archiv repräsentiert. Ohne dieses Framework wäre Arquillian nicht nutzbar, da ShrinkWrap den Deployment-Mechanismus von Arquillian steuert. Das daraus resultierende Ergebnis ist eine Java-API analog zum jar-Tool mit einem virtuellen Dateisystem und intuitiver Syntax. Als Archivtypen, die mit ShrinkWrap erzeugt werden können, stehen folgende zur Verfügung: GenericArchive, JavaArchive, EnterpriseArchive, WebArchive und ResourceAdapterArchive. Zusätzlich ermöglicht ShrinkWrap die Erstellung von Deployment-Artefakten. Es müssen nicht alle Projektdateien zum Deployment hinzugefügt werden. Nur die benötigten Beans und andere wichtige Dateien (beans.xml, persistence.xml usw.) müssen dem Archiv hinzugefügt werden. Dies sorgt für ein schnelleres Deployment, da nicht jedes Mal die komplette Anwendung deployed werden muss.

Vorbereitungen

Das integrieren der einzelnen Frameworks geschieht über die pom.xml von Maven. Nach dem Hinzufügen der Frameworks können in der arquillian.xml, die sich in test/resources befindet, der Container und die Extensions konfiguriert werden. Bei einem Managed- oder Remote-Container muss hier beispielsweise der Pfad zum Container angegeben werden.

Aufbau eines Tests mit Arquillian

Die Testklasse muss mit @RunWith(Arquillian.class) annotiert werden. Mit Hilfe dieser nutzt JUnit nicht den integrierten Test-Runner von JUnit, sondern Arquillian.class als Test-Runner.

Die Testklasse muss mit @RunWith(Arquillian.class) annotiert werden. Mit Hilfe dieser nutzt JUnit nicht den integrierten Test-Runner von JUnit, sondern Arquillian.class als Test-Runner.

Als nächstes wird das Archiv erstellt, in diesem Fall ist es ein WebArchive. Dieses Archive wird mit Hilfe des Container-Adapters deployed. Zum Erzeugen des Deployments muss dem Test die Methode public static Archive<?> createDeployment()zugefügt werden. Zusätzlich wird diese Methode mit @Deployment annotiert. In dieser wird mit ShrinkWrap das WebArchive erstellt. Diesem Archive müssen alle für den Test benötigten Klassen und Ressourcen (persistence.xml, beans.xml) hinzugefügt werden. Zum Hinzufügen werden verschiedene Methoden bereitgestellt.

In der obigen Abbildung ist zu sehen, dass dank Arquillian Injektionen im Test wie gewohnt stattfinden können. So können für Tests benötigte Klassen einfach injiziert werden um reale Tests ablaufen zu lassen.

Persitence-Extension – Ein strukturierter Integrationstest (White-Box)

Mit Hilfe der Persistence-Extension von Arquillian kann das Testen der Datenzugriffsschicht vereinfacht und besser strukturiert werden. Dank dieser Extension müssen Testdaten nicht über die setUp()-Methode in die Datenbank geschrieben werden. Es können XML-, JSON-, YAML-Dateien genutzt werden, in denen vorgefertigte Datensätze definiert sind. Diese Dateien können mit Hilfe von Annotationen verwendet werden, um vor einem Test die Datensätze in die Datenbank zu persistieren oder den Datenbankinhalt mit den Datensätzen der Datei zu validieren. Dies sorgt für übersichtlichere Testklassen mit schmaleren setUp()- und tearDown()-Methoden. Zusätzlich bereinigt die Persistence-Extension die Datenbank nach jedem Test, sodass alle Tests unabhängig voneinander ausgeführt werden. Die Persistence-Extension beinhaltet die Transaction-Extension, die es ermöglicht @Transactional zu nutzen, um eine Transaktion zu beginnen. Die Persistence-Extension beginnt automatisch eine Transaktion, sodass @Transactional nicht verwendet werden muss.

Testbeispiel mit Arquillian und der Persistence-Extension

Die von der Persistence-Extension benötigten Dateien werden in dem Ordner datasets abgelegt. Dies ist der von Arquillian vorgesehene Standardordner, welcher in der arquillian.xml  geändert werden kann. Durch anlegen von Unterordnern werden die Testdaten strukturiert abgelegt. Die Abbildung zeigt mehrere Dateiformate in einem Ordner, dies soll andeuten, dass es möglich ist verschiedene Dateitypen zu verwenden. Im Normalfall sollte sich natürlich auf ein Dateiformat festgelegt werden.

Ohne die Persistence-Extension wäre es nötig die Testdaten mit Hilfe der setUp()-Methode zu persistieren damit diese für die Tests genutzt werden können. In der tearDown()-Methode müssten die vorher angelegten Datenbankeinträge wieder entfernt werden damit für alle Tests die gleichen Anfangsbedingungen gelten. Die folgende Abbildung zeigt, wie einfach sich ein Test mit der Persistence-Extension realisieren lässt.

Tests mit Arquillian sind wie übliche JUnit-Tests mit @Test annotiert. Die beiden zusätzlichen Annotationen werden von der Persistence-Extension zur Verfügung gestellt. In der @UsingDataSet-Annotation wird die Datei angegeben deren Inhalt in die Datenbank persistiert werden soll. Danach läuft der Test wie gewohnt ab. In diesem Fall wird ein Objekt aus der Datenbank entfernt. Mit der @ShouldMatchDataSet Annotation wird eine Datei angegeben, in der alle Objekte stehen, welche sich nach dem Test in der Datenbank befinden sollen. Stimmt der Inhalt der Datei mit dem Datenbank Inhalt überein wird der Test erfolgreich beendet. Durch die Perssitence-Extension wird die Datenbank nach dem Test wieder geleert, sodass die Tests unabhängig voneinander ablaufen.

Drone und Graphene zum Erstellen von Systemtests (Black-Box)

Die Drone-Extension stellt den WebDriver von Selenium zur Verfügung. Mit diesem ist es möglich den Lifecycle des genutzten Browsers zu verwalten und die Browsersteuerung zu automatisieren. Einzelne Elemente einer XHTML-Datei können so anhand ihrer ID bezogen werden. So ist es möglich diese zu manipulieren indem beispielsweise in ein Eingabefeld neue Werte geschrieben werden.

Graphene nutzt Selenium und Drone. Graphene erweitert den Funktionsumfang von Drone und wurde speziell dafür entwickelt AJAX-basierte-Webanwendungen zu testen.

In diesem Zusammenhang wird der ShrinkWrap-Resolver benötigt, um Dependencies aus der pom.xml zu einem von ShrinkWrap erstellten Archive hinzuzufügen. Dies ist z.B. bei der Nutzung von Primefaces nötig, da sonst die Darstellung der XHTML-Seiten nicht mehr reibungslos funktioniert.

Der Testaufbau von Systemtests mit Drone und Graphene unterscheidet sich nur an den in das Archive hinzugefügten Ressourcen und den Testmethoden. Da beim Systemtest auch die Oberfläche betrachtet wird, müssen dem Archive weitere Ressourcen hinzugefügt werden, dies sind zum Beispiel die XHTML-Seiten,  Bilder, die web.xml usw. Zudem laufen diese Tests im Clientmode. Es gibt drei Möglichkeiten zum Erstellen von Tests im Clientmode:

  • Die gesamte Testklasse kann mit @RunAsClient annotiert werden, dadurch werden alle enthaltenen Testmethoden im Clientmode ausgeführt
  • Einzelne Testmethoden können mit der @RunAsClient-Annotation versehen werden, nur diese Methoden werden im Clientmode ausgeführt, so können in einer Testklasse Clientmode-Tests und In-Container-Tests festgelegt werden, nicht zu verwechseln mit Warp-Tests, wo beide Testarten in einem Test stattfinden (dazu später mehr)
  • Die createDeployment()-Methode der Testklasse wird statt nur mit @Deployment mit @Deployment(testable = false) annotiert, hierdurch werden auch alle Testmethoden im Clientmode ausgeführt

Die @Drone-Annotation in dieser Testklasse startet vor dem Start der ersten Testmethode den Browser. Danach wird das WebDriver-Objekt injiziert. Da nun die GUI getestet wird und die URL der Web-Anwendung benötigt wird, um auf die Oberfläche zuzugreifen, kann die URL mit Hilfe der @ArquillianResource bezogen werden. Dazu muss lediglich eine URL-Variable mit dieser Annotation versehen werden. Nach der Injektion steht die URL der zu testenden Anwendung zur Verfügung. Zuletzt werden die Webelemente benötigt, diese werden mi Hilfe der @FindBy-Annotation bezogen. In diesem Beispiel geschieht dies anhand der ID des Elements. Die obige Abbildung zeigt, dass die Eingabefelder für den Usernamen und das Passwort, und der Button zum Absenden der Daten bezogen werden. Bei Angabe der ID muss auf die Verschachtelung der Elemente geachtet werden (loginForm:loginButton).

Die Tests sind wie gewohnt mit @Test annotiert. Zusätzlich werden in dieser Arbeit die einzelnen Testmethoden mit @RunAsClient annotiert, um die Möglichkeit offen zu lassen, wenn nötig, auch In-Container-Tests in dieser Testklasse zu erstellen.  Da diese Testmethoden, meist voneinander abhängig sind, wird die @InSequence(...)-Annotation verwendet, um die Reihenfolge der Tests festzulegen. In diesem Test wird das Login durchgeführt. Dazu wird zuerst die Startseite der Web-Anwendung aufgerufen. Danach werden mit sendKeys()die Felder für den Username und das Passwort ausgefüllt. Die nächste Zeile verwendet einen von Graphene bereitgestellten Guard. In der dargestellten Zeile sollen die Daten durch einen „Klick“ abgesendet werden. Dies ist kein Problem, das Problem entsteht erst beim Ausführen der nächsten Testzeile bzw. des nächsten von diesem Test abhängigen Test. Da der Serveraufruf durch den „Klick“ asynchron ist, läuft der Testcode weiter, bevor auf die nächste URL der zu testenden Anwendung weitergeleitet wurde. Dies hat zur Folge, dass das WebElement, welches in dem nächsten Test verwendet werden soll, noch nicht vorhanden ist und der Test fehlschlägt. Mit dem Aufruf Graphene.guardHttp(login).click()wird sichergestellt, dass der HTTP-Request an den Server durchgeführt wird und wartet gleichzeitig, bis er beendet wurde und folgende Callbacks im Browser ausgeführt wurden. Der Guard wartet hierbei zwei Sekunden. Wird diese Zeit überschritten, schlägt der Test fehl. Dieser Timeout kann in der arquillian.xml mit Hilfe der Konfiguration waitGuardInterval verändert werden.

Warp – Integrationstests als Grey-Box-Tests

Die Warp-Extension ermöglicht es, Aktivitäten clientseitig zu initiieren und danach den Zustand im Server zu überprüfen.

Um Warp nutzen zu können, muss die Testklasse mit @WarpTest annotiert werden. Der Aufbau dieses Tests gleicht zunächst dem Aufbau eines normalen Tests im Clientmode, da ein Browser injiziert wird, um Requests ferngesteuert an die zu testende Anwendung zu senden. Zusätzlich werden die URL der Anwendung und alle Webelemente, die für den Test eine Rolle spielen, benötigt. Wie in jedem Test im Clientmode muss auch hier entweder die Klasse oder die Methode mit @RunAsClient annotiert werden. Oder alternativ wird @Deployment(testable=false) genutzt.

Um Warp nutzen zu können, muss die Testklasse mit @WarpTest annotiert werden. Der Aufbau dieses Tests gleicht zunächst dem Aufbau eines normalen Tests im Clientmode, da ein Browser injiziert wird, um Requests ferngesteuert an die zu testende Anwendung zu senden. Zusätzlich werden die URL der Anwendung und alle Webelemente, die für den Test eine Rolle spielen, benötigt. Wie in jedem Test im Clientmode muss auch hier entweder die Klasse oder die Methode mit @RunAsClient annotiert werden. Oder alternativ wird @Deployment(testable=false) genutzt.

Um Warp zu nutzen wird in dem Aufruf  Warp.initiate(Activity activity) ein neues Activity-Objekt als anonyme Klasse implementiert und dessen Methode perform() überschrieben. Die perform()-Methode enthält die eigentliche Funktion eines Tests im Clientmode. Mit dieser Methode wird die zu testende Anwendung wie bei Systemtests ferngesteuert.

Warp ermöglicht zusätzlich, durch das Anhängen der observe()-Methode zu prüfen, ob der Aufruf der Seite korrekt ausgeführt wurde (z. B. durch GET, POST, DELETE usw.). Zudem kann festgestellt werden, ob durch den Request die richtige Seite geöffnet wurde. Als nächstes kann die inspect(...)-Methode genutzt werden. Dieser wird ein Inspection-Objekt als anonyme Klasse übergeben. Dieses Objekt muss die, in der Abbildung zu sehende, serialVersionUID-Variable besitzen. Ansonsten kommt es zu folgender Exeption: “org.jboss.arquillian.warp.impl.client.transformation.NoSerialVersionUIDException: serialVersionUID for class de.gedoplan.semenu.integrationstests.greyboxtests.SessionServiceGBTest$1 is not set; please set serialVersionUID to allow Warp work correctly.”

Die Methoden dieser anonymen Klassen entsprechen einem In-Container-Test. Dazu können in ihr alle benötigten Instanzvariablen injiziert werden, wie in der Abbildung beispielsweise der SessionService. Durch die Annotationen @BeforePhase() und @AfterPhase() kann ausgewählt werden, bevor oder nach welcher JSF-Phase des JSF-Lifecycles diese Methode aufgerufen werden soll. So kann vor und nach jeder Phase der Zustand im Server überprüft werden.

In dem konkreten Beispiel wurde zuerst mit Hilfe der überschriebenen perform()-Methode der Aktivity die Login-URL aufgerufen und mit Hilfe der Form-Based-Authentication ein User angemeldet. Nach dem erfolgreichen Login wird der User auf die Startseite (localhost/semenu/index.xhtml) weitergeleitet. Dies wird mit der observe(...)-Methode überprüft. Durch die Übergabe von request().method().equal(HttpMethod.GET).uri().contains("index.xhtml") wird geprüft, ob der Request über ein GET abgeschickt wurde und ob die angeforderte URL den String ″index.xhtml″ beinhaltet, um sicherzustellen, dass sich der User auf der korrekten URL befindet.

Zuletzt folgt der In-Container-Test anhand der inspect()-Methode und der überschriebenen Inspection Klasse. Diese enthält wie oben beschrieben die private static final long Variable serialVersionUID, welche immer den Wert 1L zugewiesen bekommt. Zusätzlich wird die SessionService-Bean injiziert. Die in der anonymen Klasse Inspection festgelegte Methode wird mit @BeforePhase(Phase.RENDER_RESPONSE) annotiert, da zu diesem Zeitpunkt das Login des Users abgeschlossen ist, sodass getestet werden kann, ob die Methode getCurrentUser(), der SessionService-Bean, den richtigen Principal zurückliefert.

Fazit

Insgesamt ist Arquillian mit seinen Extensions hervorragend dazu geeignet Integrations- und Systemtests zu schreiben. Es werden zusätzlich weitere Extensions benötigt, um Arquillian nutzen zu können (z.B. JUnit oder TestNG) und den Testfortschritt auszuwerten (z.B. JaCoCo), aber da beides in jedem Projekt, welches getestet wird, vorhanden sein sollte, ist nur der Aufwand zum Integrieren von Arquillian und den Extensions ein Mehraufwand. Ist diese Hürde erst einmal genommen, kann jede Schicht einer Java-EE-Anwendung bestmöglich getestet werden.

In diesem Sinne sieht die Zukunft von Arquillian positiv aus, es werden immer mehr Extensions entwickelt, die das Testen von Java-EE-Anwendungen entweder weiter vereinfachen oder neue Möglichkeiten zum Testen bieten. Die Nutzung von Arquillian wird sich in immer mehr Java-EE-Anwendungen durchsetzen, sodass die Qualität der entwickelten Anwendungen steigt.

Unsere Kundenzeitschrift GEDOPLAN aktuell

In dieser Rubrik stellen wir Ihnen einen Artikel aus der aktuellen Ausgabe unserer Kundenzeitschrift zum Online-Lesen zur Verfügung. Alle weiteren Ausgabe zum Download finden Sie hier.

Aktueller Kurs zum Thema:

Java Test für die Enterprise Edition (Java EE)

* Nächster Termin: 11.10. - 14.10.2016 in Berlin.