lundi 29 août 2011

Tests unitaires de DAO avec Spring et Hibernate avec base de données en mémoire

Je profite de ce blog pour partager une configuration qui m'a beaucoup aidé pour mettre en place des tests unitaires de DAO (ou Repository suivant les goûts).

Des TU avec une base de données, c'est pas très unitaire...

Alors oui, utiliser un système externe même léger déroge à la règle du test unitaire. MAIS à mon humble avis, la plus value du code d'un DAO réside dans les requêtes que l'on développe et non dans la vérification du bon fonctionnement du moteur de persistence.

Bref, tester via des mocks que la méthode add appelle bien la méthode persist de l'EntityManager, c'est bien mais pour les requêtes "faites mains", c'est nettement moins efficace... D'où l'idée de tester en "vrai", les requêtes et leur comportement.

L'utilisation d'une base de données en mémoire pour le déroulement des tests est donc pratique pour plusieurs points :
  • facilité des tests unitaires
  • rapidité
  • indépendance des tests par rapport à l'utilisation d'une base de données commune aux développeurs
Il y a évidemment quelques inconvénients comme certaines requêtes utilisant des fonctions propriétaires ou des séquences qui ne sont pas toujours supportés par la base en mémoire mais dans 90% des cas d'usages, il n'y a pas de problèmes.

Gérer automatiquement la mise à niveau de la base de données c'est fastidieux

Oui mais non... Pas pour les tests unitaires.
Si on réfléchit, notre code contient une description de la base de données sur laquelle il est sensé s'exécuter, c'est le mapping ORM du moteur de persistence. Et le mapping, il est souvent à jour.

Dans notre cas, Hibernate dispose de la fonctionnalité de générer la création de la base de données lors de son lancement. Donc pour mettre à jour la base de données, il suffit de modifier le mapping. Cerise sur le gâteau, cela motive les développeurs à ajouter un maximum d'informations dans le mapping (unicité, contraintes, taille des champs, etc).

Configuration

Spring va nous aider beaucoup pour accomplir cette tache en nous simplifiant la définition de la Datasource.

Nous utilisons HSQLDB qui est une base sous forme de fichier qui dispose aussi de la fonctionnalité recherché et qui est assez connu (présente dans OpenOffice, notamment). Mais il en existe d'autres (H2, ...).

Définition de la Datasource dans Spring

Un fichier applicationContext-test.xml dans le répertoire src/test/resources avec ces éléments :
<!-- Datasource -->
<bean id="dataSource"
 class="org.springframework.jdbc.datasource.DriverManagerDataSource">
   <property name="driverClassName" value="org.hsqldb.jdbcDriver" />
   <property name="url" value="jdbc:hsqldb:mem:test" />
   <property name="username" value="sa" />
   <property name="password" value="" />
</bean>

<!-- Entity Manager Factory -->
<bean id="entityManagerFactory"
 class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
  <property name="persistenceUnitName" value="releasemanager" />
  <property name="dataSource" ref="dataSource" />
  <property name="jpaVendorAdapter">
    <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
      <property name="showSql" value="true" />
      <property name="generateDdl" value="true" />
    </bean>
  </property>
</bean>

<!-- bean post-processor for JPA annotations -->
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />

<!-- Exception translation bean post processor -->
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />

Cette configuration permet de définir les éléments suivants :
  • une datasource JDBC vers une BDD en mémoire
  • une EntityManagerFactory avec Hibernate pour injecter les EntityManagers
  • une gestion transactionnelle avec les @Transactional

Et les tests dans tout ça ?

Les tests de DAO sont simples et sont les mêmes que pour un accès vers une vraie base de données.

Dans l'exemple suivant, on teste une méthode login qui permet de récupérer un utilisateur à partir de son login/password :

//On configure JUnit pour utiliser directement Spring
//On pointe vers le fichier de configuration Spring à la racine du classpath
//On définit toutes les méthodes comme transactionnelle pour les requêtes d'écriture
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"/applicationContext-test.xml"})
@Transactional
public class UserDaoImplTest {

  @Autowired
  private UserDaoImpl dao; // on injecte automatiquement le DAO

  @Test
  public void testLoginExistingUser() {
       User loggedUser = dao.login("login","password");
       assertNotNull(loggedUser);
  }

  @Test(expected=EmptyResultDataAccessException.class)
  public void testLoginWithWrongCredentials() {
       dao.login("login","wrongPassword");
  }

}

Conclusion


Avant d'exécuter les tests, Hibernate va s'initialiser et générer une base de données correspondant au mapping. Cette configuration permet donc de tester rapidement et facilement les DAO et leurs requêtes dans un contexte quasi-réel avec les avantages suivants :
  • les tests sont répétables et identiques pour tous les développeurs
  • facile à mettre en place (pas de bases à installer sur son poste),
  • facile à faire évoluer (pas de gestion de scripts de modification)
Have fun with tests !

Aucun commentaire:

Enregistrer un commentaire