vendredi 17 août 2012

Tester son code LDAP avec OpenDJ

Ceci est la reprise de l'article que j'ai écrit pour Silverpeas. Par rapport au commentaire de Fanf concernant les différentes possibilités de tester du code LDAP avec Unboundid pour un mode complètement en-mémoire, bien que je trouve cette possibilité élégante et sûrement plus performante il se trouve que nous utilisons OpenDJ en production et que je préfére utiliser un code de serveur de production (même en mode mémoire) pour mes tests plutôt qu'un bouchon, aussi performant soit-il.

A l'heure du TDD et du test unitaire il convient de pouvoir tester son code facilement et rapidement.
Si pour les bases de données relationnelles cela est bien documenté, ce n'est pas le cas des annuaires LDAP qui viennent d'un temps où les exigences en termes de tests étaient moindre.
Heureusement les serveurs LDAP Java libres et modernes commencent à prendre ces paramètres en compte.
Si ApacheDS propose un runner JUnit, j'ai eu quelques difficultés à le mettre en œuvre.
J'ai donc décidé d'utiliser OpenDJ qui peut être facilement embarqué et qui peut utiliser un stockage mémoire ce qui permet d'avoir des performances acceptables pour une exécution dans un mode test.
Cependant même si OpenDJ utilise un backend mémoire, sa configuration reste gérée par quelques fichiers. C'est pourquoi vous devrez embarquer quelques fichiers dans vos tests.

Avec JUnit nous avons deux possibilités pour gérer des ressources externes :

  • utiliser un runner particulier qui va gérer les références au serveur embarqué (c'est le choix fait par ApacheDS).
  • utiliser des règles (rules) pour démarrer et arrêter le serveur embarqué. C'est ce choix que nous avons fait en utilisant une ClassRule : c'est à dire une règle qui s'applique sur une variable static de la classe de test. Cette fonctionnalité est disponible depuis JUnit 4.10

Afin de faciliter la configuration du serveur LDAP pour le test nous allons définir une annotation nous servant à passer un certain nombre de paramètres, CreateLdapServer :

  • un fichier LDIF pour insérer des données dans l’annuaire
  • le chemin (en relatif ou absolu) vers le répertoire contenant la configuration minimale d'OpenDJ
  • l'identifiant du backend mémoire où seront mises les données de test
  • le DN de base associé à ce backend.

Maintenant il ne reste plus qu'à écrire notre règle qui doit implémenter l'interface org.junit.rules.TestRule.
Nous avons donc une méthode à réaliser :

@Override
public Statement apply(Statement stmnt, Description d) {
  CreateLdapServer annotation = d.getAnnotation(CreateLdapServer.class);
  if (annotation != null) {
    return statement(stmnt, annotation.serverHome(), annotation.ldifConfig(), annotation.
        ldifFile(), annotation.backendID(), annotation.baseDN());
  }
  return stmnt;
}

La première ligne recherche la présence de notre annotation CreateLdapServer et si celle si est présente alors nous allons encapsuler le test pour démarrer le serveur au chargement de la classe et l'arrêter à la fin des tests. C'est là le rôle de la méthode :

private Statement statement(final Statement stmnt, final String serverHome,
  final String ldifConfigFile, final String ldifFile, final String backendId,
  final String dn) {
  return new Statement() {
    @Override
    public void evaluate() throws Throwable {
      before();
      try {
        stmnt.evaluate();
      } finally {
        after();
      }
    }
    private void after() {
      stopLdapServer();
    }
    private void before() {
      try {
        startLdapServer(serverHome, ldifConfigFile);
        loadLdif(ldifFile, backendId, DN.decode(dn));
      } catch (Exception ex) {
        ex.printStackTrace();
        throw new RuntimeException("Could'nt start LDAP Server", ex);
      }
    }
  };
}

On voit qu'on crée un org.junit.Statement qui encapsule le test originel avec deux méthodes : before() appelée avant l'exécution du test, et after() une fois le test exécuté, quelque soit son résultat. Ces méthodes sont très simples :

  • before() va démarrer le serveur LDAP et y charger le contenu du fichier LDIF,
  • after() va se contenter d'arrêter le dit-serveur.


Les données étant chargées dans un backend mémoire nous n'avons pas besoin de les nettoyer avant d'arrêter le serveur. Pour l'implémentation du démarrage du serveur et son arrêt je vous laisse consulter le code.
Comment utiliser tout cela dans un test :
Tout d'abord vous devez utiliser l'annotation que nous venons de créer :

@CreateLdapServer(ldifConfig="opendj/config/config.ldif", serverHome="opendj", ldifFile="silverpeas-ldap.ldif")
public class LDAPDriverTest {
...

Maintenant, il nous faut déclarer notre règle :

@ClassRule
public static OpenDJRule ldapRule = new OpenDJRule();

Nous voilà donc presque prêts à tester. Il ne nous manque plus que les ressources nécessaires. Dans le cadre d'un projet Apache Maven il faut ajouter les dépendances vers les bibliothèques d'OpenDJ nécessaires :

    ...
    <dependency>
      <groupId>org.forgerock.opendj</groupId>
      <artifactId>opendj-server</artifactId>
     <version>2.4.5</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>com.sleepycat</groupId>
      <artifactId>je</artifactId>
      <version>1.4.7</version>
      <scope>test</scope>
    </dependency>
    ...


Les ressources nécessaires sont au nombre de deux :
  • le fichier ldif (silverpeas-ldap.ldif) que nous avons déclaré et qui va se retrouver dans src/test/resources
  • le répertoire opendj qui contient la configuration minimale et va lui aussi dans src/test/resources
Attention à ne pas filtrer ce répertoire opendj et tout ce qu'il contient avec Apache Maven.

Le code source et un exemple d'utilisation est disponible sur Github : https://github.com/ehsavoie/embedded-ldap

Merci à Ludovic Poitou pour son aide sur la configuration et l'utilisation OpenDJ.