L’intégration des tests unitaires SeamTest dans un projet Maven est assez compliquée. Voici un exemple concret :
Présentation du projet
Avant de rentrer dans des détails techniques, je préfère vous parler de l’application qui va me servir de base pour intégrer mon test SeamTest.
Vision fonctionnelle
J’ai choisi de me baser sur l’exemple “hello world” du livre Seam Framework: Experience the Evolution of Java EE 2nd edition. Vous pouvez retrouver les sources des exemples du livre à l’adresse suivante : http://solutionsfit.com/blog/books/. Malheureusement, l’auteur utilise Ant et non Maven pour gérer la compilation de ses projets.
J’ai rajouté l’utilisation d’un service EJB Stateless pour démontrer la possibilité d’utiliser SeamTest dans un contexte technique hétérogène. Voici le diagramme des cas d’utilisation de l’application :
, [Utilisateur]-(Visualiser les personnes enregistrées))
Vision maven
Le projet est composé d’un module père ayant trois modules fils :
seamtest-parent
|– seamtest-ear
|– seamtest-ejb
`– seamtest-webapp
Vous pouvez checkouter les sources à l’adresse suivante : https://subversion.assembla.com/svn/everythingiswrong/seamtest-parent/.
Dans le reste de l’article, je vais me focaliser sur le module seamtest-ejb car c’est lui qui contient le composant Seam, la persistance et l’EJB Stateless à tester.
Composant Seam à tester
Nous allons tester le composant qui va :
- Enregistrer une nouvelle personne dans la base de données
- Rechercher toutes les personnes de la base de données
@Stateful
@Name("manager")
public class ManagerAction extends Manager {
@In
private Person person;
@Out
private List<Person> fans;
@EJB
private CalculService calculService;
@PersistenceContext
private EntityManager em;
//Méthode à tester.
public void sayHello () {
em.persist (person);
fans = em.createQuery("select p from Person p").getResultList();
}
// Méthode pour vérifier l'injection de l'EJB Stateless dans le composant Seam
public int additionner(int a, int b) {
return (int) calculService.addition(a, b);
}
@Remove
@Destroy
public void destroy() {
}
}
Installation et configuration
Pour information, SeamTest utilise le serveur JBoss embedded.
Installation de JBoss Embedded
Cette installation se résume à la copie du répertoire booststrap qui est dans la distribution de Seam (par exemple ici) dans le répertoire src/test.
Le fichier “pom.xml”
Ce fichier doit contenir l’ensemble des librairies qui vont permettre de démarrer et d’exécuter le serveur JBoss embedded. Il doit aussi prendre en compte le répertoire de bootstrap ajouté précédemment dans les ressources des tests.
<dependencies>
...
<!-- dépendances des tests unitaites : http://community.jboss.org/thread/18299 -->
<!-- dependencies related to JBoss Embedded for Seam 2.X.X -->
<dependency>
<groupId>org.jboss.seam.embedded</groupId>
<artifactId>jboss-embedded-all</artifactId>
<version>beta3.SP12</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.jboss.microcontainer</groupId>
<artifactId>jboss-deployers-client-spi</artifactId>
</exclusion>
<exclusion>
<groupId>org.jboss.microcontainer</groupId>
<artifactId>jboss-deployers-core-spi</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jboss.seam.embedded</groupId>
<artifactId>hibernate-all</artifactId>
<version>beta3.SP12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.seam.embedded</groupId>
<artifactId>thirdparty-all</artifactId>
<version>beta3.SP12</version>
<scope>test</scope>
</dependency>
<!-- dependencies related to JBoss MicroContainer -->
<!-- java.lang.NoClassDefFoundError: javax/faces/application/Application -->
<dependency>
<groupId>javax.faces</groupId>
<artifactId>jsf-api</artifactId>
<version>1.2_09</version>
<scope>test</scope>
</dependency>
<!-- java.lang.NoClassDefFoundError: org/jboss/el/ExpressionFactoryImpl -->
<dependency>
<groupId>org.jboss.el</groupId>
<artifactId>jboss-el</artifactId>
<version>1.0_02.CR4</version> <!-- http://seamframework.org/Community/IllegalAccessErrorAfterUpgradingTo212CR1 -->
<scope>test</scope>
</dependency>
<!-- dependencies related to TestNG -->
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>5.8</version>
<classifier>jdk15</classifier>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<testResources>
<testResource>
<directory>src/test/resources</directory>
<filtering>true</filtering>
</testResource>
<testResource>
<directory>src/test/bootstrap</directory>
<filtering>false</filtering>
</testResource>
<testResource>
<directory>src/test/bootstrap</directory>
<filtering>true</filtering>
<includes>
<include>**/*.xml</include>
</includes>
</testResource>
</testResources>
...
</build>
Remarque : à l’heure actuelle la version la plus récente du conteneur est la beta3.SP12.
Fichiers de tests
Dans la pratique, on ajoute toujours un fichier seam.properties vide dans le classpath des jar contenant des composants Seam. Dans le cas du test, ce fichier doit contenenir au minimum l’information sur le nom JNDI des EJB :
org.jboss.seam.core.init.jndiPattern = #{ejbName}/local
La classe de test doit étendre SeamTest. Elle peut ensuite utiliser l’EL pour référencer les composants Seam :
public class ManagerActionTest extends SeamTest {
private static Logger LOGGER = Logger.getLogger(ManagerActionTest.class);
@Test
public void testSayHello() {
try {
new ComponentTest() {
@Override
protected void testComponents() throws Exception {
setValue("#{person.name}", "Yan");
invokeMethod("#{manager.sayHello}");
List<Person> fans = (List<Person>) getValue("#{fans}");
assertNotNull(fans);
assertEquals(1, fans.size());
assertEquals("Yan", fans.get(0).getName());
// Test le service EJB
assertEquals(3, invokeMethod("#{manager.additionner(1, 2)}"));
}
}.run();
} catch (Exception e) {
LOGGER.error("TEST KO : " + e.getMessage());
fail("TEST KO : " + e.getMessage());
}
}
}
En utilisant Hibernate, vous pouvez exécuter des scripts SQL au déploiement de votre test en ajoutant un fichier import.sql dans le classpath de test (src/test/resources). Cela dit, dans ce cas, je conseillerai plutôt d’utiliser l’intégration avec DBUnit.
Exécution
Pour exécuter les tests il faut lancer la commande mvn clean test.
Variantes
Intégration avec DBUnit
L’intégration est possible entre seamTest et DBUnit. La classe de test doit étendre DBunitSeamTest et implémenter la méthode prepareDBUnitOperations(). Il faut aussi donner à TestNg la nom JNDI de la source de données vers la base de données ainsi que le type de la base de données (HSQL ou MYSQL). Pour cela, il faut configurer le plugin surefire comme cela :
<build>
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.4.3</version>
<configuration>
<systemProperties>
<property>
<name>datasourceJndiName</name>
<value>java:/DefaultDS</value>
</property>
<property>
<name>database</name>
<value>HSQL</value>
</property>
</systemProperties>
<argLine>-Dsun.lang.ClassLoader.allowArraySyntax=true</argLine>
</configuration>
...
</build>
Remarque : deux propriétés pour déclarer le type de la source de données et son nom JNDI ainsi qu’un argument pour permettre au serveur JBoss Embedded de s’éxécuter avec une JDK 1.6 (par défaut ce serveur n’est compatible qu’avec une JDK1.5).
Pour information, DBUnit propose deux implémentations différentes pour décrire les données d’une base : le FlatXmlDataSet et le XmlDataSet (http://dbunit.sourceforge.net/components.html). SeamTest ne support que le FlatXmlDataSet.
Dépendances sur des projets ou modules de type EJB
Dans le cas où votre module dépende d’un autre module EJB, il faut arriver à déployer la dépendance de type EJB dans le conteneur JBoss embedded. Il est possible de faire cette opération avec le plugin maven maven-dependency-plugin comme cela :
<build>
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy</id>
<phase>process-test-resources</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>...</groupId>
<artifactId>...</artifactId>
<version>...</version>
<type>...</type>
<classifier>...</classifier>
<overWrite>false</overWrite>
<outputDirectory>${project.build.directory}/test-classes/deploy</outputDirectory>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
...
</build>
Remarque : la copie de la dépendance se déroule avant la phase d’exécution des tests.