Couverture des tests d’intégration avec JaCoCo, Maven et Sonar

Sur certains de mes projets maven j’aimerai pouvoir séparer les tests unitaires des tests d’intégration. Les tests d’intégration sont souvent moins stables, pas toujours reproductibles et ils prennent souvent trop de temps pour être exécutés par les développeurs à chaque compilation.

Déplacer les tests dans un module dédié

Première chose à faire, créer un module dédié dans le projet maven pour y mettre uniquement les tests d’intégration. Voilà la structure globale du projet :

Ensuite pour empêcher que le module libfoo-it ne se lance à chaque compilation on crée un profil maven dédié aux test d’intégration dans le fichier pom.xml principal.

<profiles>
  <profile>
    <id>run-its</id>
    <modules>
      <module>libfoo-it</module>
    </modules>
  </profile>
</profiles>

Utiliser maven-failsafe-plugin

Le plugin failsafe permet de lancer les test d’intégration lors d’un build maven tout comme le plugin surefire le fait avec les tests unitaires. Le plugin failsafe s’attache aux phases integration-test et verify du cycle de vie du build.

Comme il n’est pas possible de configurer plusieurs répertoires contenant les tests dans maven, la discrimination entre tests unitaires et d’intégration se fait sur le nom des classes. Il ne faudra donc pas oublier de nommer correctement ses classes de test.

Les templates par défaut sont les suivants :

  • tests unitaires : **/*Test.java, **/Test*.java, **/*TestCase.java
  • tests d’intégration : **/*IT.java, **/IT*.java, **/*ITCase.java

On ajoute ensuite la configuration du plugin failsafe dans notre profil.

<profiles>
  <profile>
    <id>run-its</id>
    <modules>
      <module>libfoo-it</module>
    </modules>
    <build>
      <pluginManagement>
        <plugins>
          <plugin>
            <artifactId>maven-failsafe-plugin</artifactId>
            <version>2.11</version>
            <executions>
              <execution>
                <goals>
                  <goal>integration-test</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </pluginManagement>
    </build>
  </profile>
</profiles>

Le POM du sous-projet libfoo-it est également à mettre à jour. De cette façon, petite optimisation, le plugin failsafe ne sera lancé que dans le module qui contient les tests d’intégration.

<build>
  <plugins>
    <plugin>
      <artifactId>maven-failsafe-plugin</artifactId>
    </plugin>
  </plugins>
</build>

On peut désormais lancer un build incluant les tests d’intégration avec la commande suivante :

[/~]$ mvn -Prun-its clean verify

Couverture de code avec JaCoCo

Avoir des tests c’est bien, calculer la couverture de ces tests c’est mieux. Pour cela nous allons utiliser le moteur de couverture de code JaCoCo (Java Code Coverage), plus adapté aux tests d’intégration que le bien connu Cobertura.

JaCoCo est fournit sous la forme d’un agent à lancer avec la JVM. Heureusement, il existe un plugin maven pour JaCoCo qui va nous aider à l’intégrer dans le build. Le plugin maven JaCoCo dispose d’un goal prepare-agent qui va dans un premier temps télécharger l’agent et dans un second temps créer une variable maven contenant la configuration de l’agent qu’il suffira de passer en paramètre au plugin failsafe.

Voilà ce que donne la configuration du plugin (Nota: j’utilise la version 5.3 du plugin JaCoCo car les versions suivantes ne semblent pas fonctionner avec maven 2.2.1) :

<profiles>
  <profile>
    <id>run-its</id>
    <modules>
      <module>libfoo-it</module>
    </modules>
    <build>
      <pluginManagement>
        <plugins>
          <plugin>
            <groupId>org.jacoco</groupId>
            <artifactId>maven-jacoco-plugin</artifactId>
            <version>0.5.3.201107060350</version>
            <executions>
              <execution>
                <phase>pre-integration-test</phase>
                <goals>
                  <goal>prepare-agent</goal>
                </goals>
              </execution>
            </executions>
            <configuration>
              <propertyName>it.failsafe.argLine</propertyName>
              <destFile>${it.jacoco.destFile}</destFile>
            </configuration>            
          </plugin>
          <plugin>
            <artifactId>maven-failsafe-plugin</artifactId>
            <version>2.11</version>
            <executions>
              <execution>
                <goals>
                  <goal>integration-test</goal>
                </goals>
              </execution>
            </executions>
            <configuration>
              <argLine>${it.failsafe.argLine}</argLine>
            </configuration>            
          </plugin>
        </plugins>
      </pluginManagement>
    </build>
    <properties>
      <it.jacoco.destFile>${java.io.tmpdir}/jacoco-foo.dump</it.jacoco.destFile>
    </properties>
  </profile>
</profiles>

Le plugin JaCoCo va générer la configuration de l’agent pour lancer les tests et la placer dans variable it.failsafe.argLine. Ensuite on configure le paramètre argLine du plugin failsafe avec cette variable. On définit également le fichier qui va collecter les données de couverture (avec paramètre destFile).

Il faut également mettre à jour le POM du module contenant les tests :

<build>
  <plugins>
    <plugin>
      <groupId>org.jacoco</groupId>
      <artifactId>maven-jacoco-plugin</artifactId>
    </plugin>
    <plugin>
      <artifactId>maven-failsafe-plugin</artifactId>
    </plugin>
  </plugins>
</build>

Couverture avec Sonar

Maintenant que nous avons nos données de couverture dans un fichier, il serait intéressant de les faire digérés à Sonar lors de son analyse. Depuis la version 2.12, Sonar est livré avec le plugin JaCoCo ; pour les précédentes il faudra l’installer depuis l’update center.

Le plugin JaCoCo de Sonar attend à trouver le chemin vers le fichier de collecte des données dans la variable sonar.jacoco.itReportPath. Il est possible de configurer ce chemin dans l’interface web de Sonar (menu settings, catégorie JaCoCo, paramètre File with execution data for integration tests) mais cette variable peut également être configurée dans le POM.

Voilà donc la version définitive du profil :

<profiles>
  <profile>
    <id>run-its</id>
    <modules>
      <module>libfoo-it</module>
    </modules>
    <build>
      <pluginManagement>
        <plugins>
          <plugin>
            <groupId>org.jacoco</groupId>
            <artifactId>maven-jacoco-plugin</artifactId>
            <version>0.5.3.201107060350</version>
            <executions>
              <execution>
                <phase>pre-integration-test</phase>
                <goals>
                  <goal>prepare-agent</goal>
                </goals>
              </execution>
            </executions>
            <configuration>
              <propertyName>it.failsafe.argLine</propertyName>
              <destFile>${it.jacoco.destFile}</destFile>
            </configuration>            
          </plugin>
          <plugin>
            <artifactId>maven-failsafe-plugin</artifactId>
            <version>2.11</version>
            <executions>
              <execution>
                <goals>
                  <goal>integration-test</goal>
                </goals>
              </execution>
            </executions>
            <configuration>
              <argLine>${it.failsafe.argLine}</argLine>
            </configuration>            
          </plugin>
        </plugins>
      </pluginManagement>
    </build>
    <properties>
      <it.jacoco.destFile>${java.io.tmpdir}/jacoco-foo.dump</it.jacoco.destFile>
      <sonar.jacoco.itReportPath>${it.jacoco.destFile}</sonar.jacoco.itReportPath>
    </properties>
  </profile>
</profiles>

Il ne reste plus qu’à lancer l’analyse avec Sonar :

[/~]$ mvn -Prun-its clean verify sonar:sonar

Finalement, après avoir ajouté le widget Integration test coverage dans le dashboard, on obtient ce résultat :

Couverture de test avec Sonar

Pistes d’améliorations

Cette solution est un peu brute de décoffrage et pour dire vrai entre le début de l’écriture de ce post et maintenant j’ai eu quelques idée pour l’améliorer :

  • Passer la configuration du profil dans un super POM dont héritent tous les projets. En effet, mis à part le paramètre destFile il n’y a rien de spécifique dans cette configuration. On pourrait même imaginer la génération d’un nom de fichier aléatoire à chaque lancement.
  • Utiliser plusieurs modules de tests d’intégration. Pour cela il faut mettre le paramètre supplémentaire append à true dans le plugin maven JaCoCo afin que les données d’exécution de tous les modules soient collectées dans le même fichier.

Et si vous avez d’autres idées, n’hésitez pas à les poster dans les commentaires.

This entry was posted in Tests, build, qualité, etc and tagged , , , , , , , . Bookmark the permalink.

2 Responses to Couverture des tests d’intégration avec JaCoCo, Maven et Sonar

  1. Sébastien says:

    Excellent post très complet, et pleins de bonnes idées !
    Mais question : A part pour mutualiser la configuration du pom dans le cas de plusieurs modules de tests, y a t-il a une raison particulière pour mettre toute la conf des plugins dans le pom parent et non dans le pom du module de test ?

  2. @Sébastien
    Je vois une deuxième raison pour remonter toute la config dans le POM parent : la cacher :) Je pense que les développeurs n’ont pas tous nécessairement ni envie ni besoin de voir toute la mécanique (ou la magie) de maven et pour beaucoup moins ils ont de xml à taper, mieux ils se portent.

    Pour te donner un exemple, dans ma boîte j’ai un super POM dont héritent tous les projets ; ce POM contient beaucoup de config pour les plugins, des profils pour les tests, pour la release… J’ai mis également ajouté le profil run-its dans ce POM. Quand un dev veux ajouter les tests d’intégrations il suffit de deux petits bouts de code

    <profiles>
      <profile>
        <id>run-its</id>
        <modules>
          <module>libfoo-it</module>
        </modules>
      </profile>
    </profiles>
    

    et

    <build>
      <plugins>
        <plugin>
          <groupId>org.jacoco</groupId>
          <artifactId>maven-jacoco-plugin</artifactId>
        </plugin>
        <plugin>
          <artifactId>maven-failsafe-plugin</artifactId>
        </plugin>
      </plugins>
    </build>
    

    Simple à documenter pour les équipes de dev et si, par malheur, ça ne fonctionne pas, c’est beaucoup plus simple pour moi à debugger.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

*


trois − 3 =

Vous pouvez utiliser ces balises et attributs HTML : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">