JEE: Arquillian

Überblick

Arquillian ist ein Framework für Integration Tests. Es bietet die Möglichkeit für bestimmte Testfälle ein Deployment bereitzustellen, was die nur die relevanten Klassen besitzt. Diese sogenannten Micro-Deployments können mittels Arquillian einfach definiert werden und dann in einem embedded or existierenden Container eingesetzt werden. In diesem werden dann die Testfälle durchgeführt. Zur Vollständigkeit ist noch erwähnt, dass Arquillian auch für client-seitige Tests verwendet werden kann, in solchen Fällen ist ein Deployment nicht notwendig. Als Deployment-Format stehen verschiedene zur Verfügung, z.B. Jar, War, Ear etc. Genauso stehen als Container verschiedene Alternativen zur Verfügung. Arquillian bietet hierfür die gängigsten Adapter an, siehe [2].

Einrichten

Die Einrichtung ist recht simple, und u.a. hier [1] sehr gut erklärt. Nach der Anpassung der pom.xml kann es schon losgehen:

<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>junit</groupId>
				<artifactId>junit</artifactId>
				<version>${version.junit}</version>
				<scope>test</scope>
			</dependency>
			<dependency>
				<groupId>org.jboss.arquillian</groupId>
				<artifactId>arquillian-bom</artifactId>
				<version>${version.arquillian}</version>
				<scope>import</scope>
				<type>pom</type>
			</dependency>
		</dependencies>
	</dependencyManagement>

<dependencies>

		<!-- Testing: JUnit, Arquillian -->
		<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>

Zu beachten ist, dass wegen einem Bug in dem alten Surefire Plugin es zu Problem mit der Nutzung von Arquillian kommen kann. Dabei wird der Container nicht sauber beendet. Dies ist in aktuelleren Surefire Plugins behoben.

<plugin>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.17</version>
</plugin>

Die wichtigsten Punkte für den erfolgreichen Einsatz:

  • Testklasse mit @RunWith(Arquillian.class) annotieren
  • Public static Methode mit @Deployment erstellen, welches das Deployment (org.jboss.shrinkwrap.api.ShrinkWrap)
  • Test-Methoden (mit @Test)

Testfälle

Anbei folgen verschiedene Testfälle, die heute nur Java-Archive beachten. Komplexere Deployments werden in einem nachfolgenden Beitrag beschrieben.

Java-Archive und POJO

Im Projekt gibt es eine einfache Java Util-Klasse OneUtils, welche für die ersten Tests verwendet werden soll.

public class OneUtils {

	public long getCurrentMillis() {
		return System.currentTimeMillis();
	}
}

Die Test-Klasse hierfür sie wie folgt aus:

@RunWith(Arquillian.class)
public class OneUtilsTest {

	@Inject
	private OneUtils oneUtils;
	
	
	@Deployment
	public static JavaArchive createDeployment() {
		final JavaArchive jar = ShrinkWrap.create(JavaArchive.class)
				.addClass(OneUtils.class);

		// Print out the archive
		System.out.println(jar.toString(true));
		return jar;
	}
	
	@Test
	public void testUtilGetCurrentMillis() {
		
		Assert.assertNotNull(oneUtils);
		System.out.println(oneUtils.getCurrentMillis());
	}
}

Das erstellte Deployment (hier Jar) soll nur die OneUtils-Klasse. Im dem Testfall wird diese Klasse auch gleich durch die Injezierung verwendet. Die Test-Methode prüft hier nur, ob das Objekt existiert. Nach Ausführung dieses JUnit-Testfalls wird der Inhalt des Jars ausgegeben und die Test-Methode ausgeführt. Output sieht etwa wie folgt aus:

[code] 3a4df341-1026-42e4-ac33-c9200f17acd8.jar: /com/ /com/haddouti/ /com/haddouti/pg/ /com/haddouti/pg/jee6/ /com/haddouti/pg/jee6/util/ /com/haddouti/pg/jee6/util/OneUtils.class 111 [main] INFO org.jboss.weld.Version - WELD-000900 1.1.5 (Final) 1398533984836 [/code]

Wie man sehen kann, wird der Name der Jar-Datei dynamisch vergeben. Als Container wird der aktuell konfigurierte verwendet und das ist gerade: Weld EE embedded. Natürlich kann hier auch mvn test verwendet werden, dann wird auch das Surefire Plugin verwendet.

Git Tag: JEE-Testapp-01

Java-Archive und CDI

Im folgenden Beispiel werden mehr CDI Klassen verwendet, mit einer Beziehung zueinander. So können wir auch sehen, dass Arquillian die Klassen richtig mit CDI initialisiert.

public class OneStatelessBean {

    /\*\*
     \* Default constructor. 
     \*/
    public OneStatelessBean() {
    }

    @PostConstruct
    public void init() {
    	System.out.println("init(): " + this.getClass().getSimpleName());
    }    
    
    public String process() {
    	// lot of magic
    	return this.getClass().getSimpleName() + "@" + this.hashCode();
    }
}

public class OneService {

	@Inject
	private OneStatelessBean bean;
	
	@PostConstruct
	public void init() {
		System.out.println("init(): " + this.getClass().getSimpleName()); 
	}
	
	public String process() {
		// lot of magic
		return this.getClass().getSimpleName() + "." + bean.process();
	}
}

OneService greift auf OneStatelessBean so dass in der process()-Methode auch die process()-Methode von OneStatelessBean verwendet wird. Die Klassen sind nicht annotiert, da im CDI-Umfeld dies nicht notwendig ist. Die Injezierung geschieht mittels Inject. Die Test-Klasse für ein Jar-Archive sieht wie folgt aus:

@RunWith(Arquillian.class)
public class OneServiceJarTest {

	@Inject
	OneService oneService;
	
	@Deployment
	public static JavaArchive createDeployment() {
		final JavaArchive jar = ShrinkWrap.create(JavaArchive.class)
				.addClass(OneStatelessBean.class)
				.addClass(OneService.class)
				.addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml");
		
		System.out.println(jar.toString(true));
		return jar;
	}
	
	@Test
	public void testOne() {
		Assert.assertNotNull(oneService);
		System.out.println(oneService.process());
	}
}

Die Ausführung liefert u.a. folgendes: [code] d97a6ce3-943b-4377-8e41-94968459a99b.jar: /com/ /com/haddouti/ /com/haddouti/pg/ /com/haddouti/pg/jee6/ /com/haddouti/pg/jee6/service/ /com/haddouti/pg/jee6/service/OneStatelessBean.class /com/haddouti/pg/jee6/OneService.class /META-INF/ /META-INF/beans.xml 125 [main] INFO org.jboss.weld.Version - WELD-000900 1.1.5 (Final) init(): OneStatelessBean init(): OneService OneService.OneStatelessBean@11576309 [/code]

Git Tag: JEE-Testapp-02

Java-Archive und EJB

Im folgenden Testfall wollen wir EJB verwenden, d.h. @Stateless und @EJB statt @Inject. Hierfür reicht jedoch der aktuell verwendete Weld Container nicht aus. Da Arquillian verschiedene andere Container unterstützt, ersetzen wir den aktuellen Container einen richtigen Application Server, jetzt durch Glassfish Embedded. Dafür muss in der pom.xml die Abhängigkeiten zu Weld durch die für Glassfish Embedded ersetzt werden. Zusätzlich muss auch die JEE-API entfernt werden, da dies Glassfish schon mitliefert.

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

Nach der minimalen Anpassung des Testfalls (@Inject durch @EJB) läuft der Testfall sauber durch:

@RunWith(Arquillian.class)
public class OneServiceJarTest {

	@EJB
	OneService oneService;
	
	@Deployment
	public static JavaArchive createDeployment() {
		final JavaArchive jar = ShrinkWrap.create(JavaArchive.class)
				.addClass(OneStatelessBean.class)
				.addClass(OneService.class)
				.addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml");
		
		System.out.println(jar.toString(true));
		return jar;
	}
	
	
	
	@Test
	public void testOne() {
		Assert.assertNotNull(oneService);
		System.out.println(oneService.process());
	}
}

Die Ausgabe ist jetzt umfangreicher, da Glassfish gestartet wird. Auszugweise sieht das etwa wie folgt aus:

26.04.2014 22:09:19 com.sun.enterprise.v3.server.CommonClassLoaderServiceImpl findDerbyClient INFO: Cannot find javadb client jar file, derby jdbc driver will not be available by default. 
26.04.2014 22:09:20 org.hibernate.validator.util.Version INFO: Hibernate Validator 4.2.0.Final …. INFO: Grizzly Framework 1.9.46 started in: 274ms - bound to \[0.0.0.0:8181\] 
26.04.2014 22:09:23 com.sun.enterprise.v3.server.AppServerStartup run INFO: GlassFish Server Open Source Edition 3.1.2 (java\_re-private) startup time : Embedded (2.959ms), startup services(2.361ms), total(5.320ms) 
26.04.2014 22:09:24 org.glassfish.admin.mbeanserver.JMXStartupService$JMXConnectorsStarterThread run INFO: JMX006: JMXStartupService had disabled JMXConnector system 5ac8c374-a454-4378-9a3c-82f17b2e5129.jar: /com/ /com/haddouti/ /com/haddouti/pg/ /com/haddouti/pg/jee6/ /com/haddouti/pg/jee6/service/ /com/haddouti/pg/jee6/service/OneStatelessBean.class /com/haddouti/pg/jee6/OneService.class /META-INF/ /META-INF/beans.xml 
26.04.2014 22:09:29 com.sun.ejb.containers.EjbContainerUtilImpl createThreadPoolExecutor INFO: Created EjbThreadPoolExecutor with thread-core-pool-size 16 thread-max-pool-size 32 thread-keep-alive-seconds 60 thread-queue-capacity 2147483647 allow-core-thread-timeout false … INFO: WEB0172: Virtual server \[server\] loaded default web module \[\] classLoader = WebappClassLoader (delegate=true; repositories=WEB-INF/classes/) SharedSecrets.getJavaNetAccess()=java.net.URLClassLoader$7@13cb2c6 
26.04.2014 22:09:36 com.sun.ejb.containers.BaseContainer initializeHome INFO: EJB5181:Portable JNDI names for EJB OneService: \[java:global/test/OneService!com.haddouti.pg.jee6.OneService, java:global/test/OneService\] 
26.04.2014 22:09:37 com.sun.ejb.containers.BaseContainer initializeHome INFO: EJB5181:Portable JNDI names for EJB OneStatelessBean: \[java:global/test/OneStatelessBean!com.haddouti.pg.jee6.service.OneStatelessBean, java:global/test/OneStatelessBean\] 
26.04.2014 22:09:37 org.jboss.weld.bootstrap.WeldBootstrap INFO: WELD-000900 SNAPSHOT 
26.04.2014 22:09:41 com.sun.enterprise.web.WebApplication start INFO: WEB0671: Loading application \[test\] at \[/test\] 
26.04.2014 22:09:41 org.glassfish.deployment.admin.DeployCommand execute INFO: test was successfully deployed in 14.115 milliseconds. init(): OneService init(): OneStatelessBean OneService.OneStatelessBean@28967553 classLoader = WebappClassLoader (delegate=true; repositories=WEB-INF/classes/) SharedSecrets.getJavaNetAccess()=java.net.URLClassLoader$7@13cb2c6 PlainTextActionReporterSUCCESSNo monitoring data to report. 
26.04.2014 22:09:44 org.glassfish.admin.mbeanserver.JMXStartupService shutdown INFO: JMX001: JMXStartupService and JMXConnectors have been shut down. 
26.04.2014 22:09:44 com.sun.enterprise.v3.server.AppServerStartup stop INFO: Shutdown procedure finished 
26.04.2014 22:09:44 AppServerStartup run INFO: \[Thread\[GlassFish Kernel Main Thread,5,main\]\] exiting

Git Tag: JEE-Testapp-03

Verschiedene Container

Eins der Stärken von Arquillian ist die Unterstützung verschiedener Container. Um verschiedene Container zu unterstützten, empfiehlt es sich, diese mit Maven Profile zu realisieren. Exemplarisch erstellen wir drei Profile für Weld, Glassfish Embedded und WildFly Embedded. Bzgl. WildFly Embedded ist die Bezeichnung etwas irreführend. Da mit WildFly Embedded die Standalone Application Server in derselben JVM gestartet wird, wie die Test-Fälle auch [3]. Weiter muss noch das Systemproperty gesetzt werden „-Djava.util.logging.manager=org.jboss.logmanager.LogManager“. Ob hier noch eine richtige Embedded-Version kommt, ist unklar. Ein Auszug des pom.xml sieht dann wie folgt aus:

	<profiles>
		<profile>
			<id>arquillian-weld-embedded</id>
			<dependencies>

				<!-- JEE Spec -->
				<dependency>
					<groupId>org.jboss.spec</groupId>
					<artifactId>jboss-javaee-6.0</artifactId>
					<version>1.0.0.Final</version>
					<type>pom</type>
					<scope>provided</scope>
				</dependency>

				<!-- Arquillian Container: Weld EE container -->
				<dependency>
					<groupId>org.jboss.arquillian.container</groupId>
					<artifactId>arquillian-weld-ee-embedded-1.1</artifactId>
					<version>1.0.0.CR3</version>
					<scope>test</scope>
				</dependency>
				<dependency>
					<groupId>org.jboss.weld</groupId>
					<artifactId>weld-core</artifactId>
					<version>1.1.5.Final</version>
					<scope>test</scope>
				</dependency>
				<dependency>
					<groupId>org.slf4j</groupId>
					<artifactId>slf4j-simple</artifactId>
					<version>1.6.4</version>
					<scope>test</scope>
				</dependency>
			</dependencies>
		</profile>
		<profile>
			<id>arquillian-glassfish-embedded</id>
			<dependencies>
				<!-- ArquillianContainer: Glassfish embedded -->
				<dependency>
					<groupId>org.jboss.arquillian.container</groupId>
					<artifactId>arquillian-glassfish-embedded-3.1</artifactId>
					<version>1.0.0.CR3</version>
					<scope>test</scope>
				</dependency>
				<dependency>
					<groupId>org.glassfish.main.extras</groupId>
					<artifactId>glassfish-embedded-all</artifactId>
					<version>3.1.2</version>
					<scope>provided</scope>
				</dependency>
			</dependencies>
		</profile>
		<profile>
			<id>arquillian-wildfly-embedded</id>

			<dependencies>
				<!-- JEE Spec -->
				<dependency>
					<groupId>org.jboss.spec</groupId>
					<artifactId>jboss-javaee-6.0</artifactId>
					<version>1.0.0.Final</version>
					<type>pom</type>
					<scope>provided</scope>
				</dependency>

				<!-- Arquillian Container: WildFly 8 Embedded -->
				<dependency>
					<groupId>org.wildfly</groupId>
					<artifactId>wildfly-arquillian-container-embedded</artifactId>
					<version>8.1.0.CR1</version>
					<scope>test</scope>
				</dependency>
				<dependency>
					<groupId>org.wildfly</groupId>
					<artifactId>wildfly-embedded</artifactId>
					<version>8.1.0.CR1</version>
					<scope>test</scope>
				</dependency>
			</dependencies>
		</profile>
	</profiles>

Zu beachten ist, dass WildFly 8 Java Version 7 erwartet. Zusätzlich benötigt es den Parameter jbossHome, mit einem Verweis auf das Verzeichnis wo der Application Server installiert ist. Dies wird in src/test/resources/arquillian.xml hinterlegt:

<?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 http://jboss.org/schema/arquillian/arquillian\_1\_0.xsd">

    <container qualifier="jboss" default="true">
        <configuration>
            <!-- CHANGE to your WildFly installation -->
            <property name="jbossHome">/Fullpath/to/WildFlyHome</property>
            <property name="modulePath">/Fullpath/to/WildFlyHome/modules</property>
        </configuration>
    </container>
</arquillian>

Dadurch dass jetzt Profile hinterlegt sind, und die notwendigsten Abhängigkeiten in den einzelnen Profilen hinterlegt sind, existieren nun Compiler-Fehler. Dies kann behoben werden, wenn in Eclipse/Maven ein Profil als Standard ausgewählt wird. Entweder direkt in dem Profil durch activation > activeByDefault = true oder in dem in Project properties > Maven > Active Maven profiles explizit ein Profil angegeben wird. Für WildFly steht ein RunConfig zur Verfügung, der noch den Jboss LogManager setzt. Wie man sehen kann, lassen sich verschiedene Container leicht mit Arquillian einbinden. WildFly ist noch recht frisch, so dass hier zu hoffen ist, dass zukünftig die Nutzung genauso einfach funktioniert wie bei den anderen Containern.

Git Tag: JEE-Testapp-04 Git: git://git.schokokeks.org/playground.git Project: testapp-jee6

Referenzen

comment

Comments

arrow_back

Previous

DevEnv: Docker und AppServer

Next

CDI in JavaSE
arrow_forward