Beispiel: Unit-Test mit Maven und Arquillian
Inhalt:
Arquillian
Unittest oder Integrationstest?
Code des Tests
Erzeugen des deploybaren Artefakts über Shrinkwrap
Konfiguration des Tests: Profiles
Konfiguration des Tests: Profile für "Remote Server"
Konfiguration des Tests: Profile für "Managed Server"
Variante 1: Ausführen als Maven-Test über Eclipse
Variante 2: Ausführen außerhalb Eclipse über Maven
Variante 3: Ausführen als Eclipse-Unit-Test
Debugging der Unit Test-Clientseite
Ausführen mit Java 17
Wie der Unit-Test abläuft
Für WildFly 26: hier wird ein Unit-Test für ein EAR-Projekt, bestehend aus EJB-Projekt und Web-Projekt, unter Verwendung des Arquillian-Framework, mittels Maven erstellt und deployed.
Dieses Beispiel basiert komplett auf dem Beispiel Stateless Session Bean und Maven (dort finden sich Erklärungen zur Struktur und zu den diversen "pom.xml")
und erweitert es nur um einen Unit-Test. Dieser Test ist ein Integrationstest, da er eine EJB testet, die in der Web-Schicht der Anwendung verwendet wird.
Hier gibt es das gepackte Eclipse-Projekt zum Download: StatelessMaven.zip. Die Importanleitung findet man ebenfalls im Stateless Session Bean und Maven-Beispiel.
Arquillian
Das Arquillian-Framework (http://arquillian.org/) ist ein Aufsatz auf die bekanntesten Unittestframeworks, darunter JUnit oder auch TestNG. Es erweitert diese Frameworks so,
dass Unit-Tests für Server-Anwendungen durchgeführt werden können: der zu testende Code läuft auf einem JavaEE-Server, während der Unit-Test-Client in seiner eigenen JVM, eventuell sogar auf einem anderen Rechner,
läuft und die Aufrufe der Testmethoden vom Client zum Server "getunnelt" werden. Mehr dazu weiter unten.
Unittest oder Integrationstest?
Maven hat zwei Plugins für Tests: für Unittests wird der "maven-surefire-plugin" verwendet, für Integrationstests der "maven-failsafe-plugin".
Für uns relevant ist die Position der Testtypen im "maven build life cycle"
(Quelle: http://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html):
- validate
- initialize
- generate-sources
- process-sources
- generate-resources
- process-resources
- compile
- process-classes
- generate-test-sources
- process-test-sources
- generate-test-resources
- process-test-resources
- test-compile
- process-test-classes
- test
- prepare-package
- package
- pre-integration-test
- integration-test
- post-integration-test
- verify
- install
- deploy
Der "maven-surefire-plugin" wird in Phase 15 ("test") durchgeführt, der "maven-failsafe-plugin" in Phase 19 ("integration-test"). Dazwischen erfolgt die Phase "package", und diese ist für uns wichtig:
wir benötigen weiter unten im Schritt Erzeugen des deploybaren Artefakts über Shrinkwrap die WAR-Datei, die in der Phase "package" vom "maven-war-plugin" erzeugt wurde. Und diese WAR-Datei
ist noch nicht da, wenn der Schritt "test" erfolgt. Das heißt wir können unseren Test nicht als Unit-Test implementieren ("maven-surefire-plugin"), sondern müssen ihn als Integrationstest implementieren ("maven-failsafe-plugin").
Wir müssen die Testklassen entsprechend benennen, damit sie vom jeweiligen Maven-Plugin verarbeitet werden:
Das bedeutet: unsere Testklasse muss GeometricModelBeanIT
heißen!
In Maven-Projekten liegt Unittest-Code in einem eigenen Unterverzeichnis "src\test". In diesem Verzeichnis fügen wir die Klasse de.hsrm.cs.javaee8.statelessmaven.web.test.GeometricModelBeanIT
zu:
Sie hat diesen Code:
package de.hsrm.cs.javaee8.statelessmaven.web.test;
import javax.ejb.EJB;
import org.jboss.arquillian.junit.Arquillian;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import de.hsrm.cs.javaee8.statelessmaven.ejb.GeometricModelLocal;
@RunWith(Arquillian.class)
public class GeometricModelBeanIT
{
@EJB
private GeometricModelLocal geometricModelLocal;
@Test
public void testCuboidSurface()
{
System.out.println("Testing cuboid surface...");
double surface = geometricModelLocal.computeCuboidSurface(1, 2, 3);
//Hier muss ein "Delta" angegeben werden für Abweichung durch Rundungsungenauigkeit. Das kann hier nicht zuschlagen, deshalb können wir Delta = 0 angeben.
Assert.assertEquals(22, surface, 0);
}
@Test
public void testCuboidVolume()
{
System.out.println("Testing cuboid volume...");
double volume = geometricModelLocal.computeCuboidVolume(1, 2, 3);
//Hier muss ein "Delta" angegeben werden für Abweichung durch Rundungsungenauigkeit. Das kann hier nicht zuschlagen, deshalb können wir Delta = 0 angeben.
Assert.assertEquals(6, volume, 0);
}
}
Das Local Interface der EJB wird vom Server injiziert. Die Test-Methoden prüfen die Berechnung von Oberfläche und Volumen.
Die Annotation org.junit.runner.RunWith
hat als Parameter die Klasse org.jboss.arquillian.junit.Arquillian
: dies sorgt dafür, dass für das Ausführen des
Tests das Arquillian-Framework verwendet wird.
Fürs Compilieren des Code müssen in "pom.xml" von "StatelessMaven-web" folgende Dependencies enthalten sein (dies ist durch die Erzeugung des Projekts aus dem Archetype "wildfly-jakartaee-ear-archetype" schon gegeben):
<dependencies>
...
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.arquillian.junit</groupId>
<artifactId>arquillian-junit-container</artifactId>
<scope>test</scope>
</dependency>
...
</dependencies>
Falls man selbst Dependencies hinzufügt, dann muss danach im Contextmenü "Maven" => "Update Project" aufgerufen werden, damit Eclipse diese sieht.
Die Versionen werden hier nicht angegeben, sie ergeben sich aus dem WildFly-BOM, in unserem Fall aus https://repo.maven.apache.org/maven2/org/wildfly/bom/wildfly-jakartaee8-with-tools/26.0.0.Final/wildfly-jakartaee8-with-tools-26.0.0.Final.pom,
das in "pom.xml" des Root-Projekts eingebunden ist. Dies sind JUnit 4.13.1 und Arquillian 1.6.0.Final.
Der Wert "test" des Elements "scope" bedeutet, dass diese Abhängigkeit nur zur Compilierung und Ausführung von Testklassen zur Verfügung steht, aber nicht im regulären Code der Anwendung.
Erzeugen des deploybaren Artefakts über Shrinkwrap
Wie eingangs beschrieben führt Arquillian den Testcode auf dem Server aus. Damit dies klappt, muss (je nach Anwendungsfall) eine EJB-JAR-Datei, eine WAR-Datei oder eine EAR-Datei erzeugt werden, die den Testcode enthält.
Maven erzeugt zwar beim Ausführen der Anwendung (Goal "wildfly:deploy") ebenfalls eine EAR-Datei. Diese enthält aber nicht die Testklassen. Das Arquillian-Framework bietet eine API namens "ShrinkWrap"
(http://arquillian.org/modules/shrinkwrap-shrinkwrap/), die für das Erzeugen von deploybaren Artefakten zuständig ist: sie kann JAR-, WAR- und EAR-Dateien
erzeugen. Sie ist enthalten in der im letzten Schritt eingebundenen Dependency mit der GroupID "org.jboss.arquillian.junit" und der ArtifactId "arquillian-junit-container".
ShrinkWrap-Einführung: http://arquillian.org/guides/shrinkwrap_introduction/.
Die Unit-Test-Klasse erhält eine Methode, die ein Generic org.jboss.shrinkwrap.api.Archive
zurückliefert und mit der Annotation org.jboss.arquillian.container.test.api.Deployment
versehen ist:
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.shrinkwrap.api.Archive;
@RunWith(Arquillian.class)
public class GeometricModelBeanIT
{
@Deployment
public static Archive<?> getEarArchive()
{
}
}
Da für den Maven-Testlauf ein Compile und Build durchgeführt wird, verwenden wir soweit möglich die vom Build-Prozess erzeugten Artefakte und modifizieren diese nur noch.
Der Ablauf beim Maven-Testlauf ist, dass zuerst das "innerste" Projekt compiliert wird und dessen Tests (sofern vorhanden) durchgeführt werden. Danach sind die Projekte an der Reihe, die dieses Projekt
als Abhängigkeit haben, und am Schluss ist das Root-Projekt an der Reihe. In unserem Fall hat das Web-Projekt eine Abhängigkeit zum EJB-Projekt, und das EAR-Projekt hat EJB- und Web-Projekt als Abhängigkeiten.
Der Ablauf ist also dieser:
- EJB-Projekt bereinigen (Goal "clean")
- EJB-Projekt compilieren
- Unittests des EJB-Projekts durchführen
- EJB-JAR erzeugen
- Integrationstests des EJB-Projekts durchführen
- Web-Projekt bereinigen (Goal "clean")
- Web-Projekt compilieren
- Unittests des Web-Projekts durchführen
- WAR-Datei erzeugen
- Integrationstests des Web-Projekts durchführen
- EAR-Projekt bereinigen (Goal "clean")
- EAR-Projekt compilieren und EAR erzeugen (in das die JAR-Datei und die WAR-Datei aus den Build-Prozessen der abhängigen Projekte eingebunden werden)
Da unsere Integrationstests im Web-Projekt liegen, sind zum Zeitpunkt der Ausführung der Tests das EJB-Projekt und das Webprojekt compiliert und die JAR- bzw. WAR-Dateien erzeugt.
Allerdings ist die EAR-Datei für das Gesamtprojekt noch nicht erzeugt!
Wir können für das Erzeugen des Deployment also EJB-JAR und Web-WAR verwenden, und müssen diese zur EAR-Datei zusammenbündeln. Der Code sieht so aus:
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.importer.ZipImporter;
import org.jboss.shrinkwrap.api.spec.EnterpriseArchive;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.jboss.shrinkwrap.api.spec.WebArchive;
...
@RunWith(Arquillian.class)
public class GeometricModelBeanIT
{
@Deployment
public static Archive<?> getEarArchive()
{
EnterpriseArchive ear = ShrinkWrap.create(EnterpriseArchive.class, "StatelessMaven-ear.ear");
File f = new File("../StatelessMaven-ejb/target/StatelessMaven-ejb.jar");
JavaArchive ejbJar = ShrinkWrap.create(ZipImporter.class, "StatelessMaven-ejb.jar").
importFrom(f).as(JavaArchive.class) ;
ear.addAsModule(ejbJar);
f = new File("../StatelessMaven-web/target/StatelessMaven-web.war");
WebArchive war = ShrinkWrap.create(ZipImporter.class, "StatelessMaven-web.war").
importFrom(f).as(WebArchive.class) ;
ear.addAsModule(war);
//hier kommt weiter unten noch "application.xml"...
war.addPackage("de.hsrm.cs.javaee8.statelessmaven.web.test");
return ear;
}
}
Zur Erklärung:
- Über
ShrinkWrap.create
wird eine leere EAR-Datei im Speicher erzeugt.
- Es wird die vorhandene Datei "StatelessMaven-ejb.jar"
als
JavaArchive
importiert und der EAR-Datei als Modul zugefügt.
Sie wird über ihren relativen Pfad referenziert, ausgehend vom Verzeichnis des "StatelessMaven-web"-Projekts. Die vom Maven-Build erzeugte JAR-Datei liegt im Unterverzeichnis "target".
- Es wird die vorhandene Datei "StatelessMaven-web.war" als
WebArchive
importiert und der EAR-Datei als Modul zugefügt. Hier könnte man den relativen Pfad vereinfachen zu "./target/StatelessMaven-web.war".
- Im letzten Schritt wird die Testklasse dem WAR zugefügt. Hier fügen wir das gesamte Package zu:
WebArchive.addPackage
Es fehlt noch die Datei "application.xml", siehe unten. Und es gibt einen weiteren Unterschied zu der EAR-Datei, die Maven beim Build erzeugt: die "Manifest.mf" fehlt uns. Aber diese ist nicht benötigt.
Falls wir uns anschauen wollen, was die ShrinkWrap-API für eine Datei erzeugt, können wir diese exportieren lassen:
import org.jboss.shrinkwrap.api.exporter.ZipExporter;
...
ear.as(ZipExporter.class).exportTo(new File("c:\\temp\\test.ear"), true);
Wenn der Maven-Build-Ablauf so wäre, dass zuerst alle Projekt durchcompiliert und die Archivdateien erzeugt werden, dann könnte man die vom EAR-Projekt-Build erzeugte EAR-Datei verwenden und diese
erweitern. Das würde den Code deutlich vereinfachen:
EnterpriseArchive ear = ShrinkWrap.create(ZipImporter.class, "StatelessMaven-ear.ear")
.importFrom(new File("../StatelessMaven-ear/target/StatelessMaven-ear.ear")).as(EnterpriseArchive.class);
WebArchive war = ear.getAsType(WebArchive.class, "/StatelessMaven-web.war");
war.addPackage("de.hsrm.cs.javaee8.statelessmaven.web.test");
return ear;
Allerdings funktioniert dies in der Realität nicht, weil diese EAR-Datei vermutlich noch nicht vorhanden ist. Sie ist dann da, wenn vor dem aktuellen Maven-Testlauf das Goal "install" gestartet hat,
dass das gesamte Projekt compiliert. Dieses Goal führt zwar Unit-Tests durch, aber da wir den "maven-failsafe-plugin" verwenden und dieser nur in zwei Profilen aktiv wird, in der Default-Konfiguration aber nicht aktiv ist,
werden unsere Integrationstests nicht durchgeführt.
Dieses händische Aufrufen von "install" vor den Integrationstests ist allerdings keine praxistauglichen Lösungen, d.h. man muss damit leben dass Maven zwar die perfekte EAR-Datei erzeugen würde,
man sie aber trotzdem über die ShrinkWrap-API nachbauen muss.
Erzeugen von "application.xml"
Der Maven-Buildprozess des EAR-Projekts erzeugt diese "application.xml" (zu finden in "StatelessMaven-ear\target"):
<?xml version="1.0" encoding="UTF-8"?>
<application xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee/ http://xmlns.jcp.org/xml/ns/javaee/application_8.xsd" version="8">
<display-name>StatelessMaven-ear</display-name>
<module>
<web>
<web-uri>StatelessMaven-web.war</web-uri>
<context-root>/StatelessMaven-web</context-root>
</web>
</module>
<module>
<ejb>StatelessMaven-ejb.jar</ejb>
</module>
<library-directory>lib</library-directory>
</application>
Diese wollen wir beim Erzeugen des Deployments ebenfalls erzeugen. Leider können wir nicht die vorhandene Datei recyclen, weil je nach Maven-Ablauf nicht sichergestellt ist, dass sie da ist.
Die einfachste Lösung wäre es, eine solche Datei irgendwo im Projekt abzulegen und sie einzulesen. Aber wir gehen hier den aufwändigen Web über die ShrinkWrap-API bzw. deren Unterprojekt "ShrinkWrap Descriptors"
(http://arquillian.org/modules/descriptors-shrinkwrap/).
Bei WildFly 26 wird Arquillian 1.6.0.Final verwendet. Dort ist "shrinkwrap-descriptors" bereits eingebunden, siehe
https://repo.maven.apache.org/maven2/org/jboss/arquillian/arquillian-bom/1.6.0.Final/arquillian-bom-1.6.0.Final.pom.
Deshalb steht können wir die Artefakte dieser BOM automatisch verwenden.
In "arquillian-bom-1.6.0.Final.pom" ist dies so eingetragen (allerdings ist die Versionsnummer dort nur als Property eingetragen, die weiter oben in der Datei mit dem Wert "2.0.0" definiert ist):
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.jboss.shrinkwrap.descriptors</groupId>
<artifactId>shrinkwrap-descriptors-bom</artifactId>
<version>2.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Diese Dependency stellt uns nur einen Satz von Dependencies zur Verfügung, aus denen das ShrinkWrap Descriptors-Projekt besteht. Im eigentlichen "dependencies"-Element können wir auf diese zugreifen.
Der "scope"-Wert "import" steht nur im Element "dependencyManagement" zur Verfügung. Es bedeutet dass die aktuelle Dependency durch die im Element "dependencyManagement" definierten Dependencies des importierten
Artefakts ersetzt wird. Die so bereitgestellten Dependencies stehen aber noch nicht in Quellcode oder zur Ausführung zur Verfügung, sondern müssen explizit aktiviert werden, siehe nächster Schritt.
Im Element "project" => "dependencies" fügen wir jetzt die eigentliche Abhängigkeit hinzu:
<dependencies>
...
<dependency>
<groupId>org.jboss.shrinkwrap.descriptors</groupId>
<artifactId>shrinkwrap-descriptors-depchain</artifactId>
<type>pom</type>
<scope>test</scope>
</dependency>
</dependencies>
Die Version dieser Dependency ergibt sich aus der im vorherigen Schritt definierten "shrinkwrap-descriptors-bom". Dies ist wiederum nur ein Sammel-Artifact, das eine Reihe von JAR-Dateien einbindet,
für die verschiedenen Typen von Deployment-Deskriptoren, darunter die JavaEE-Standard-Deskriptoren, aber auch die WildFly-spezifischen wie "jboss-web.xml".
Jetzt können wir den Code unserer "getEarArchive"-Methode erweitern, so dass "application.xml" erzeugt wird:
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.descriptor.api.Descriptors;
import org.jboss.shrinkwrap.descriptor.api.application7.ApplicationDescriptor;
@RunWith(Arquillian.class)
public class GeometricModelBeanIT
{
@Deployment
public static Archive<?> getEarArchive()
{
...
ear.addAsManifestResource(f, "StatelessMaven-ds.xml");
ApplicationDescriptor descriptor = Descriptors.create(ApplicationDescriptor.class);
descriptor.applicationName("StatelessMaven-ear");
//Webmodul bauen:
descriptor.createModule().getOrCreateWeb().contextRoot("/StatelessMaven-web").webUri("StatelessMaven-web.war");
//EJB-Modul reinpacken:
descriptor.createModule().ejb("StatelessMaven-ejb.jar");
//In EAR-Datei schreiben:
ear.setApplicationXML(new StringAsset(descriptor.exportAsString()));
war.addPackage("de.hsrm.cs.javaee8.statelessmaven.web.test");
return ear;
}
}
Wie man an dem Import org.jboss.shrinkwrap.descriptor.api.application7.ApplicationDescriptor
sieht, kann die API keine JavaEE8-Deskriptoren erzeugen. Es sieht auch nicht so aus, als würde
sie aktiv weiterentwickelt...
Falls wir eine vorhandene "application.xml" irgendwo abgelegt hätten, könnten wir diese so ins
EnterpriseArchive
schreiben:
@RunWith(Arquillian.class)
public class GeometricModelBeanIT
{
@Deployment
public static Archive<?> getEarArchive()
{
...
f = new File("../pfad/zu/application.xml");
ear.setApplicationXML(f);
ear.addAsManifestResource(f, "StatelessMaven-ds.xml");
...
}
Eigentlich ist die "application.xml" garnicht nötig, man könnte sich den Aufwand also sparen ;-)
Konfiguration des Tests: Profiles
Das Arquillian-Framework übernimmt hier die Testdurchführung. Im Rahmen des Testlaufs wird ein laufender WildFly-Server benötigt. Es gibt zwei Varianten:
- Die Anwendung wird auf einen bereits laufenden Server deployed ("remote server").
- Arquillian selbst startet und stoppt einen WildFly-Server während des Testlaufs ("managed server").
In beiden Fällen wird das Verfahren durch Einträge in "pom.xml" sowie durch eine zusätzliche Datei "arquillian.xml" gesteuert. Der Archetype bereitet das Projekt schon entsprechend vor,
im Folgenden wird deshalb nur der Ist-Zustand beschrieben:
In "pom.xml" des Root-Projekts "StatelessMaven" steht eine Property, die die Version für den "maven-failsafe-plugin" definiert:
<version.surefire.plugin>2.22.1</version.surefire.plugin>
<version.failsafe.plugin>2.22.1</version.failsafe.plugin>
<version.war.plugin>3.2.2</version.war.plugin>
In "pom.xml" des Projekts "StatelessMaven-Web" ist die Abhängigkeit "arquillian-protocol-servlet" eingetragen:
<dependency>
<groupId>org.jboss.arquillian.protocol</groupId>
<artifactId>arquillian-protocol-servlet</artifactId>
<scope>test</scope>
</dependency>
Die Datei "arquillian.xml" im Projekt "StatelessMaven-web" im Unterverzeichnis "src\test\resources" wird benötigt:
Sie hat diesen Inhalt:
<arquillian xmlns="http://jboss.org/schema/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">
<!-- Sets the protocol which is how Arquillian talks and executes the tests inside the container -->
<defaultProtocol type="Servlet 3.0" />
...
</arquillian>
Das Element "defaultProtocol" gibt an, mit welchem Verfahren Arquillian arbeiten soll und steht hier auf "Servlet 3.0" - siehe Abschnitt Wie der Unit-Test abläuft.
An die Stelle der drei Punkte kommen die Konfigurationen für die Remote- bzw. Managed Server.
Man könnte sich für eines der beiden Verfahren entscheiden, oder man legt sich Maven-Profile an und gibt beim Testlauf an, welches Profil zu verwenden ist. Im Folgenden wird die Variante mit zwei Profilen beschrieben.
ACHTUNG:
In "arquillian.xml" dürfen keine Umlaute (oder allgemein: Zeichen mit ASCII-Index höher als 127) verwendet werden.
Ansonsten erhält man diesen Fehler:
[INFO] Running de.hsrm.cs.javaee7.statelessmaven.web.test.GeometricModelBeanIT
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.264 s <<< FAILURE! - in de.hsrm.cs.javaee7.statelessmaven.web.test.GeometricModelBeanIT
[ERROR] de.hsrm.cs.javaee7.statelessmaven.web.test.GeometricModelBeanIT Time elapsed: 0.263 s <<< ERROR!
java.lang.RuntimeException: Could not create new instance of class org.jboss.arquillian.test.impl.EventTestRunnerAdaptor
Caused by: java.lang.reflect.InvocationTargetException
Caused by: org.jboss.shrinkwrap.descriptor.api.DescriptorImportException: Could not import XML from stream
Caused by: com.sun.org.apache.xerces.internal.impl.io.MalformedByteSequenceException: Ungültiges Byte 1 von 1-Byte-UTF-8-Sequenz.
Hier liegt wohl ein Bug im Parser der Datei vor:
https://issues.jboss.org/browse/SHRINKDESC-97.
In meinem Fall ist die XML-Datei sowieso unsauber definiert, da sie selbst kein Encoding definiert, und das Default-Encoding des Eclipse-Projekts auf "UTF-8" steht. Aber sogar
wenn in "arquillian.xml" ein sauberes Encoding definiert wird, bleibt der Fehler erhalten.
Konfiguration des Tests: Profile für "Remote Server"
In "pom.xml" des Root-Projekts "StatelessMaven" gibt es Root-Element "project" ein Unterelement "profiles", darin ist ein Profile mit dem Namen "arq-remote" definiert:
<profiles>
<!-- Arquillian WildFly remote profile -->
<profile>
<id>arq-remote</id>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
<dependencies>
<dependency>
<groupId>org.wildfly.arquillian</groupId>
<artifactId>wildfly-arquillian-container-remote</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${version.failsafe.plugin}</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
<configuration>
<!-- Variablen für "arquillian.xml" definieren: -->
<systemPropertyVariables>
<!-- Defines the container qualifier in "arquillian.xml" -->
<arquillian.launch>remote</arquillian.launch>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
Die wichtigsten Einstellungen in diesem Element:
- Es wird eine Profile-ID "arq-remote" vergeben.
- Wenn im Element "activeByDefault" der Wert "true" eingetragen wird, dann könnte man dieses Profil als Default-Profil einstellen, so dass es verwendet wird, wenn beim Maven-Testlauf kein Profil spezifiziert wird.
- Die Dependency "wildfly-arquillian-container-remote" bindet die Arquillian-Komponente ein, die einen Remote-Container ansteuert. Die Version ergibt sich aus dem WildFly-BOM.
- Der Maven-Failsafe-Plugin (der die Test-Ausführung übernimmt) wird registriert und es müssen unter "executions" zwei "goals" definiert werden, die von diesem Plugin verarbeitet werden sollen.
- Für den Maven-Failsafe-Plugin (der die Test-Ausführung übernimmt) wird eine zusätzliche System Property namens "arquillian.launch" gesetzt: diese definiert den Namen einer Configuration in "arquillian.xml", siehe nächster Schritt.
In "arquillian.xml" wird ein "container" eingetragen:
<!-- Configuration to be used when the WildFly remote profile is active -->
<container qualifier="remote">
<configuration>
<property name="managementAddress">127.0.0.1</property>
<property name="managementPort">9990</property>
<!-- If deploying to a remote server, you have to specify username/password here -->
<!--
<property name="username">admin</property>
<property name="password">admin</property>
-->
</configuration>
</container>
- Der Name des Container muss dem in "pom.xml" in der System Property "arquillian.launch" definierten Namen entsprechen.
- "managementAddress" und "managementPort" definieren die Adresse des WildFly-Servers
- "username" und "password" sind hier nicht nötig, da sie bei Zugriff auf einen lokalen Server nicht erforderlich sind.
Konfiguration des Tests: Profile für "Managed Server"
In "pom.xml" legen wir uns im Root-Element "project" ein Unterelement "profiles" an, darin wird ein Profile mit dem Namen "arq-managed" definiert:
<profiles>
...
<profile>
<!-- An optional Arquillian testing profile that executes tests in your JBoss EAP instance.
This profile will start a new JBoss EAP instance, and execute the test, shutting it down when done.
Run with: mvn clean verify -Parq-managed -->
<id>arq-managed</id>
<dependencies>
<dependency>
<groupId>org.wildfly.arquillian</groupId>
<artifactId>wildfly-arquillian-container-managed</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${version.failsafe.plugin}</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
<configuration>
<!-- Configuration for Arquillian: -->
<systemPropertyVariables>
<!-- Defines the container qualifier in "arquillian.xml" -->
<arquillian.launch>managed</arquillian.launch>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
</profile>
...
</profiles>
Die wichtigsten Einstellungen in diesem Element:
- Es wird eine Profile-ID "arq-managed" vergeben.
- Wenn im Element "activeByDefault" der Wert "true" eingetragen wird, dann könnte man dieses Profil als Default-Profil einstellen, so dass es verwendet wird, wenn beim Maven-Testlauf kein Profil spezifiziert wird.
- Die Dependency "wildfly-arquillian-container-managed" bindet die Arquillian-Komponente ein, die einen Managed-Container ansteuert. Die Version wird aus dem WildFly-BOM geholt.
- Der Maven-Failsafe-Plugin (der die Test-Ausführung übernimmt) wird registriert und es müssen unter "executions" zwei "goals" definiert werden, die von diesem Plugin verarbeitet werden sollen.
- Für den Maven-Failsafe-Plugin wird eine zusätzliche System Property Variable namens "arquillian.launch" gesetzt: diese definiert den Namen einer Configuration in "arquillian.xml", siehe nächster Schritt.
In "arquillian.xml" wird ein "container" eingetragen:
<!-- Configuration to be used when the WildFly managed profile is active -->
<container qualifier="managed">
<!-- By default, Arquillian will use the JBOSS_HOME environment variable to find the JBoss EAP installation.
If you prefer not to define the JBOSS_HOME environment variable, alternatively you can uncomment the
following `jbossHome` property and replace EAP_HOME with the path to your JBoss EAP installation. -->
<!--
<configuration>
<property name="jbossHome">EAP_HOME</property>
</configuration>
-->
</container>
- Der Name des Container muss dem in "pom.xml" in der System Property "arquillian.launch" definierten Namen entsprechen.
- Per Default wird der Pfad zum WildFly-Server aus der Systemproperty/Umgebungsvariablen "JBOSS_HOME" geholt (siehe weiter unten).
Alternativ könnte man die Property "jbossHome" definieren und dort den Pfad zum WildFly-Server eintragen.
Alternative: es ist möglich, Maven den Server aus dem Maven Repository herunterladen zu lassen. Danach startet Maven ihn selbst:
<profiles>
<!-- Arquillian WildFly managed profile -->
<profile>
<id>arq-managed</id>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
<dependencies>
<dependency>
<groupId>org.wildfly.arquillian</groupId>
<artifactId>wildfly-arquillian-container-managed</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.0.2</version>
<executions>
<execution>
<id>unpack</id>
<phase>pre-integration-test</phase>
<goals>
<goal>unpack</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.wildfly</groupId>
<artifactId>wildfly-dist</artifactId>
<version>15.0.0.Final</version>
<type>zip</type>
<overWrite>false</overWrite>
<outputDirectory>target</outputDirectory>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.20</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
<configuration>
<!-- Variablen für "arquillian.xml" definieren: -->
<systemPropertyVariables>
<jboss.home>${project.basedir}/target/wildfly-15.0.0.Final</jboss.home>
<!-- Auszuführende Konfiguration in "arquillian.xml" -->
<arquillian.launch>managed</arquillian.launch>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
</profile>
...
</profiles>
Unterschiede zur vorherigen Konfiguration:
- Es wird ein neuer Plugin "maven-dependency-plugin" registriert, der in der Phase "pre-integration-test" (vor Aufruf der Integrationstests durch den "maven-failsafe-plugin") aktiv wird und einen
WildFly-Server aus dem Maven-Repository herunterlädt und in das Unterverzeichnis "target" entpackt.
- Der Maven-Failsafe-Plugin setzt außerdem eine System Property Variable namens "jboss.home", in die der Pfad der WildFly-Runtime geschrieben wird, die der "maven-dependency-plugin" heruntergeladen hat: "${project.basedir}/target/wildfly-11.0.0.Final".
Auf diese Variable wird in "arquillian.xml" zugegriffen. Dadurch können wir "arquillian.xml" unabhängig von der WildFly-Version halten. Bei einer Änderung der WildFly-Version müssten wir diesen Pfad anpassen.
In
"arquillian.xml" sieht der Container "managed" etwas anders aus als im vorherigen Beispiel:
<!-- Configuration to be used when the WildFly managed profile is active -->
<container qualifier="managed" default="false" >
<configuration>
<property name="jbossHome">${jboss.home}</property>
</configuration>
</container>
- Die Property "jbossHome" definiert den Pfad zum WildFly-Server. Hier wird dies aus einer Variable "jboss.home" gelesen, die in "pom.xml" vom "maven-failsafe-plugin" gesetzt wurde und auf "${project.basedir}/target/wildfly-15.0.0.Final" zeigt.
Bei Ausführung dieser Tests wird der WildFly-Server einmalig aus dem Maven-Repository ins lokale Repository geladen, und beim Testlauf (auch z.B. nach Aufrufen von "clean") neu aus dem lokalen Repository entpackt:
[INFO] --- maven-dependency-plugin:3.0.2:unpack (unpack) @ StatelessMaven-web ---
[INFO] Configured Artifact: org.wildfly:wildfly-dist:11.0.0.Final:zip
[INFO] Unpacking C:\Users\USERNAME\.m2\repository\org\wildfly\wildfly-dist\11.0.0.Final\wildfly-dist-11.0.0.Final.zip to C:\Temp\workspace\StatelessMaven\StatelessMaven-web\target with includes "" and excludes ""
Hier könnte es optimaler sein, wenn man den WildFly-Server in ein externes Verzeichnis legt.
Variante 1: Ausführen als Maven-Test über Eclipse
Weiter unten ist beschrieben, warum der einfache Weg für das Ausführen von Tests aus Eclipse heraus nicht klappt.
Variante 1a: remote Server:
Hier wird das Deploy im Rahmen des Tests gegen einen bereits laufenden WildFly-Server ausgeführt.
Wir legen eine "Run Configuration" in der Gruppe "Maven" an, setzen das "Base directory" auf "${workspace_loc:/StatelessMaven}" und geben bei "Profiles" das auszuführende Profil an: "arq-remote" oder "arq-managed".
Da in beiden Profilen der "maven-failsafe-plugin" konfiguriert wurde, stellen wir außerdem das "Goal" "verify" ein (statt "failsafe:integration-test"): das führt dazu dass nach dem Integrationstest
geprüft wird dass die Tests erfolgreich waren.
Hier die Konfiguration für das Profile "arq-remote" - der WildFly-Server muss vorher händisch gestartet werden!
Variante 1b: managed Server:
Hier die Konfiguration für das Profile "arq-managed" - hier wird der WildFly-Server von Arquillian gestartet:
Sofern man keine systemweite Umgebungsvariable "JBOSS_HOME" definiert hat, die zum WildFly-Server zeigt, muss man die Umgebungsvariable im Maven-Profil definieren. Dies geschieht auf dem Karteireiter "Environment":
Jetzt können wir den Test laufen lassen.
Bei Verwendung des Profiles "arq-managed" sehen wir die WildFly-Startmeldungen in der gleichen Konsole, in der auch die Ausgabe des Tests liegt.
Eventuell muss auf dem Karteireiter "JRE" ein JDK ausgewählt werden, wenn Eclipse eine JRE verwendet.
Eventuell zeigt der Test eine solche Konsolenausgabe:
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-failsafe-plugin:2.20:verify (default) on project StatelessMaven-web: There are test failures.
[ERROR]
[ERROR] Please refer to C:\Temp\workspace\StatelessMaven\StatelessMaven-web\target\failsafe-reports for the individual test results.
[ERROR] Please refer to dump files (if any exist) [date]-jvmRun[N].dump, [date].dumpstream and [date]-jvmRun[N].dumpstream.
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException
[ERROR]
[ERROR] After correcting the problems, you can resume the build with the command
[ERROR] mvn -rf :StatelessMaven-web
Wie die Fehlermeldung schon besagt, finden wir im Unterverzeichnis "target\failsafe-reports" des Projekts, das den Fehler verursacht hat, eine Logdatei.
Für "reguläre" Unit-Tests würde es reichen, im Contextmenü den Punkt "Run as" => "9 Maven test" aufzurufen.
Allerdings funktioniert das nicht, da wir Integrationstests haben. Für deren Ausführung gibt es keinen Contextmenüpunkt. Ein Lösungsansatz wäre: wir öffnen über den Toolbar-Button "Run"
die "Run configurations" und legen unter "Maven" eine neue an. Als "Base directory" wählen wir "${workspace_loc:/StatelessMaven}" (Root-Projekt des Multi-Modul-Projekts) aus,
als "Goal" geben wir "failsafe:integration-test" ein - das führt alle Schritte des Build Lifecycle bis zum Integrationstest durch.
Wenn wir als Goal nur "integration-test" angeben, dann scheinen keine Tests ausgeführt zu werden. Grund scheint zu sein, dass der "maven-failsafe-plugin" nicht per Default ausgeführt wird,
sondern im "pom.xml" explizit aktiviert werden muss. Das ist hier nicht geschehen. Entsprechend bewirkt auch der Schritt "verify", den man eigentlich bei Aufruf des "maven-failsafe-plugin" verwenden sollte, nichts.
Das Ausführen des Goal "failsafe:integration-test" führt allerdings zu einem Fehler:
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.451 s <<< FAILURE! - in de.hsrm.cs.javaee7.statelessmaven.web.test.GeometricModelBeanIT
[ERROR] de.hsrm.cs.javaee7.statelessmaven.web.test.GeometricModelBeanIT Time elapsed: 0.45 s <<< ERROR!
org.jboss.arquillian.container.test.impl.client.deployment.ValidationException:
DeploymentScenario contains a target (_DEFAULT_) not matching any defined Container in the registry.
Please include at least 1 Deployable Container on your Classpath.
Ursache ist, dass wir zwei Profile angelegt haben und keines davon als Default-Profil definiert ist. Und dies wiederum führt zu den eingangs beschriebenen Wegen für den Start des Unit-Tests.
Variante 2: Ausführen außerhalb Eclipse über Maven
Wir benötigen eine Maven-"Installation" und können dann aus dem "StatelessMaven-web"-Projekt heraus die Unit-Tests unter Angabe eines Profils aufrufen:
c:\pfad\zu\maven\apache-maven-3.5.0\bin\mvn.cmd -Parq-managed verify
Im Beispiel wird das Profil "arq-managed" verwendet.
Variante 3: Ausführen als Eclipse-Unit-Test
Diese Variante hat den Vorteil, dass wir den Unit-Test debuggen können (wobei ein besseres Verfahren im nächsten Schritt beschrieben wird). Allerdings gibt es einige Einschränkungen:
- Da komplett ohne Maven gearbeitet wird, erfolgt im Rahmen der Testausführung kein Maven-Build-Vorgang für EJB- und Web-Projekt. Die von diesem Build-Vorgang erzeugte "JAR"- bzw. "WAR"-Datei benötigt
der ShrinkWrap-Aufruf der
@Deployment
-annotierten Methode allerdings.
Wir müssen also sicherstellen, dass der Build vorher erfolgt ist: wir rufen die Goals "clean install" auf. Das würde wiederum einen unnötigen Testlauf durchführen, sofern wir Unit-Tests im Projekt hätten,
die vom "maven-surefire-plugin" angefasst werden. In unserem Fall macht das aber kein Problem, da wir keine solchen Tests haben und der "maven-failsafe-plugin" für die Integrationstests nicht im Default-Profil
aktiviert ist. Ansonsten könnte man sich behelfen, indem man in der "Run Configuration" die Option "skip tests" einschaltet:
- Der WildFly-Server muss im Debug-Modus gestartet sein, und Eclipse muss an den Server attached sein.
Den Debug-Modus kann man über Änderungen an "standalone.conf.bat" aktivieren: dort finden sich zwei auskommentierte Zeilen:
rem # Uncomment to run server in debug mode
rem set "DEBUG_MODE=true"
rem set "DEBUG_PORT=8787"
Danach muss Eclipse dazu gebracht werden, ein Remote Debugging durchzuführen. Dies scheint nur für "normale" Java-Anwendungen zu klappen, siehe Anleitung in
http://www.it-adviser.net/wildfly-debug-mode-aktivieren-und-mit-eclipse-verbinden/.
Wir wollen hier allerdings einen JUnit-Test debuggen. Dafür habe ich keine Konfigurationsmöglichkeit gefunden.
Aber es geht ganz einfach: wir nutzen die "Servers"-View, in der wir einen WildFly 11-Server angelegt haben sollten. Diesen starten wir im Debug-Modus:
- Profile funktionieren nicht (zumindest habe ich keinen Weg gefunden, ein Profil anzugeben). Das bedeutet, dass wir das Profil "arq-wildfly-remote-web" als "Default" definieren müssen:
<profile>
<id>arq-remote</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<dependencies>
- Für einen Managed Server habe ich ebenfalls nicht herausgefunden, wie man ihn debuggen könnte. Vermutlich müsste man entsprechende JVM-Parameter übergeben und den Eclipse-Debugger attachen.
Also bleibt uns nur das Remote-Profil.
Jetzt legen wir eine "Debug Configuration" in der Gruppe "JUnit" an. Es wird die Option "Run all tests in the selected project" gewählt und das Projekt "StatelessMaven-web" angegeben.
Beim Ausführen dieser Debug Configuration wird das Arquillian-Framework angestoßen, und es sollten jetzt Breakpoints in der mit @Deployment
annotierten Methode
(diese wird auf Client-Seite ausgeführt) und auch in den @Test
-Methoden angesprungen werden.
Debugging der Unit Test-Clientseite
In diesem Abschnitt wird beschrieben, wie die Clientseite der Unit Tests (also die Klasse GeometricModelBeanIT
) gedebuggt werden kann. Wird die Run Configuration für den Maven Build im Debugmodus
ausgeführt, dann passiert einfach nur nichts - keine Breakpoints werden angesprungen.
Im Web findet sich der Tip, in der Run Configuration einen Parameter "forkCount
" auf den Wert "0" zu setzen:
Dies funktioniert in meinem Beispiel allerdings nicht: Es wird es wird eine java.io.FileNotFoundException
ausgelöst im Erzeugen des ShrinkWrap-Archivs, und zwar an der Stelle wo
"StatelessMaven-ejb.jar" in die EAR-Datei eingebunden werden soll. Ursache ist, dass ein relativer Pfad verwendet wird. Durch die Art und Weise wie Maven die Java VM durch das Setzen des Parameters "forkCount
" aufruft, ändert sich für einige Dateizugriffsaufrufe der
aktuelle Pfad, siehe auch der Hinweis in https://bugs.java.com/bugdatabase/view_bug?bug_id=4483097. Dadurch funktionieren relative Pfade nicht mehr,
z.B. bei einem Aufruf von File.exists
oder ClassLoader.getResource
.
Lösung:
Siehe https://maven.apache.org/surefire/maven-surefire-plugin/examples/debugging.html: Maven wird über die Kommandozeile mit
dem Parameter -Dmaven.surefire.debug
(für "normale" Unittests) bzw. -Dmaven.failsafe.debug
(für Integrationstests) gestartet. Bei mir sieht der Aufruf dann so aus:
mvn -Parq-remote -Dmaven.failsafe.debug verify
Jetzt stoppt Maven die Testausführung mit dieser Ausgabe:
Listening for transport dt_socket at address: 5005
Also legt man in Eclipse eine Debug Configuration vom Typ "Remote Java Application" an und verbindet sich an Port 5005:
Sobald der Debugger sich verbunden hat, geht der Maven-Lauf weiter und man kann in Eclipse debuggen.
Ausführen mit Java 17
Im Profil "arq-managed" wird es beim Ausführen mit Java 17 eine Fehlermeldung geben:
19:33:11,840 ERROR [org.jboss.msc.service.fail] (MSC service thread 1-8) MSC000001: Failed to start service org.wildfly.clustering.infinispan.cache-container-configuration.ejb:
org.jboss.msc.service.StartException in service org.wildfly.clustering.infinispan.cache-container-configuration.ejb: java.lang.ExceptionInInitializerError
at org.wildfly.clustering.service@26.0.1.Final//org.wildfly.clustering.service.FunctionalService.start(FunctionalService.java:66)
at org.jboss.msc@1.4.13.Final//org.jboss.msc.service.ServiceControllerImpl$StartTask.startService(ServiceControllerImpl.java:1739)
at org.jboss.msc@1.4.13.Final//org.jboss.msc.service.ServiceControllerImpl$StartTask.execute(ServiceControllerImpl.java:1701)
at org.jboss.msc@1.4.13.Final//org.jboss.msc.service.ServiceControllerImpl$ControllerTask.run(ServiceControllerImpl.java:1559)
at org.jboss.threads@2.4.0.Final//org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
at org.jboss.threads@2.4.0.Final//org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:1990)
at org.jboss.threads@2.4.0.Final//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1486)
at org.jboss.threads@2.4.0.Final//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1377)
at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.ExceptionInInitializerError
...
... 8 more
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make field private final java.lang.Class java.util.EnumMap.keyType accessible: module java.base does not "opens java.util" to unnamed module @af418a8
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)
at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:178)
at java.base/java.lang.reflect.Field.setAccessible(Field.java:172)
at org.wildfly.clustering.marshalling.protostream@26.0.1.Final//org.wildfly.clustering.marshalling.protostream.util.EnumMapMarshaller$1.run(EnumMapMarshaller.java:53)
at org.wildfly.clustering.marshalling.protostream@26.0.1.Final//org.wildfly.clustering.marshalling.protostream.util.EnumMapMarshaller$1.run(EnumMapMarshaller.java:48)
at org.wildfly.security.elytron-base@1.18.3.Final//org.wildfly.security.manager.WildFlySecurityManager.doUnchecked(WildFlySecurityManager.java:838)
at org.wildfly.clustering.marshalling.protostream@26.0.1.Final//org.wildfly.clustering.marshalling.protostream.util.EnumMapMarshaller.(EnumMapMarshaller.java:48)
... 30 more
Zwei Workaround sind hier zu finden: https://developer.jboss.org/thread/279810
Workaround 1:
Es wird eine Property "javaVmArguments" zu "arquillian.xml" und dem Container "managed" zugefügt (der vom Profil "arq-managed" verwendet wird):
<container qualifier="managed">
<configuration>
<property name="javaVmArguments">--add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED</property>
</configuration>
</container>
Eigentlich könnte man auch den "--add-opens"-Teil der WildFly-Befehlszeile kopieren, die ausgeführt wird beim Start über "standalone.bat". Aber diese zwei Stücke sind auf jeden Fall erforderlich, um das Beispiel auszuführen.
Workaround 2:
In "StatelessMaven/pom.xml" wird die Version des Plugins "org.wildfly.arquillian:wildfly-arquillian-container-managed" überdefiniert, die per Default aus dem WildFly-BOM vorbelegt ist (Für WildFly 26.x: 3.0.1.Final).
Oben verlinkter Post deklariert eine Version "2.2.0.Final" als funktionsfähig, diese klappte aber bei mir nicht - eventuell weil der Post sich noch auf Java 11 bezieht.
Aber es funktioniert mit neueren Versionen, z.B. aktuell mit "5.0.0.Alpha6":
<profiles>
<profile>
<id>arq-managed</id>
<dependencies>
<dependency>
<groupId>org.wildfly.arquillian</groupId>
<artifactId>wildfly-arquillian-container-managed</artifactId>
<version>5.0.0.Alpha6</version>
<scope>test</scope>
</dependency>
</dependencies>
Da diese Version im BOM von WildFly 27 automatisch gesetzt ist, wird der Workaround dort nicht mehr nötig sein.
Wie der Unit-Test abläuft
Der oben gezeigte Aufruf der ShrinkWrap-API erzeugt ein Archiv, das vom Arquillian-Framework auf den Anwendungsserver deployed wird. Vor dem Deploy erweitert das Arquillian-Framework
dieses Archiv um "Dinge": Man werfe einen Blick in "pom.xml" des Webprojekts:
<dependency>
<groupId>org.jboss.arquillian.protocol</groupId>
<artifactId>arquillian-protocol-servlet</artifactId>
<scope>test</scope>
</dependency>
Diese Dependency findet sich hier:
https://repo1.maven.org/maven2/org/jboss/arquillian/protocol/arquillian-protocol-servlet/1.6.0.Final/.
In dieser Datei findet sich unter "org\jboss\arquillian\protocol\servlet\v_3" eine Datei "web-fragment.xml", die ein Servlet "ArquillianServletRunner" definiert.
Mit einem Trick kann man die EAR-Datei anschauen, die vom Unit-Test-Framework auf eine bereits laufenden WildFly-Server deployed wird (geschickterweise nimmt man hier das Remote-Profil, da man dann eher in den Server schauen kann):
man fügt in eine beliebige @Test
-Methode ein ausreichend langes Thread.Sleep
ein. Dann hat man Zeit, um sich den Pfad zur deployten EAR-Datei aus "standalone.xml" zu holen und die Datei im
Unterverzeichnis "%WILDFLY_HOME%\standalone\data\content\..." zu finden. Diese Datei schauen wir uns an:
In der EAR-Datei selbst findet sich ein neues Verzeichnis "lib", in dem viele Arquillian-Jars liegen:
In dem EAR befindet sich natürlich auch unsere "war"-Datei:
Unter "WEB-INF\lib" befindet sich eine Datei "arquillian-protocol.jar". Schauen wir uns diese Datei an, sehen wir dass sie unter "META-INF" die schon bekannte "web-fragment.xml" enthält, die
das Servlet "org.jboss.arquillian.protocol.servlet.runner.ServletTestRunner" definiert. Wir können also davon ausgehen, dass in unserer Webanwendung unter
http://127.0.0.1:8080/StatelessMaven-web/ArquillianServletRunner ein bisher unbekannter Gast antwortet.
Aus den Teilen der eingangs genannten Dependency "arquillian-protocol-servlet-1.6.0.Final.jar" wird also eine JAR-Datei gebaut, die in unsere EAR-Anwendung injiziert wird. Das erklärt auch, warum in "arquillian.xml"
folgendes Element steht:
<defaultProtocol type="Servlet 3.0" />
Hiermit wird das Verfahren definiert, mittels dem die Client-Seite des Arquillian-Framework sich mit der serverseitigen Gegenseite verbindet: es wird der
Pfad "org\jboss\arquillian\protocol\servlet\v_3" aus "arquillian-protocol-servlet-1.6.0.Final.jar" aktiv.
Beim Blick in die Klasse "org.jboss.arquillian.protocol.servlet.runner.ServletTestRunner" bzw. deren Methode "execute" stellen wir fest, dass über einen simplen GET-Request und ein paar HTML-Parameter
definiert werden kann, welche Testmethode aufgerufen werden soll.
Die URL für eine der beiden Methoden meines Unit-Test-Beispiels sieht also so aus:
http://127.0.0.1:8080/StatelessMaven-web/ArquillianServletRunner?outputMode=serializedObject&className=de.hsrm.cs.javaee8.statelessmaven.web.test.GeometricModelBeanIT&methodName=testCuboidVolume
Sie ist am einfachsten aufrufbar, indem man die EAR-Datei, die ich mir weiter oben durch den "Thread.sleep"-Trick vom Server gezogen hatte, erneut auf den Server deployed.
Mit dem Ergebnis kann man leider nicht viel anfangen, es ist ein binär serialiertes Java-Objekt, das die Ergebnisse des Aufrufs der Testmethode (Erfolg, Assertion-Verletzungen oder Exceptions) enthält.
Den Parameter "outputMode" kann man im Moment nur auf "serializedObject" stellen. Im Quellcode findet man einen Zweig, über dem "// TODO: implement a html view of the result" steht - schade.
Stand 23.04.2023
Historie:
13.02.2018: erstellt
07.01.2019: WildFly 15, Hinweis: keine Umlaute in "arquillian.xml"
16.02.2019: diverse Plugins aktualisiert
15.01.2020: Projekt auf Basis des Archetype "wildfly-jakartaee-ear-archetype" erstellt - dadurch weniger Nacharbeiten an "pom.xml" nötig, Profile "arq-managed" startet jetzt Server aus "JBOSS_HOME"-Variable, WildFly 18
15.01.2022: WildFly 26, JavaEE8
16.01.2023: Hinweis zu Fehler bei Java 17
23.04.2023: Debugging der UnitTests bei Maven-Ausführung.