Le premier article Tester son application JavaEE avec Arquillian montrait comment effectuer des tests sur des composants EJB.
Lors de l’écriture des tests unitaires, il est nécessaire de tester les composants touchant la couche persistance JPA dans un contexte transactionnel. Nous allons donc voir comment prendre en compte la persistance et les transactions lors des tests avec Arquillian.
Quelques points à savoir avant de commencer :
L’entité doit être annotée
L’
Pour les tests, je peux très bien intégrer ce datasource dans le
Cette façon de faire est donc idéale pour les micro-déploiements des tests unitaires.
Ce datasource est défini comme suit :
On remarque qu’il faut explicitement enlister l’
La méthode
Une fois le test effectué, la méthode annotée
Les sources complètes de l’application de tests sont disponibles ici. Je vous invite à y jeter un œil pour mieux comprendre ce qu’elles font. Les tests sont ici présentés en utilisant Wildfly !!!! Attention, pour Wildfly, il faut modifier la dépendance, et pour l’instant, Wildfly n’est pas en Release et donc le container plugin n’est pas pas encore disponible en version Release non plus. La seule différence pour nous est de prendre en compte le container dans la version adéquate, c’est à dire :
Lors de l’écriture des tests unitaires, il est nécessaire de tester les composants touchant la couche persistance JPA dans un contexte transactionnel. Nous allons donc voir comment prendre en compte la persistance et les transactions lors des tests avec Arquillian.
Quelques points à savoir avant de commencer :
- Lors du passage des tests unitaires, il est important d’isoler les données utilisés pour les tests de ceux utilisées pour l’intégration. Il est donc nécessaire de créer une base dédiée pour les tests. Créer une base "manuellement" pour chaque test est trop long et pas industriel. Il est donc nécessaire de prendre une base embarquée type HSQLDB ou H2. Ici, nous prenons une base de donnée embarquée H2 car elle est déjà intégrée dans le profile par défaut JBoss, il n’y a donc pas de configuration supplémentaire à faire dans les drivers JDBC.
- il est préférable de garder les TU au sein d’une transaction. L’état de la base reste propre entre chaque passage de test et les données modifiées pendant les tests doivent être rollbackées.
- C’est lors des tests de persistance qu’Arquillian prend tout son sens par rapport à Spring Test. En effet, lors de l’écriture de l’article précédent, comme on ne travaillait qu’avec des EJB, on aurait pu utiliser Spring Test, puisqu’il interprete certaines annotations standards (notamment @Inject, @EJB). A partir du moment ou l’on utilise JPA, Datasources, Transactions, Producers et Resources alors Spring Test n’est plus en capacité de répondre à nos besoins : il nous faut un conteneur EE.
- En corollaire du point précédent, les tests en mode "JBoss Embedded" ne sont plus possible avec JPA et les transactions. Il faudra utiliser un déploiement en Container : managed ou remote.
- Le site d’Arquillian propose un article complet disponible à cette adresse : http://arquillian.org/guides/testing_java_persistence/ Cet article est une bonne base et nous allons voir comment aller plus loin en injectant des données et gérer les transactions.
Création de l’Entité JPA
L’entité JPA assure le mapping avec la base de données. Dans le cas de test, l’EntityWeatherInfo porte des infos de temps et est mappée avec une table. Les détails de l’implémentation de la table ne nous importe pas ici, on va laisser l'ORM générer la table.L’entité doit être annotée
@Entity et contenir une clé primaire représentée par @Id.@Entity
public class WeatherInfo implements Serializable {
@Id @GeneratedValue
private long id;
private String townName;
private String townCode;
private String temperature;
private boolean isRainingInAnHour;
...
Création du Dao
Le Dao (Object d’Accès au Donnés) sur l’entitéWeatherInfo est représenté sous la forme d’un EJB Stateless avec l’annotation @Stateless.L’
EntityManager est injecté dans l’EJB avec @PersistenceContext. Pour simplifier l’exemple, les méthodes du Dao effectuent des créations de requêtes depuis le PersistenceContext.
J’ai annoté le Dao en tant que @TransactionAttribute = MANDATORY : Je force le Dao a être marqué pour s'exécuter au sein d'une transaction. Ce flag est une sécurité et assure qu'il est bien appelé au sein d’une transaction déjà ouverte. Pour le respect de l’architecture en couche, c’est primordial. Si le Dao est appelé depuis un service, alors la transaction est portée par le service et est utilisée par le Dao. Si un autre appelant (service IHM par exemple) appelle directement le Dao hors transaction, c’est qu’il ne respecte pas les couches de l’architecture et n’est pas "autorisé" à être appelé (ce qui finira en Exception).@Stateless
@TransactionAttribute(TransactionAttributeType.MANDATORY)
public class WeatherInfoDao {
@PersistenceContext
EntityManager em;
public WeatherInfo getInfoFromTown(String townId) {
WeatherInfo info = em.createQuery("select w from WeatherInfo w where w.townCode='"
+ townId + "'", WeatherInfo.class).getSingleResult();
return info;
}
Configuration JPA
Le fichierpersistence.xml est disponible dans src/test/resources/test-resource.xml. Il est injecté dans l’archive grace à Shrinkwrap et renommé en persistence.xml pour que le mapping JPA soit pris en compte.
(je ne m’étends pas sur les spécificités JPA ici). Ce fichier définit le jta-data-source à utiliser ainsi que les propriétés spécifiques de l'ORM, et pour lequel on demande la création du schéma (create-drop). le dialect doit être org.hibernate.dialect.H2Dialect pour H2.<persistence version="2.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/persistence" xsi:schemalocation="
http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="test">
<jta-data-source>jdbc/arquillian</jta-data-source>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" />
<property name="hibernate.hbm2ddl.auto" value="create-drop" />
<property name="hibernate.show_sql" value="true" />
</properties>
</persistence-unit>
</persistence>
Déploiement du datasource
Même si je fais les tests dans une base de données embarquées, je dois définir un Datasource H2 et le déployer au sein de JBoss.Pour les tests, je peux très bien intégrer ce datasource dans le
WEB-INF du WAR pour qu’il soit déployé en même temps que l’application. (les fichiers nommés en *-ds.xml sont reconnus et déployés en tant que datasource, c’est pour cette raison que le fichier weather-ds.xml en pris en compte en tant que datasource lors du déploiement de l’application).Cette façon de faire est donc idéale pour les micro-déploiements des tests unitaires.
Ce datasource est défini comme suit :
<datasources xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.jboss.org/ironjacamar/schema"
xsi:schemalocation="http://www.jboss.org/ironjacamar/schema
http://docs.jboss.org/ironjacamar/schema/datasources_1_0.xsd">
<datasource enabled="true" jndi-name="jdbc/arquillian" pool-name="ArquillianEmbeddedH2Pool">
<connection-url>jdbc:h2:mem:arquillian;DB_CLOSE_DELAY=-1</connection-url>
<driver>h2</driver>
</datasource>
</datasources>
Ce qui est important à voir ici, c’est le JNDI name qui est le même que celui référencé dans le persistence.xml.
Ecriture de notre classe de tests
Je reprends le même cas de test de l’article précédent, que je vais étoffer pour prendre en compte les tests de Dao. Dans la classe de testWeatherDaoTest, j'injecte notre Dao, puis l’EntityManager ainsi que le UserTransaction.
@RunWith(Arquillian.class)
public class WeatherDaoTest {
@EJB
WeatherInfoDao dao;
@PersistenceContext
EntityManager em;
@Inject
UserTransaction utx;
On remarque qu’il faut explicitement enlister l’
EntityManager dans la transaction JTA. Cette étape est nécessaire car j'utilise les deux ressources indépendamment. Cela peut sembler anormal si on utilise JPA depuis un EJB car dans ce cas, l’enlistement est automatique, mais ici, il faut l’ajouter explicitement.
Arquillian exécute les méthodes @Before et @After dans le conteneur, respectivement avant et après les méthode de tests.La méthode
@Before est invoquée après que les injections (EJB, em et utx) aient eu lieu.
@Before
public void preparePersistenceTest() throws Exception {
clearData();
insertData();
startTransaction();
}
J'ai besoin d’assurer une injection et suppression de données et pour chaque cas de test. Ces injections/suppressions de données sont prises en compte dans les méthodes clearData() et insertData() et appelées depuis la preparePersistenceTest().preparePersistenceTest() est appelé dans le @Before, la transaction n’est donc pas encore ouverte. Il faut que ces méthodes aient la responsabilité d’ouverture/fermeture des transactions pour assurer l’ajout de données. L’ajout de données est réalisé en créant les entités et en les persistant :private void insertData() throws Exception {
utx.begin();
em.joinTransaction();
// on ajoute des objets
WeatherInfo info1 = new WeatherInfo("NTE", "Nantes", "32°C", false);
WeatherInfo info2 = new WeatherInfo("BRT", "Brest", "18°C", true);
em.persist(info1);
em.persist(info2);
utx.commit();
// clear the persistence context (first-level cache)
em.clear();
}
private void clearData() throws Exception {
utx.begin();
em.joinTransaction();
em.createQuery("delete from WeatherInfo").executeUpdate();
utx.commit();
}
Une fois les ajouts de donnés effectués, la méthode preparePersistenceTest() va lancer la transaction en appelant le startTransaction() qui va ouvrir la transaction pour les tests.private void startTransaction() throws Exception {
utx.begin();
em.joinTransaction();
}
Une fois le test effectué, la méthode annotée
@After est appelée et dans notre cas commiter la transaction.@After
public void commitTransaction() throws Exception {
utx.commit();
}
Les sources complètes de l’application de tests sont disponibles ici. Je vous invite à y jeter un œil pour mieux comprendre ce qu’elles font. Les tests sont ici présentés en utilisant Wildfly !!!! Attention, pour Wildfly, il faut modifier la dépendance, et pour l’instant, Wildfly n’est pas en Release et donc le container plugin n’est pas pas encore disponible en version Release non plus. La seule différence pour nous est de prendre en compte le container dans la version adéquate, c’est à dire :
<dependency> <groupId>org.wildfly</groupId> <artifactId>wildfly-arquillian-container-managed</artifactId> <version>8.0.0.Beta1</version> <scope>test</scope> </dependency>
En conclusion
C'est une approche intéressante et fonctionnelle, mais qui reste compliquée :- Beaucoup de code technique et qui nécessite une maitrise du cycle de vie des test unitaires et des transactions (
@Before,@After...). - Des méthodes de chargement de données communes à toutes les méthodes de tests. Peut-être que dans certains cas de tests précis, on souhaiterait ne charger que certains lots de données : avec cette approche, on ne peut pas.
- Une création de données depuis du code Java, explicite et donc très (trop!) verbeux lorsque l’on dispose de beaucoup de jeux de données. De plus, le code Java n'est pas non plus le format le plus adapté pour représenter des jeux de données (difficulté de lecture).
- Pas de tests sur le jeu de données attendu. Comment assurer que les données de sorties sont bien celles que nous attendions pour notre cas de test précis ?
Pour aller plus loin :
- Les sources complètes sont disponibles sur Github.
- L'articles Arquillian et Persistence : http://arquillian.org/guides/testing_java_persistence/
- Déploiement des Datasources dans Jboss 7: https://docs.jboss.org/author/display/AS71/DataSource+configuration
Aucun commentaire:
Enregistrer un commentaire