Ejecución de tests de integración en aplicaciones OSGI con el soporte de Arquilian.
0. Índice de contenidos.
- 1. Introducción.
- 2. Entorno.
- 3. Nuestro primer servicio: diferenciando entre tests unitarios y de integración.
- 4. Creando nuestro primer servicio OSGI.
- 5. Test de integración con el soporte de Arquilian.
- 6. Referencias.
- 7. Conclusiones.
1. Introducción
Hace poco ya vimos
cómo ejecutar tests de integración con el soporte de Arquilian en aplicaciones Java bajo el entorno de CDI, el estandar de inyección de dependencias de JEE,
y Weld, su implementación de referencia. En este tutorial vamos a hacer uso también de Arquilian pero para ejecutar tests
de integración en aplicaciones java bajo un entorno OSGI.
OSGI (Open Services Gateway Initiative) define un sistema modular para las aplicaciones Java
estableciendo un estandar para la creación de servicios, empaquetados en módulos (bundles) y una
forma de interactuar estos servicios entre sí en tiempo de ejecución.
Esos bundles de OSGI no son más que las clásicas librerías o empaquetaciones, jars, que hacen uso de un fichero
que describe las características que va a tener dentro del contenedor de OSGI: versión del bundle,
servicios que expone y consume,… ese fichero es el archifamoso MANIFEST.MF, que
hasta ahora no habías usado para nada 😉
Algunas de las características básicas de un bundle dentro de un contenedor OSGI son las siguientes:
- tiene su propio classloader independiente del resto de bundles, con lo que las librerías de una empaquetación no afectan a otras,
- la instalación, arranque, desinstalación de un bundle se realiza dinámicamente en tiempo de ejecución,
- se pueden facetar para que tengan características web y desplegar una aplicación web dinámica.
El contenedor OSGI por excelencia es Apache Equinox, la base del IDE Eclipse, pero existen otras alternativas
open source y los servidores de aplicaciones dan soporte para despliegue de las mismas como bundles
de OSGI, así:
- Apache Felix
- Weblogic de Oracle
- Websphere Application Server de IBM o
- WildFly AKA Jboss AS
Y hasta aquí vamos a llegar en relación a OSGI, ya entraremos en mayor detalle más adelante, porque en este primer tutorial
solo queremos exponer cómo realizar tests de integración de servicios OSGI con el soporte de un contenedor embebido,
estableciendo una clara distinción entre que es un test unitario y en qué se diferencia de un test de integración.
2. Entorno.
El tutorial está escrito usando el siguiente entorno:
- Hardware: Portátil MacBook Pro 15′ (2.4 GHz Intel Core i7, 8GB DDR3 SDRAM).
- Sistema Operativo: Mac OS X Lion 10.7.4
- Java(TM) SE Runtime Environment (build 1.7.0_11-b21)
3. Nuestro primer servicio: diferenciando entre tests unitarios y de integración.
Vamos a comenzar con el test, que es lo que toca:
package com.autentia.osgi.tutorial; import static org.junit.Assert.assertEquals; import org.junit.Test; public class CalculadoraTest { @Test public void dadoDosEnterosCuandoSeSumaDevuelveLaAdiccionDeLosMismos() { final Calculadora calculadora = new Calculadora(); assertEquals(5, calculadora.suma(3, 2)); } }
Bueno, ya estamos en ROJO y sí, nuestro servicio va a ser tan simple.
Y ahora vamos a crear nuestro servicio que cumpla con los requisitos del test:
package com.autentia.osgi.tutorial; class Calculadora { public int suma(int x, int y) { return x + y ; } }
Si ejecutamos el test pasaríamos a VERDE, lo refactorizaremos
más adelante cuando elevemos esta calculadora al nivel de un servicio OSGI.
¿Demasiado simple?, pues esto es un test unitario. Si nuestra calculadora dependiera de otro servicio
deberíamos mockearlo, si no, no es un test unitario.
4. Creando nuestro primer servicio OSGI.
Ya tenemos un servicio simple y queremos exponerlo como un servicio OSGI dentro del contenedor
en el que se ejecutan el resto de bundles para que puedan hacer uso del mismo.
Lo primero que vamos a hacer que crear una interfaz para nuestra calculadora puesto que va a ser la interfaz del servicio la que vamos a exponer en el bundle,
de hecho ya habíamos reducido la visibilidad del servicio a nivel de paquete.
Esta es nuestra interfaz de servicio:
package com.autentia.osgi.tutorial; public interface CalculadoraService { int suma (int x, int y); }
Y modificamos el servicio marcándolo para que implemente la interfaz:
package com.autentia.osgi.tutorial; class Calculadora implements CalculadoraService{ public int suma(int x, int y) { return x + y ; } }
El último paso es exponer el servicio en el contenedor y para ello debemos registrarlo en el contexto de bundles,
añadiéndolo a la clase Activator, esta clase es la que se marca en el MANIFEST.MF como Bundle-Activator:
Bundle-Activator: com.autentia.osgi.tutorial.Activator
El contenido de la clase sería el siguiente:
package com.autentia.osgi.tutorial; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceRegistration; public class Activator implements BundleActivator { ServiceRegistration<?> serviceRegistration; public void start(BundleContext context) { serviceRegistration = context.registerService(CalculadoraService.class.getName(), new Calculadora(), null); } public void stop(BundleContext context) { serviceRegistration.unregister(); } }
Ya lo tenemos, ¿y ahora cómo lo probamos?, ¿levantamos el servidor de aplicaciones para probarlo?;
en nuestro caso el servicio es muy atómico, pero si dependiese de otro servicio o tuviera más
dependencias del contenedor osgi, ¿como aseguramos su funcionalidad?.
La respuesta es un test de integración que permita recuperar la interfaz del servicio del contenedor sin estar acoplado con la
implementación concreta.
5. Test de integración con el soporte de Arquilian.
Lo primero, como siempre, es añadir las dependencias de las librerías necesarias para ejecutar el test de integración
en el ámbito de test que, usando maven, bastará con incluir las siguientes en nuestro pom.xml.
<dependency> <groupId>org.jboss.arquillian.junit</groupId> <artifactId>arquillian-junit-container</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.jboss.arquillian.container</groupId> <artifactId>arquillian-osgi-embedded</artifactId> <version>2.0.0.CR4</version> <scope>test</scope> </dependency> <dependency> <groupId>org.jboss.osgi.framework</groupId> <artifactId>jbosgi-framework-core</artifactId> <version>3.0.0.CR1</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> <scope>test</scope> </dependency>
Si recordamos del tutorial de Arquilian
la implementación concreta del contenedor en la que se ejecutarán las pruebas se descubre automáticamente del classloader,
no es necesario configurar nada más, si bien, usando como hacemos el contenedor de OSGI de Jboss, necesitamos incluir en el path del
entorno de test.
- un fichero arquilian.xml con el siguiente contenido:
<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> <property name="frameworkProperties">src/test/resources/jbosgi-framework.properties</property> </configuration> </container> </arquillian>
- un fichero jbosgi-framework.properties en el path indicado en el fichero anterior con el siguiente contenido:
# Properties to configure the Framework org.osgi.framework.storage=./target/osgi-store org.osgi.framework.storage.clean=onFirstInit # Extra System Packages org.osgi.framework.system.packages.extra=org.jboss.logging;version\=3.0,org.slf4j;version\=1.6
Ya lo tenemos todo y ahora, vamos con el código del test:
package com.autentia.osgi.tutorial; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import java.io.InputStream; import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.junit.Arquillian; import org.jboss.arquillian.test.api.ArquillianResource; import org.jboss.osgi.metadata.OSGiManifestBuilder; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.asset.Asset; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.Test; import org.junit.runner.RunWith; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; @RunWith(Arquillian.class) public class CalculadoraIntegrationTest { @Deployment public static JavaArchive createdeployment() { final JavaArchive archive = ShrinkWrap.create(JavaArchive.class, "test.jar"); archive.addClasses(Calculadora.class, CalculadoraService.class, Activator.class); archive.setManifest(new Asset() { public InputStream openStream() { OSGiManifestBuilder builder = OSGiManifestBuilder.newInstance(); builder.addBundleSymbolicName(archive.getName()); builder.addBundleManifestVersion(2); builder.addBundleVersion("1.0.0"); builder.addBundleActivator(Activator.class.getName()); return builder.openStream(); } }); return archive; } @Test public void dadoDosEnterosCuandoSeSumaDevuelveLaAdiccionDeLosMismos(@ArquillianResource Bundle bundle) throws Exception { assertNotNull("Bundle injected", bundle); bundle.start(); assertEquals("Bundle ACTIVE", Bundle.ACTIVE, bundle.getState()); BundleContext context = bundle.getBundleContext(); final ServiceReference sref = context.getServiceReference(CalculadoraService.class.getName()); final CalculadoraService calculadora = (CalculadoraService) context.getService(sref); assertNotNull("Service not null", calculadora); assertEquals(5, calculadora.suma(3, 2)); } }
A destacar:
- el método marcado con la anotación @Deployment crea la empquetación del bundle «al vuelo»
con las clases que queremos probar, y del mismo modo se crea y se da contenido al MANIFEST.MF, - el método anotado con @Test recibe como parámetro la instancia del bundle desplegado,
lo arrancamos invocando al método start y podemos recuperar los servicios que contiene para testarlos.
Termina probando lo mismo que comprobábamos con el test unitario, pero obteniendo el servicio
del contexto de OSGI del contenedor embebido, con lo que estamos probando tambien la lógica de la clase Activator y,
lo más importante, dejamos abierta la vía para comprobar el resto de servicios que sí tienen dependencias
con otros servicios expuestos por el mismo u otros bundles.
6. Referencias.
- https://adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=CDIWeldArquilian
- http://www.javahispano.org/storage/contenidos/OSGI_Roberto_Montero.pdf
7. Conclusiones.
No creais que nos faltaría nada más, puesto que como con Arquilian seleccionamos las clases que
formarán parte del bundle, podemos mockear la lógica de clases o servicios que dependan de infraestructuras
de entorno.
Espero que os sea de utilidad.
Un saludo.
Jose