Nous allons voir ici comment, grâce à Sonar, Maven et JaCoCo nous pouvons obtenir la couverture de code par des tests d’intégration.

Contexte

Dans mon exemple, je me baserais sur les outils suivants : Sonar 2.8, Maven 2.2.1, Java 1.6.

Au sein de l’un de mes projets, je dispose de quelques rares tests unitaires, qui couvre environ 7% de l’application. C’est peu. Très peu. Trop peu même.
Mais nous avons également des tests d’intégration.
En gros, ces tests d’intégration vont exécuter un jeu d’environ 300 fichiers d’entrée, qui seront ensuite analysés par mon application.

Mon but est donc de connaître, en plus de ma couverture de test par les tests JUnit, la couverture du code exécuté par mes tests d’intégration.

La structure de mon projet ressemble en gros à ceci :

mon-projet
  +- pom.xml (ce pom-là n'est qu'un pom d'aggrégation)
  +- parent
  |   +- pom.xml (c'est le vrai parent de tous les autres modules)
  +- module-1
  |   +- pom.xml
  +- module-2
  |   +- pom.xml
  +- ...
  +- module-tests
      +- pom.xml
      +- src/test/java/
          +- foo/bar/IntegrationTestsRunner.java

module-tests est un module ne contenant que quelques classes de tests, en particulier IntegrationTestsRunner.java dont le but est d’aller lire mes fichiers de tests, et les exécuter.

Afin de ne pas lancer ces tests d’intégration à chaque build, nous définissons un profil Maven dans le pom.xml de module-tests, comme suit :

    <profiles>
        <profile>
            <id>run-it</id>
            <activation>
                <property>
                    <name>runIT</name>
                    <value>true</value>
                </property>
            </activation>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-surefire-plugin</artifactId>
                        <configuration>
                            <includes>
                                <include>**/IntegrationTestRunner.java</include>
                            </includes>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>

Ce profil sera activé soit en ajoutant -DrunIT=true soit -Prun-it dans la ligne de commande Maven.

Avec une méthode « traditionnelle », c’est-à-dire l’exécution de tests unitaires via Surefire, aucune couverture de code ne sera détectée, car le code exécuté par les tests d’intégration l’est sur les autres modules du projet. Or Cobertura ne va instrumentaliser que les classes du module courant et non les dépendances (i.e. module-1, module-2, etc.).

Mise en place de JaCoCo

Pour résoudre mon problème, je vais faire appel à JaCoCo, un outil de couverture de test plus performant que Cobertura. Son principal avantage est de pouvoir instrumentaliser les classes au runtime. Dans mon cas présent, JaCoCo va donc pouvoir vérifier la couverture du code sur les différents modules au moment où l’on exécute module-tests.

Avant toute chose, il va nous falloir configurer un peu notre environnement :

  • Installer le plugin JaCoCo pour Sonar (à copier dans le répertoire extensions/plugins de son installation Sonar).
  • Une fois Sonar redémarré, penser à ajouter dans le dashboard le widget IT Coverage Widget (il faudra sans doute pour cela être connecté en tant qu’administrateur).
  • Télécharger l’agent JaCoCo. Pour cela, il faut récupérer le ZIP de JaCoCo ici, l’agent se trouvant alors dans lib/jacocoagent.jar.

Maintenant que ceci est en place, nous allons devoir indiquer à Surefire d’utiliser un agent Java spécifique (l’agent JaCoCo).
Etant donné que nous n’avons besoin de cet agent qu’au moment de l’exécution des tests d’intégration, nous le définirons que pour le profil run-it.
Au sein du pom.xml parent, j’ai donc ajouté :

    <profiles>
        <!-- Profile used to run Integration Tests. In such case, we add the Java agent for JaCoCo... -->
        <profile>
            <id>run-it</id>
            <activation>
                <property>
                    <name>runIT</name>
                    <value>true</value>
                </property>
            </activation>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-surefire-plugin</artifactId>
                        <configuration>
                            <argLine>-javaagent:${jacoco.agent.path}=destfile=${jacoco.file.path}</argLine>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>

J’ai défini mon profil deux fois, dans le pom.xml parent et dans celui de module-tests. Nous aurions pu tout mettre dans le parent.

Execution

Nous allons avoir besoin d’exécuter maintenant deux commandes Maven :

  • mvn clean install -DrunIT=true -Djacoco.agent.path=C:\dev\jacocoagent.jar -Djacoco.file.path=C:\dev\monprojet\jacoco.exec
  • mvn -Dsonar.jacoco.itReportPath=C:\dev\monprojet\jacoco.exec sonar:sonar

La première ligne va activer le profil run-it, ce qui aura pour conséquence de lancer le tests d’intégration.
On y définit les deux paramètres (jacoco.agent.path et jacoco.file.path) qui seront passés à Surefire pour définir un agent Java.

La seconde ligne va simplement exécuter l’analyse Sonar sur mon projet, mais en définissant le paramètre sonar.jacoco.itReportPath (généré lors de la première commande), cette analyse comprendra également la couverture des tests d’intégration.

Une fois les deux commandes exécutées, il suffit de se rendre sur son serveur Sonar pour apprécier cette nouvelle mesure de couverture de code !

Comme le montre les captures d’écran sur le post du blog de Sonar à ce sujet, il est possible, à l’instar de la couverture de code, de visualiser la couverture de code de tests d’intégration directement dans la fenêtre de visualisation du code source d’une classe.

Start Slide Show with PicLens Lite PicLens