Tester une application JavaEE avec Arquillian


Comment tester du code destiné à être exécuté dans un conteneur (Servlet, JPA, Spring, EJB, etc.), c'est à dire tirant partie des services offert par ce conteneur (transactions, base de données,etc.) ? A cette question, Spring répond avec un module dédié aux tests. Cette lacune de J2EE a été résolue avec l'introduction dans Java EE 6 de la notion de « conteneur embarqué », sous entendu embarqué dans un test.

Ecosystème Arquillian

L'objectif d'Arquillian est d'aller plus loin dans la simplicité et la concision pour accélérer encore l'écriture de tests JavaEE. Bien que ce soit une projet Red Hat, ce n'est pas juste une extension de JBoss pour les tests unitaires. Arquillian se veut plus généraliste, et supporte d'autres conteneurs comme GlassFish ou Tomcat. C'est ce que nous verrons dans cet article qui montre comment tester avec JUnit un EJB qui utilise JPA avec GlassFish et EclipseLink.

Téléchargement

Arquillian n'est pas, pour l'heure, disponible dans une version finale empaquetée comme on pourrait s'y attendre. Tout les binaires nécessaires se trouvent dans le repository Maven de JBoss, on commence donc par ajouter cela dans notre pom.xml :

    <repositories>
        <repository>
            <id>jboss.org</id>
            <name>JBoss Repository</name>
            <url>https://repository.jboss.org/nexus/content/groups/public/</url>
        </repository>
    </repositories>

Puis on va ajouter des dépendances sur les modules GlassFish et JUnit d'Arquillian. La version (1.0.0.Final-SNAPSHOT) peut faire sourire (Final et SNAPSHOT sont antinomiques) ou inquiéter. L'API a été figé il y a plusieurs mois déjà, on peut donc espérer qu'il n'y aura pas de changements majeurs d'ici la version finale :

    <dependencies>
        <dependency>
            <groupId>org.jboss.arquillian.container</groupId>
            <artifactId>arquillian-glassfish-embedded-3.1</artifactId>
            <version>${arquillian.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.jboss.arquillian.junit</groupId>
            <artifactId>arquillian-junit-container</artifactId>
            <version>${arquillian.version}</version>
            <scope>test</scope>
        </dependency>

Évidemment, il nous faudra aussi GlassFish Embedded, un driver JDBC pour accéder à la base de données et JUnit, rien d'extraordinaire en somme :

        <dependency>
            <groupId>org.glassfish.extras</groupId>
            <artifactId>glassfish-embedded-all</artifactId>
            <version>3.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.derby</groupId>
            <artifactId>derby</artifactId>
            <version>10.8.2.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

Il se trouve que les dépendances Arquillian tirent des dépendances dont les numéros de version ne sont pas cohérents entre eux. Pour palier à cela, et maîtriser les numéros de version des sous modules Arquillian, il faudra ajouter des dépendances en scope import :

    <dependencyManagement>
        <dependencies>
			<dependency>
				<groupId>org.jboss.arquillian</groupId>
				<artifactId>arquillian-bom</artifactId>
				<version>${arquillian.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
			<dependency>
				<groupId>org.jboss.shrinkwrap</groupId>
				<artifactId>shrinkwrap-bom</artifactId>
				<version>1.0.0-cr-3</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
        </dependencies>
    </dependencyManagement>

ShrinkWrap est un autre projet de RedHat qui permet de fabriquer des Jars ou des Wars en quelques lignes. Il est très lié à Arquillian, nous verrons son utilité ultérieurement.

Au final, on se retrouve avec près d'un trentaine de Jars sur le classpath : C'est beaucoup et ridicule à la fois, ca la plupart des Jars ne contiennent qu'une poignée de classes. Bref, le packaging est pour moi le point noir d'Arquillian : C'est compliqué et peu rassurant. Espérons que la version finale corrigera ce défaut.

Cycle de vie du conteneur

Arquillian lie l'exécution des tests unitaires et le cycle de vie du conteneur : démarrage, deploiements, arrêt... Cycle de vie Arquillian

Pour cela, les classes de test seront annotées @RunWith(Arquillian.class) pour qu'Arquillian puisse s'intégrer dans le flux d'exécution JUnit et orchestre l'interaction avec le conteneur :

@RunWith(Arquillian.class)
public class UserDAOTest {

Au tout début des tests, Arquillian va donc démarrer le conteneur (GlassFish Embedded dans notre cas). Dans un fichier arquillian.xml, on spécifie quel conteneur on utiliser et son paramétrage  :

<arquillian>
    <engine>
        <property name="deploymentExportPath">target/arquillian</property>
    </engine>
    <container default="true" qualifier="glassfish">
        <configuration>
            <property name="sunResourcesXml">src/test/resources/glassfish-resources-test.xml</property> 
        </configuration>
    </container>
</arquillian>

Le fichier glassfish-resources-test.xml référencé ci-dessus est une configuration spécifique à Glassfish, on y trouve notamment les services fournis par le conteneur à l'application, comme le pool de connexions à la base de données :

<resources>
    <jdbc-connection-pool name="jdbc/JeeDemoTestPool" res-type="javax.sql.DataSource"
                      datasource-classname="org.apache.derby.jdbc.EmbeddedXADataSource40"
                      pool-resize-quantity="1" max-pool-size="5" steady-pool-size="0"
                      statement-timeout-in-seconds="60" >
        <property name="databaseName" value="JeeDemoTestDB" />
        <property name="user" value="JeeDemoTest" />
        <property name="password" value="JeeDemoTest" />
        <property name="connectionAttributes" value="create=true" />
    </jdbc-connection-pool>
    <jdbc-resource jndi-name="jdbc/JeeDemoTestDS" pool-name="jdbc/JeeDemoTestPool" />
</resources>

Déploiement d'une application

Dans « test unitaire », il y a « unitaire » : Pourquoi déployer l'application dans sont intégralité si l'on souhaite ne tester qu'un seul composant ?

Pour chaque classe de test, Arquillian permet de déployer une archive (Jar/War/Ear) dont le contenu sera spécifique au test. On peut ainsi ne déployer que le strict minimum (une Servlet), ou bien avoir une configuration spécifique pour un test. Sans oublier que déployer peu, c'est déployer vite !

    @Deployment
    public static JavaArchive createTestArchive() {
        return ShrinkWrap.create(JavaArchive.class, "UserDAOTest.jar")
                .addPackage("com.zenika.jeedemo.model")
                .addClass(UserDAO.class)
                .addAsManifestResource("persistence-test.xml","persistence.xml");
    }

C'est donc ici qu'intervient ShrinkWrap : Cet outil permet, via une API « fluent » très efficace, de fabriquer une archive (ici un Jar), d'y mettre des classes (addPackage, addClass) ou des fichiers de ressources. La méthode qui produit cette archive est static est annotée @Deployment sera invoquée par Arquillian pour le déploiement, à la façon des méthodes @BeforeClass de JUnit.

Évidemment, Arquillian permet aussi de déployer une application dans son intégralité à des fins de tests d'intégration ou d'acceptation. Test d'un EJB et de JPA : Pour chaque méthode de test, Arquillian est capable d'injecter les services et composants du conteneur : on récupère ici l'EJB que l'on souhaite tester, on aurait tout aussi bien pu récupérer une DataSource JDBC ou bien l'EntityManager JPA. :

    @EJB
    private UserDAO userDAO;

    @Test
    public void testFindAll() {
        List<User> users=userDAO.findAllUsers();
        assertFalse(users.isEmpty());
    }

Sans Arquillian, notre classe de test n'étant pas gérée par le conteneur, l'injection de dépendances dans la classe de test ne fonctionne pas. On se retrouve alors contraint de passer par JNDI pour récupérer notre objet.

Le UserDAO bénéficie de tous les services proposés par le conteneur (transactions, sécurité, base de données). « Conteneur embarqué » ne signifie pas « Conteneur au rabais », les conditions du test sont on ne peut plus proche de la réalité. La détection des problèmes et leur débogage s'en trouve donc facilitée.

Sur un banal PC portable, le démarrage du conteneur GlassFish prend environ 2s, le déploiement et l'initialisation du fragment d'application prend entre 5 et 10s.

Conclusion

Passé le goût un peu amer de la configuration Maven, Arquillian est un outil vraiment efficace, les tests unitaires n'ont plus rien à envier à Spring Test. Plus qu'une simple librairie, c'est un véritable framework. A l'origine Seam fut conçu pour améliorer l'intégration EJB/JSF, Arquillian applique la même recette dans un domaine technique différent : rapprocher les tests automatisées et les conteneurs Java EE.


Commentaires

1. Le vendredi 2 mars 2012, 17:25 par Pierrot

Y'a-t-il possibilité d'utiliser un serveur LDAP embarqué ?

2. Le dimanche 4 mars 2012, 20:57 par Dridi Boukelmoune

ApacheDS is an extensible and *embeddable directory server* entirely written in Java

https://directory.apache.org/apache...
http://repo1.maven.org/maven2/org/a...

3. Le dimanche 4 mars 2012, 21:12 par Gérald

Si vous savez lancer votre serveur d'application avec un annuaire LDAP embarqué, je ne vois aucune raison qu'Arquillian pose problème. Autrement dit, pour moi ca n'est pas lié à Arquillian.

4. Le vendredi 6 avril 2012, 10:01 par niiico

C'est un bon article avec plein de trucs utiles dedans, mais il ne s'agit pas de tests unitaires mais plutôt de tests d'intégration.

Après, les deux sont utiles. Mais je ne me passerai pas de vrais tests unitaires.

Fil des commentaires de ce billet

Ajouter un commentaire

Le code HTML est affiché comme du texte et les adresses web sont automatiquement transformées.