Documentación oficial: http://arquillian.org/docs/
Arquillian es un framework de testing que nos permite hacer pruebas de integración, el mismo en conjunto con la librería ShrinkWrap se encarga de generar y desplegar un WAR/JAR/EAR como microservicio en nuestro server de aplicaciones (al que nosotros le configuramos que paquetes y archivos queremos agregarle) y luego se le realizan tests con JUnit o TestNG en el ambiente real donde estará desplegado el proyecto.
Este framework me solucionó la problemática de testear un generador de reportes PDF, el cual trabaja con archivos de resources (xml, png, etc) y necesitamos que al estar desplegado en un contenedor Java EE (Wildfly) el mismo pueda encontrar los archivos en las rutas especificadas.
Contenedores según Arquillian
Un contenedor en el contexto de este framework se define como un servidor de aplicaciones (ej: Wildfly, Glassfish, etc), un Contenedor de Servlets (ej: Tomcat, Jetty) o un contenedor de beans independientes (ej: OpenEJB).
Arquillian permite 3 formas de interacción con contenedores:
- Contenedor remoto
- Es un contenedor que está en una ubicación de red externa, en una JVM aparte, y no es administrado por Arquillian.
- Contenedor administrado
- Similar al Contenedor remoto, pero Arquillian administra el ciclo de vida del contenedor (iniciar/apagar).
- Contenedor incrustado
- El contenedor incrustado reside en la misma JVM
En este artículo se va a especificar una implementación de Contenedor Remoto, aprovechando este ambienté de testing docker que desarrollamos en este post https://filenotfound.com.uy/2022/07/nuestro-ambiente-de-testing-con-docker-y-wildfly/.
Puertos de Debugging
Las JVM tienen un puerto utilizado para realizar debugging remoto,por defecto es el 8787.
Ahora bien, si deseamos habilitar el puerto de la jvm para debugging en la jvm con Wildfly debemos agregar esta línea en el fichero wildflyHOME\bin\standalone.conf:
JAVA_OPTS=’$JAVA_OPTS -agentlib:jdwp=transport=dt_socket,address=8787,server=y,suspend=n’
El segundo requisito será mapear los puertos para poder acceder al mismo en la red.
En el caso del Testing Environment, la idea sería mapear los siguientes puertos de debugging:
Nodo docker | Puerto de acceso | Opción a agregar en el docker run |
Dockernode1 | 8787 | -p 8787:8787 |
Dockernode2 | 8788 | -p 8788:8787 |
Dockernode3 | 8789 | -p 8789:8787 |
Dockernode4 | 8790 | -p 8790:8787 |
Para poder utilizar esta característica debemos usar la property:
-Darquillian.debug=true
Cuando especifique como configurar Arquillian en el IDE mostraré más claro dónde va esa property de la JVM.
Configuración de Maven
Las dependencias requeridas por Arquillian son las siguientes:
<!--Integración Arquilian, JUnit, Wildfly-->
<dependency>
<groupId>org.jboss.arquillian</groupId>
<artifactId>arquillian-bom</artifactId>
<version>1.7.0.Alpha12</version>
<scope>provided</scope>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.wildfly.bom</groupId>
<artifactId>wildfly-javaee8</artifactId>
<version>14.0.1.Final</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope> <!-- Para test con Arquillian dentro de src/test/java -->
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>compile</scope> <!-- Para test locales que están dentro del paquete src/main/java/test-->
</dependency>
<dependency>
<groupId>org.jboss.arquillian.junit</groupId>
<artifactId>arquillian-junit-container</artifactId>
<version>1.7.0.Alpha12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.wildfly.arquillian</groupId>
<artifactId>wildfly-arquillian-container-remote</artifactId>
<version>5.0.0.Alpha5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
<dependency>
<groupId>org.jboss.shrinkwrap</groupId>
<artifactId>shrinkwrap-api</artifactId>
<version>1.2.6</version>
</dependency>
<dependency>
<groupId>org.jboss.shrinkwrap</groupId>
<artifactId>shrinkwrap-depchain</artifactId>
<version>1.2.6</version>
<type>pom</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.shrinkwrap.resolver</groupId>
<artifactId>shrinkwrap-resolver-impl-maven</artifactId>
<version>3.1.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.arquillian.core</groupId>
<artifactId>arquillian-core-api</artifactId>
<version>1.7.0.Alpha12</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jboss.arquillian.protocol</groupId>
<artifactId>arquillian-protocol-servlet</artifactId>
<version>1.7.0.Alpha12</version>
<scope>test</scope>
</dependency>
<!--Integración Arquilian, JUnit, Wildfly-->
Cabe destacar que este framework no es compatible con Junit 5.
Estas dependencias incluyen el manejo de contenedores remotos para arquillian, protocolo servlet, cliente http, junit y la librería ShrinkWrap para generación del WAR.
Fichero Arquillian.xml
Arquillian en su documentación especifica el uso de un fichero xml para el seteo de configuraciones del container con el que va a interactuar el framework.
A continuación, especifico el contenido de este:
<arquillian
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://jboss.org/schema/arquillian"
xsi:schemaLocation="http://jboss.org/schema/arquillian
http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
<defaultProtocol type="Servlet 5.0"/>
<container qualifier="wildfly" default="true">
<configuration>
<property name="managementPort">9997</property>
<property name="port">8087</property>
<property name="managementAddress">192.168.0.100</property>
<property name="host">192.168.0.100</property>
<property name="username">pass</property>
<property name="password">pass1234</property>
</configuration>
</container>
</arquillian>
El protocolo de comunicación será Servlet, luego las properties ingresadas son los puertos, dirección ip del contenedor docker, y credenciales de management de Wildfly.
En este caso está configurado para usar con el nodo Dockernode1, si se desea usar cualquiera de los otros 3 nodos solo se debe modificar puertos de debugging, http y management. Observe que 192.168.0.100 es la ip del anfitrión docker, se mantiene para cualquiera de los 4 nodos del Testing Environment.
Este archivo debe estar en la ruta src/test/resources/arquillian.xml del proyecto.
Configuración de Arquillian en IntelliJ
Para ejecutar los tests en este contenedor docker, será necesario configurar la ejecución desde el IDE.
Para esto vamos a “Edit Configurations…”

Luego agregamos una configuración de Arquillian Junit:

Ahora seleccionamos “Wildfly Remote” y luego clic en “Configure”:

Al ingresar en configure lo primero que debemos ingresar es en “VM Options:” la property de JVM para debugging remoto que les nombré anteriormente.
-Darquillian.debug=true

Si vamos a la pestaña “Debug” podemos habilitar el “Remote Debug”, poner host y puerto de debugging de la JVM donde está deployado el EJB.

Una vez finalizado esto ya estamos en condiciones de ejecutar tests Arquillian en el server y hacer su debugging en cualquier proyecto.
Codificando un test Arquillian
Un test de Arquillian difiere un poco del test que usualmente codificamos con Junit, en los siguientes aspectos:
@RunWith(Arquillian.class)
public class EcliTest {
Encima de la clase debemos usar la etiqueta RunWith, indicando que el test se ejecutará con Arquillian.
En vez de realizar las invocaciones a los métodos desde la lógica interior del proyecto debemos hacerlo a través del EJB que inyectamos, usando de esta forma los métodos públicos del api.
Así que en la clase de cada test agregamos:
@Inject
IReportEjb ejb;
También es necesario agregar un método con la annotation @Deployment, el cual se encargará de generar el WAR con ShrinkWrap, para que luego Arquillian lo deploye en el server.
Acá tenemos un snippet de ejemplo de este método:
@Deployment
public static WebArchive createDeployment() {
final WebArchive archive = ShrinkWrap.create(WebArchive.class, "EcliTest.war");
/*Paquetes con codigo java que se agregan al JAR*/
archive.addPackages(true, "uy.com.filenotfound.Reports.api");
archive.addPackages(true,"uy.com.filenotfound.Reports.barcodegenerator");
archive.addPackages(true, "uy.com.filenotfound.Reports.datatypes");
archive.addPackages(true, "uy.com.filenotfound.Reports.ejb");
archive.addPackages(true, "uy.com.filenotfound.Reports.logic");
archive.addPackages(true, "uy.com.filenotfound.Reports.qr");
archive.addPackages(true, "uy.com.filenotfound.Reports.utils");
archive.addClasses(BarCodeQRTest.class, EcliTest.class, EprovTest.class, EResgEcliTest.class, EresgTest.class);
/*Archivos de Resources*/
archive.addAsResource(new File("src/main/resources/log4j.properties"), new BasicPath("src/main/resources/log4j.properties"));
archive.addAsResource(new File("src/main/resources/log4j2.xml"), new BasicPath("src/main/resources/log4j2.xml"));
archive.addAsResource(new File("src/main/resources/apache_fop"), new BasicPath("src/main/resources/apache_fop"));
archive.addAsWebInfResource(EmptyAsset.INSTANCE, "src/test/java/resources/beans.xml");
/*Removemos pdfs generados localmente en los test*/
archive.delete("src/main/resoures/apache_fop/ecli/out");
archive.delete("src/main/resoures/apache_fop/eprov/out");
archive.delete("src/main/resoures/apache_fop/eresg/out");
archive.delete("src/main/resoures/apache_fop/eresg_ecli/out");
/*Agregar dependencias al jar de arquillian*/
for(String dependency : Constants.DEPENDENCIES){
File[] files = Maven.resolver().resolve(dependency).withTransitivity().asFile();
archive.addAsLibraries(files);
}
return archive;
}
En él se agregan clases de test, paquetes de código, archivos de resources y dependencias Maven con sus transitivas.
Convengamos que ese Constants.DEPENDENCIES equivale a una constante String que en algún lugar tenemos, con el siguiente formato
public static final String[] DEPENDENCIES= { /*Listado de dependencias maven para que arquillian se lleve los jar al deployar los test*/
"net.sourceforge.barbecue:barbecue:1.5-beta1",
"org.apache.xmlbeans:xmlbeans:5.1.0",
"org.apache.xmlgraphics:fop:2.7",
"org.apache.xmlgraphics:xmlgraphics-commons:2.7",
"xerces:xercesImpl:2.12.2",
"org.apache.xmlgraphics:fop-core:2.7",
"com.google.zxing:javase:3.5.0",
"org.apache.logging.log4j:log4j-core:2.18.0",
"org.apache.logging.log4j:log4j-api:2.18.0",
"xalan:xalan:2.7.2",
"org.jboss.spec.javax.ejb:jboss-ejb-api_3.2_spec:2.0.0.Final",
"org.jboss.spec.javax.xml.bind:jboss-jaxb-api_2.3_spec:2.0.1.Final",
"org.bouncycastle:bcprov-jdk15on:1.70",
"javax.xml.soap:javax.xml.soap-api:1.4.0",
"org.apache.commons:commons-lang3:3.12.0"
};
El formato de cada dependencia a ingresar es [GROUPID]:[ARTIFACTID]:[VERSION] .
Luego la mecánica de ejecución de test es la misma que Junit, solo hay que agregar la annotation @Test en cada método de pruebas.
Los Asserts también se realizan con Junit.