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-Serverseite
Debugging der Unit Test-Clientseite über Maven, Variante 1
Debugging der Unit Test-Clientseite über Maven, Variante 2
Debugging der Unit Test-Clientseite über Eclipse-Unit-Test
Wie der Unit-Test abläuft


Für WildFly 35 und JakartaEE10: 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):
  1. validate
  2. initialize
  3. generate-sources
  4. process-sources
  5. generate-resources
  6. process-resources
  7. compile
  8. process-classes
  9. generate-test-sources
  10. process-test-sources
  11. generate-test-resources
  12. process-test-resources
  13. test-compile
  14. process-test-classes
  15. test
  16. prepare-package
  17. package
  18. pre-integration-test
  19. integration-test
  20. post-integration-test
  21. verify
  22. install
  23. 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!

Code des Tests

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:
Testklasse

Sie hat diesen Code:
package de.hsrm.jakartaee.knauf.statelessmaven.web.test;

import jakarta.ejb.EJB;

import org.jboss.arquillian.junit5.ArquillianExtension;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import de.hsrm.jakartaee.knauf.statelessmaven.ejb.GeometricModelLocal;

@ExtendWith(ArquillianExtension.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.
    Assertions.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.
    Assertions.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.jupiter.api.extension.ExtendWith hat als Parameter die Klasse org.jboss.arquillian.junit5.ArquillianExtension: 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>org.junit.jupiter</groupId>
			<artifactId>junit-jupiter</artifactId>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.jboss.arquillian.junit5</groupId>
			<artifactId>arquillian-junit5-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 Version von JUnit 5 ist im Root-"pom.xml" des Projeks definiert:
    <properties>
        <version.junit5>5.10.1</version.junit5>
    </properties>
Die Version für "org.jboss.arquillian.junit5:arquillian-junit5-container" ist nicht in diesem Projekt definiert, sie ergibt sich aus dem WildFly-BOM, in unserem Fall aus
https://repo.maven.apache.org/maven2/org/wildfly/bom/wildfly-ee-with-tools/35.0.0.Final/wildfly-ee-with-tools-35.0.0.Final.pom bzw. dem von diesem eingebundenen https://repo.maven.apache.org/maven2/org/wildfly/bom/wildfly-tools/35.0.0.Final/wildfly-tools-35.0.0.Final.pom, das in "pom.xml" des Root-Projekts eingebunden ist. Dies ist Arquillian 1.9.2.Final.
Das WildFly-BOM definiert übrigens noch eine Dependency zu JUnit 4, die wir hier aber nicht benutzen.

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: 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.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit5.ArquillianExtension;
import org.jboss.shrinkwrap.api.Archive;
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;
...

@ExtendWith(ArquillianExtension.class)
public class GeometricModelBeanIT
{
  @Deployment
  public static Archive<?> getEarArchive() 
  {
    EnterpriseArchive ear = ShrinkWrap.create(EnterpriseArchive.class, "StatelessMaven-ear.ear");
    
    File f = new File("../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("../web/target/StatelessMaven-web.war");
    WebArchive war = ShrinkWrap.create(ZipImporter.class, "StatelessMaven-web.war").
           importFrom(f).as(WebArchive.class) ;
    ear.addAsModule(war);
    
    war.addPackage("de.hsrm.jakartaee.knauf.statelessmaven.web.test");
      
    return ear;
  }
}
Zur Erklärung: 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.jakartaee.knauf.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="https://jakarta.ee/xml/ns/jakartaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/application_10.xsd" version="10">
  <description>This is the EAR POM file</description>
  <display-name>StatelessMaven-ear</display-name>
  <module>
    <ejb>StatelessMaven-ejb.jar</ejb>
  </module>
  <module>
    <web>
      <web-uri>StatelessMaven-web.war</web-uri>
      <context-root>/StatelessMaven-web</context-root>
    </web>
  </module>
  <library-directory>lib</library-directory>
</application>	

Diese würden wir im Idealfall 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.

Im JakartaEE8-Beispiel bin ich den Weg über die ShrinkWrap-API bzw. deren Unterprojekt "ShrinkWrap Descriptors" gegangen: (http://arquillian.org/modules/descriptors-shrinkwrap/).
Diese unterstützt allerdings maximal JavaEE7, weshalb es kein gangbarer Weg ist. Es gibt ein Ticket, um Unterstützung für JakartaEE 10 zu implementieren: https://github.com/shrinkwrap/descriptors/issues/115.

Für ein funktionierendes Deploy ist die "application.xml" nicht nötig, in diesem Beispiel sparen wir uns also den Aufwand.

Falls wir eine vorhandene "application.xml" irgendwo abgelegt hätten, könnten wir diese so ins EnterpriseArchive schreiben:
@ExtendWith(ArquillianExtension.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");
    ...
  }


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:
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.failsafe.plugin>3.5.2</version.failsafe.plugin>
Weiter unten in den einzelnen Profilen wird dieser Plugin eingebunden.

Die Datei "arquillian.xml" im Projekt "StatelessMaven-web" im Unterverzeichnis "src\test\resources" wird benötigt:
arquillian.xml
Sie hat diesen Inhalt:
<?xml version="1.0" encoding="UTF-8"?>
<arquillian xmlns="http://jboss.org/schema/arquillian"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://jboss.org/schema/arquillian
                        https://jboss.org/schema/arquillian/arquillian_1_0.xsd">

    <!-- Use the Servlet 6.0 protocol to communicate with the container -->
    <defaultProtocol type="Servlet 6.0" />

    ...
</arquillian>
Das Element "defaultProtocol" gibt an, mit welchem Verfahren Arquillian arbeiten soll und steht hier auf "Servlet 6.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.jakartaee.knauf.statelessmaven.web.test.GeometricModelBeanIT
[Fatal Error] :4:41: Ungültiges Byte 2 von 4-Byte-UTF-8-Sequenz.
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.195 s <<< FAILURE! -- in de.hsrm.jakartaee.knauf.statelessmaven.web.test.GeometricModelBeanIT
[ERROR] de.hsrm.jakartaee.knauf.statelessmaven.web.test.GeometricModelBeanIT -- Time elapsed: 0.195 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: org.xml.sax.SAXParseException; lineNumber: 4; columnNumber: 41; Ungültiges Byte 2 von 4-Byte-UTF-8-Sequenz.
Caused by: com.sun.org.apache.xerces.internal.impl.io.MalformedByteSequenceException: Ungültiges Byte 2 von 4-Byte-UTF-8-Sequenz.
Hier liegt wohl ein Bug im Parser der Datei vor: https://web.archive.org/web/20200512135200/https://issues.redhat.com/browse/SHRINKDESC-97 (das original Issue gibt es nicht mehr) und eine neue Kopie davon: https://github.com/shrinkwrap/descriptors/issues/119.
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:

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>


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:

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>
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.8.1</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>35.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-35.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:
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>
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:
(die folgende Ausgabe stammt es einer älteren Version des dependency-plugin, der mehr Logausgabe schrieb):
[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!
Maven configuration (remote server)

Variante 1b: managed Server:
Hier die Konfiguration für das Profile "arq-managed" - hier wird der WildFly-Server von Arquillian gestartet:
Maven configuration (managed server)
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":
Maven configuration (JBOSS_HOME)

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 zeigt der Test eine solche Konsolenausgabe:
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-failsafe-plugin:3.5.2:verify (default) on project StatelessMaven-web:
[ERROR] 
[ERROR] See C:\Temp\workspace\StatelessMaven\web\target\failsafe-reports for the individual test results.
[ERROR] See dump files (if any exist) [date].dump, [date]-jvmRun[N].dump and [date].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 <args> -rf :StatelessMaven-web

Abgesehen davon, dass die Fehlermeldungen weiter oben in der Konsole stehen sollten, 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" => "8 Maven test" bzw. "9 Maven verify" aufzurufen.
Maven test

Ersters würde für Unittests mit dem "Maven surefire plugin" funktionieren, zweiteres für Integrationstests über den "Maven failsafe plugin" (würde also zu unserer Situation passen).
Allerdings werden trotzdem keine Tests ausgeführt. Ursache ist, dass wir zwei Profile haben, in denen der "Maven failsafe plugin" sowie weitere Konfiguration definiert ist. Dieses Profil haben wir oben in der "Run Configuration" angegeben. Solange keines als Defaultprofil markiert ist, können wir nicht die Kontextmenüpunkte verwenden.

Wir könnten auf diesem Weg ein Profil als Defaultprofil aktivieren:
<profile>
	<id>arq-remote</id>
	<activation>
		<activeByDefault>true</activeByDefault>
	</activation>


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.9.9\bin\mvn.cmd -Parq-managed verify
Im Beispiel wird das Profil "arq-managed" verwendet, das einen WildFly-Server gemäß Umgebungsvariable "JBOSS_HOME" startet.


Variante 3: Ausführen als Eclipse-Unit-Test

Diese Variante hat den Vorteil, dass wir die Client-Seite der Unit-Test relativ einfach debuggen können, und Eclipse zeigt die Testergebnisse an. Allerdings gibt es einige Einschränkungen:

Einschränkung 1: 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: dazu legen wir eine "Maven Run Configuration" an, in der die Goals "clean install" aufgerufen werden. Das würde wiederum einen unnötigen Testlauf durchführen, sofern wir Unit-Tests im Projekt hätten, 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:
Skip tests
Diese "Run Configuration" müssen wir jedesmal ausführen, bevor wir den Unit Test starten:

Einschränkung 2: Profile funktionieren nicht (zumindest habe ich keinen Weg gefunden, ein Profil anzugeben). Das bedeutet, dass wir das Profil "arq-remote" als "Default" definieren müssen:
		<profile>
		<id>arq-remote</id>
		<activation>
			<activeByDefault>true</activeByDefault>
		</activation>
		<dependencies>


Jetzt legen wir eine "Run 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. Als Test Runner wird "JUnit 5" angegeben::
Debug configuration
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.

Mit dem Archetype für WildFly 35 schlug der Unit-Test fehl:
Debug-Fehler
Die Fehlermeldung war diese:
java.lang.RuntimeException: Could not create new instance of class org.jboss.arquillian.test.impl.EventTestRunnerAdaptor
	at org.jboss.arquillian.test.spi.SecurityActions.newInstance(SecurityActions.java:146)
	at org.jboss.arquillian.test.spi.SecurityActions.newInstance(SecurityActions.java:89)
	at org.jboss.arquillian.test.spi.TestRunnerAdaptorBuilder.build(TestRunnerAdaptorBuilder.java:49)
	at org.jboss.arquillian.junit5.JUnitJupiterTestClassLifecycleManager.initializeAdaptor(JUnitJupiterTestClassLifecycleManager.java:38)
	at org.jboss.arquillian.junit5.JUnitJupiterTestClassLifecycleManager.getManager(JUnitJupiterTestClassLifecycleManager.java:25)
	at org.jboss.arquillian.junit5.ArquillianExtension.beforeAll(ArquillianExtension.java:40)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
	Suppressed: java.lang.RuntimeException: Arquillian initialization has already been attempted, but failed. See previous exceptions for cause
		at org.jboss.arquillian.junit5.JUnitJupiterTestClassLifecycleManager.handleSuiteLevelFailure(JUnitJupiterTestClassLifecycleManager.java:62)
		at org.jboss.arquillian.junit5.JUnitJupiterTestClassLifecycleManager.getManager(JUnitJupiterTestClassLifecycleManager.java:30)
		at org.jboss.arquillian.junit5.ArquillianExtension.afterAll(ArquillianExtension.java:47)
		... 1 more
	Caused by: [CIRCULAR REFERENCE: java.lang.RuntimeException: Could not create new instance of class org.jboss.arquillian.test.impl.EventTestRunnerAdaptor]
Caused by: java.lang.reflect.InvocationTargetException
	at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
	at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
	at org.jboss.arquillian.test.spi.SecurityActions.newInstance(SecurityActions.java:144)
	... 6 more
Caused by: java.lang.IllegalStateException: Defined default protocol Servlet 6.0 can not be found on classpath
	at org.jboss.arquillian.container.test.impl.client.protocol.ProtocolRegistryCreator.createRegistry(ProtocolRegistryCreator.java:58)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at org.jboss.arquillian.core.impl.ObserverImpl.invoke(ObserverImpl.java:86)
	at org.jboss.arquillian.core.impl.EventContextImpl.invokeObservers(EventContextImpl.java:103)
	at org.jboss.arquillian.core.impl.EventContextImpl.proceed(EventContextImpl.java:90)
	at org.jboss.arquillian.core.impl.ManagerImpl.fire(ManagerImpl.java:134)
	at org.jboss.arquillian.core.impl.ManagerImpl.fire(ManagerImpl.java:106)
	at org.jboss.arquillian.core.impl.ManagerImpl.bindAndFire(ManagerImpl.java:233)
	at org.jboss.arquillian.core.impl.InstanceImpl.set(InstanceImpl.java:67)
	at org.jboss.arquillian.config.impl.extension.ConfigurationRegistrar.loadConfiguration(ConfigurationRegistrar.java:71)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at org.jboss.arquillian.core.impl.ObserverImpl.invoke(ObserverImpl.java:86)
	at org.jboss.arquillian.core.impl.EventContextImpl.invokeObservers(EventContextImpl.java:103)
	at org.jboss.arquillian.core.impl.EventContextImpl.proceed(EventContextImpl.java:90)
	at org.jboss.arquillian.core.impl.ManagerImpl.fire(ManagerImpl.java:134)
	at org.jboss.arquillian.core.impl.ManagerImpl.fire(ManagerImpl.java:106)
	at org.jboss.arquillian.core.impl.ManagerImpl.start(ManagerImpl.java:254)
	at org.jboss.arquillian.test.impl.EventTestRunnerAdaptor.<init>(EventTestRunnerAdaptor.java:61)
	... 9 more
Lösung/Workaround: in "StatelessMaven-web\pom.xml" wird in den Dependencies diese Deklaration zugefügt (die Versionsnummer ergibt sich aus dem WildFly-BOM):
        <dependency>
            <groupId>org.jboss.arquillian.protocol</groupId>
            <artifactId>arquillian-protocol-servlet-jakarta</artifactId>
            <scope>test</scope>
        </dependency>
Von Maven wird diese Abhängigkeit automatisch gefunden, aber scheinbar nicht, wenn wir den Test über JUnit ausführen.


Debugging der Unit Test-Serverseite

Dies betrifft den Code der eigentlichen @Test-Methoden, die auf dem WildFly-Server ausgeführt werden.
Sobald wir Breakpoints in Eclipse gesetzt haben, können wir die Unittest-Ausführung über die oben beschriebenen Varianten 1 oder 3 anstoßen. Eclipse sollte dann die Breakpoints anspringen.

Für einen Managed Server habe ich 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.

Variante 1:
Wir nutzen die "Servers"-View, in der wir einen WildFly 35-Server angelegt haben sollten. Diesen starten wir im Debug-Modus:
Debug server


Variante 2:
Den Debug-Modus kann man über Änderungen an "standalone.conf.bat" aktivieren: dort finden sich diese Zeilen:
rem # Uncomment to run server in debug mode
rem set "DEBUG_MODE=true"
rem set "DEBUG_PORT=8787"
Diese Zeilen einkommentieren und den Server starten.

In Eclipse eine neue Debug Configuration vom Typ "Remote Application" anlegen. Als "Project" habe ich "StatelessMaven-web" gewählt, ich weiß aber nicht, ob das relevant ist. Den Port "8787" angeben:
Debug Configuration

Jetzt für diese Debug Configuration das Debugging starten. Hier passiert scheinbar noch nichts.


Unabhängig von der oben gewählten Variante: Wir setzen einen Breakpoint in eine @Test-Methode und starten entweder ein Maven-Profil (aus "Variante 1: Ausführen als Maven-Test über Eclipse") oder das JUnit-Profil aus "Variante 3: Ausführen als Eclipse-Unit-Test". Dies muss nicht im Debug-Modus gestartet werden.
Jetzt sollte der Breakpoint angesprungen werden, und vermutlich wird Eclipse eine solche Meldung anzeigen:
Source not found
Also klicken wir auf "Edit Source Lookup Path...". Dort gehen wir auf "Add...", wählen den Typ "Java Project" und wählen "StatelessMaven-web" aus:
Source lookup path
Jetzt können wir auch die Testmethode debuggen.


Debugging der Unit Test-Clientseite über Maven, Variante 1

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 können zwar weiterhin die auf dem Server ausgeführten Tests gedebuggt werden, aber bei Breakpoints in z.B. der @Deployment-Methode passiert einfach nur nichts.

Im Web findet sich der Tip, in der Run Configuration einen Parameter "forkCount" auf den Wert "0" zu setzen:
Fork Count

Dies funktioniert in meinem Beispiel allerdings nicht: 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. Dadurch funktionieren relative Pfade nicht mehr, z.B. bei einem Aufruf von File.exists oder ClassLoader.getResource.

Aber es gibt eine Möglichkeit, dies zu umgehen: in der Debug Configuration wird "${workspace_loc:/StatelessMaven-web}" als "Base Directory" angegeben statt des Root-Projekts "${workspace_loc:/StatelessMaven}":
Fork Count
Dies bedeutet natürlich, dass beim Ausführen nicht das gesamte EAR-Projekt gebaut wird, sondern nur das Web-Projekt. Eventuell muss also vorher eine eigene Konfiguration mit den Goals "clean install" und ("skipTests") ausgeführt werden.

Debugging der Unit Test-Clientseite über Maven, Variante 2

Statt den Unit-Test aus Eclipse heraus mit "forkCount=0" zu starten, kann man den Maven-Lauf auch über die Kommandozeile extern starten und sich dann mit Eclipse anhängen.

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
Dies gilt für eine ältere Version des "maven-failsafe-plugin", von der auch der Screenshot stammt, die aktuelle Version 3.5.2 gibt diese Meldung erst aus, nachdem ein Debugger attached wurde, was sehr irritierend ist: https://issues.apache.org/jira/browse/SUREFIRE-2294.

Maven listening for debugger

Also legt man in Eclipse eine Debug Configuration vom Typ "Remote Java Application" an und verbindet sich an Port 5005:
Remote Java Application
Sobald der Debugger sich verbunden hat, geht der Maven-Lauf weiter und man kann in Eclipse debuggen.


Debugging der Unit Test-Clientseite über Eclipse-Unit-Test

Führen wir die Tests aus wie im Kapitel "Variante 3: Ausführen als Eclipse-Unit-Test" beschrieben, dann werden ohne weitere Zusatzarbeiten die Breakpoints in der @Deployment-Methode angesprungen.


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":
Das WildFly-BOM bindet über dessen Abhängigkeit
https://repo.maven.apache.org/maven2/org/wildfly/bom/wildfly-tools/35.0.0.Final/wildfly-tools-35.0.0.Final.pom eine Abhängigkeit "arquillian-protocol-servlet-jakarta" ein, die die Kommunikation zwischen dem clientseitigen Unittestcode und den Serverkomponenten übernimmt:
		<dependency>
			<groupId>org.jboss.arquillian.protocol</groupId>
			<artifactId>arquillian-protocol-servlet-jakarta</artifactId>
			<version>${version.org.jboss.arquillian.protocol}</version>
		</dependency>

Die Versionsnummer entspricht "10.0.0.Final".
Der Quellcode findet sich hier: https://github.com/arquillian/arquillian-jakarta.

		<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-jakarta/10.0.0.Final/. In dieser Datei findet sich unter "org\jboss\arquillian\protocol\servlet5\v_5" eine Datei "web-fragment.xml", die ein Servlet "ArquillianServletRunnerEE9" 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:
EAR-Datei
In dem EAR befindet sich natürlich auch unsere "war"-Datei:
WAR-Datei
Unter "WEB-INF\lib" befindet sich eine Datei "arquillian-jakarta-servlet-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.servlet5.runner.ServletTestRunner" definiert. Wir können also davon ausgehen, dass in unserer Webanwendung unter http://127.0.0.1:8080/StatelessMaven-web/ArquillianServletRunnerEE9 ein bisher unbekannter Gast antwortet. Rufen wir die URL auf, kommt allerdings nur ein Fehler "className must be specified".

Aus den Teilen der eingangs genannten Dependency ";arquillian-protocol-servlet-jakarta-10.0.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 6.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\servlet5\v_6" aus "arquillian-protocol-servlet-jakarta-10.0.0.Final.jar" aktiv.

Beim Blick in die Klasse "org.jboss.arquillian.protocol.servlet5.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/ArquillianServletRunnerEE9?outputMode=serializedObject&className=de.hsrm.cs.jakartaee.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 oder weglassen. Im Quellcode findet man für letztere Variante einen Zweig, über dem "// TODO: implement a html view of the result" steht - schade. Läßt man den Parameter "outputMode" also weg (URL: http://127.0.0.1:8080/StatelessMaven-web/ArquillianServletRunnerEE9?className=de.hsrm.cs.jakartaee.statelessmaven.web.test.GeometricModelBeanIT&methodName=testCuboidVolume), dann erhält man mittlerweile immerhin eine rudimentäre Ausgabe (die allerdings auch nichts hilft):
de.hsrm.cs.jakartaee.statelessmaven.web.test.GeometricModelBeanIT from [Module "deployment.StatelessMaven-ear.ear.StatelessMaven-web.war" from Service Module Loader]



Stand 08.02.2025
Historie:
08.02.2025: erstellt aus JavaEE8-Beispiel und angepasst an WildFly 35, Kapitel für Debugging und Ausführung der Unit-Tests überarbeitet.