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