A.2. Automatiser vos tests avec Ant

Mettre en place des tests automatisés avec Ant est aussi relativement facile, bien que cela requiert un peu plus de plomberie qu’avec Maven. En particulier, Ant ne fournit pas directement les librairies JUnit et les tâches Ant adaptées, donc il faut les installer soi-même quelque part. L’approche la plus portable est d’utiliser un outil de Gestion de dépendances tel qu’Ivy, ou de placer les fichiers JAR correspondants dans un répertoire à l’intérieur de la structure de votre projet.

Pour lancer les tests avec Ant, vous appelez la tâche <junit>. Une configuration typique adaptée à Jenkins est présentée dans cet exemple :

<property name="build.dir" value="target" />
<property name="java.classes" value="${build.dir}/classes" />
<property name="test.classes" value="${build.dir}/test-classes" />
<property name="test.reports" value="${build.dir}/test-reports" />
<property name="lib" value="${build.dir}/lib" />

<path id="test.classpath">1
  <pathelement location="/jenkins-guide-complet/hudsonbook-content-fr/tools/junit/*.jar" />
  <pathelement location="${java.classes}" />
  <pathelement location="${lib}" />
</path>

<target name="test" depends="test-compile">
  <junit haltonfailure="no" failureproperty="failed">2
    <classpath>3
      <path refid="test.classpath" />
      <pathelement location="${test.classes}" />
    </classpath>
    <formatter type="xml" />4
    <batchtest fork="yes" forkmode="perBatch"5 todir="${test.reports}">
      <fileset dir="${test.src}">6
        <include name="**/*Test*.java" />
      </fileset>
    </batchtest>
  </junit>
  <fail message="TEST FAILURE" if="failed" />7
</target>
1

Nous devons mettre en place un classpath contenant les fichiers JAR junit et junit-ant, sans oublier les classes de l’application et toute autre dépendance de l’application pour la compilation et le lancement.

2

Les tests en eux-mêmes sont lancés ici. L’option haltonfailure est utilisée pour faire échouer le build immédiatement dès qu’un test échoue. Dans un environnement d’Intégration Continue, ce n’est pas exactement ce qu’on veux, puisque nous devons également avoir les résultats des tests suivants. Nous avons donc positionné cette valeur à no et utilisé l’option failureproperty pour forcer l’échec du build dès que tous les tests sont terminés.

3

Le classpath doit contenir les librairies JUnit, les classes de votre application et leurs dépendances, et vos classes de test compilées.

4

La tâche Junit de Ant peut produire des rapports aux formats texte et XML, mais pour Jenkins, nous avons seulement besoin de ceux en XML.

5

L’option fork lance vos test dans une JVM séparée. C’est généralement une bonne idée, puisque cela peut éviter les problèmes de type classloader liés à des conflits avec les librairies propres à Ant. Néanmoins, le comportement par défaut de la tâche Junit de Ant est de créer une nouvelle JVM pour chaque test, ce qui ralentit significativement les tests. L’option perBatch est plus intéressante, puisqu’elle crée une seule nouvelle JVM pour chaque batch de tests.

6

Vous définissez les tests que vous voulez lancer au sein d’un élément fileset. Cela permet une grande souplesse, et rend simple la définition d’autres objectifs pour différents sous-ensembles de tests (intégration, web, et autres).

7

Force l’échec du build après la fin des tests, si l’un d’entre eux a échoué.

Si vous préférez TestNG, Ant est évidemment bien supporté également. En utilisant TestNG pour l’exemple précédent, vous pourriez faire quelque chose comme ceci :

<property name="build.dir" value="target" />
<property name="java.classes" value="${build.dir}/classes" />
<property name="test.classes" value="${build.dir}/test-classes" />
<property name="test.reports" value="${build.dir}/test-reports" />
<property name="lib" value="${build.dir}/lib" />

<path id="test.classpath">
  <pathelement location="${java.classes}" />
  <pathelement location="${lib}" />
</path>

<taskdef resource="testngtasks" classpath="lib/testng.jar"/>

<target name="test" depends="test-compile">
  <testng classpathref="test.classpath"
          outputDir="${testng.report.dir}"
          haltonfailure="no" 
          failureproperty="failed">
    <classfileset dir="${test.classes}">
      <include name="**/*Test*.class" />
    </classfileset>
  </testng>
  <fail message="TEST FAILURE" if="failed" />
</target>

TestNG est une librairie de test très flexible, et la tâche TestNG a beaucoup plus d’options que ça. Par exemple, pour lancer seulement les tests définis comme faisant partie du groupe “integration-test” que nous avons vu précédemment, nous pourrions faire ça :

<target name="integration-test" depends="test-compile">
  <testng classpathref="test.classpath"
          groups="integration-test"
          outputDir="${testng.report.dir}"
          haltonfailure="no" 
          failureproperty="failed">
    <classfileset dir="${test.classes}">
      <include name="**/*Test*.class" />
    </classfileset>
  </testng>
  <fail message="TEST FAILURE" if="failed" />
</target>

Ou pour lancer les tests en parallèle, en utilisant quatre threads concurrents, vous pourriez faire ça :

<target name="integration-test" depends="test-compile">
  <testng classpathref="test.classpath"
          parallel="true"
          threadCount=4
          outputDir="${testng.report.dir}"
          haltonfailure="no" 
          failureproperty="failed">
    <classfileset dir="${test.classes}">
      <include name="**/*Test*.class" />
    </classfileset>
  </testng>
  <fail message="TEST FAILURE" if="failed" />
</target>