23 jan 2011

Séparer tests unitaires et tests d’intégrations

Catégorie : Non classéJohnny Beuve @ 21 h 05 min

Des tests automatiques soignés, c’est très bien et je ne peux que vous encourager à continuer.
Des tests unitaires et des tests d’intégrations séparés c’est encore mieux. Mais comment faire?

1. Profils Maven

On peut jouer avec les profils maven. L’idée est d’exclure les tests d’intégrations en temps normal et de créer un profil maven juste pour les tests d’intégrations. L’exclusion peut se faire avec les noms des packages ou un suffixe de nom de classe. Ici un exemple de config avec le plugin de surefire et JUnit :

Build normal, on exclut les tests unitaires (mvn install) :


<build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.4.3</version>
                <configuration>
                    <skip>true</skip>
                </configuration>
                <executions>
                    <execution>
                        <id>surefire-test</id>
                        <phase>test</phase>
                        <goals>
                            <goal>test</goal>
                        </goals>
                        <configuration>
                            <skip>false</skip>
                            <excludes>
                                <exclude>**/integrationtest/**</exclude>
                            </excludes>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

Build avec les tests d’intégrations (mvn install -Pintegrationtest)


<profiles>
        <profile>
            <id>integrationtest</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-surefire-plugin</artifactId>
                        <version>2.4.3</version>
                        <configuration>
                            <skip>true</skip>
                        </configuration>
                        <executions>
                            <execution>
                                <id>surefire-itest</id>
                                <phase>integration-test</phase>
                                <goals>
                                    <goal>test</goal>
                                </goals>
                                <configuration>
                                    <skip>false</skip>
                                    <includes>
                                        <include>**/integrationtest/**</include>
                                    </includes>
                                </configuration>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile>

Une alternative consiste à faire une « test suite » et à la mettre dans la liste d’inclusion. Ainsi on maîtrise exactement qu’elles classes sont exécutées lors des tests.


         <configuration>
          <includes>
            <include>**/*TestSuite.java</include>
          </includes>
          <systemProperties>
            <property>
              <name>monapplication.db.target</name>
              <value>${monapplication.db.target}</value>
            </property>
          </systemProperties>
        </configuration>

2. TestNG

Avec TestNG il suffit d’annoter la class @Test(groups = « integration ») et de mettre les groupes dans la config Surefire ou FailSafe de maven. L’avantage c’est qu’il est relativement aisé de classer ses Tests. Ici un exemple avec TestNg et le plugin surefire :

Code java :


package org.jo.testit;

import org.junit.Assert;
import org.testng.annotations.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Created by IntelliJ IDEA.
 */
@Test
public class CalculatorNgTest {
    static Logger logger = LoggerFactory.getLogger(CalculatorNgTest.class);

    //@Test(groups = "unittest,integrationtest", suiteName = "suite1" )
    @Test(groups = "unittest,integrationtest")
    public void testAdd() {
        logger.debug("RUNNING Test NG UNIT test!");
        Assert.assertEquals("30", 30, Calculator.add(10, 20));
        // TODO Pourquoi ce test n'est pas lancé quand on fait mvn install -Pintegrationtest
    }
}

Au niveau du plugin surefire :


                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-surefire-plugin</artifactId>
                        <version>2.4.3</version>
                        <configuration>
                            <groups>integrationtest</groups>
                        </configuration>
                    </plugin>      

3. Plugin FailSafe

Le plugin FailSafe permet de simplifier la config. Surtout il fait le post-integrationtest même si les tests d’intégrations plantent, ce que ne fait pas Surefire. L’idée est donc d’utiliser Surefire pour les tests unitaires et FailSafe pour les tests d’intégrations en attachant l’exécution à la phase integration-test par exemple. Attention, par convention le plugin FailSafe exécute les tests src/test/java/<above packages>/*IT.java. Si par hasard vos tests ne respectaient pas cette règle de nommage il faut les inclure explicitement. Ici un exemple de config :


          <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-failsafe-plugin</artifactId>
                <version>2.7.1</version>
                <configuration>
                    <groups>integrationtest</groups>
                    <includes>
                        <include>**/*Test.java</include>
                    </includes>
                </configuration>
                <executions>
                    <execution>
                        <id>integration-test</id>
                        <phase>integration-test</phase>
                        <goals>
                            <goal>integration-test</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>verify</id>
                        <phase>verify</phase>
                        <goals>
                            <goal>verify</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>     

4. @IfProfileValue de spring

Si vous faites du Spring l’annotation  @IfProfileValue(name= »nom-de-la-variable », value= »valeur ») permet de lancer le test que si la variable d’environnement est valide. Pour qu’ils soient exécutés, il suffit de les lancer avec -Dnom-de-la-variable=valeur. Si on utilise maven il faudra spécifier une variable système. Exemple ici :

Code java :


package org.jo.testit;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.test.annotation.IfProfileValue;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 */
@ContextConfiguration(locations = {"/META-INF/spring/config.xml"})
@RunWith(SpringJUnit4ClassRunner.class)
public class CalculatorTest {
    static Logger logger = LoggerFactory.getLogger(CalculatorTest.class);

    @Test
    @IfProfileValue(name = "group-unittest", value = "true")
    public void testAdd() {
        logger.debug("RUNNING UNIT test!");

        Assert.assertEquals("30", 30, Calculator.add(10, 20));
    }
}      

Config maven :


                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-surefire-plugin</artifactId>
                        <version>2.4.3</version>
                        <configuration>
                            <skip>true</skip>
                        </configuration>
                        <executions>
                            <execution>
                                <id>surefire-itest</id>
                                <phase>integration-test</phase>
                                <goals>
                                    <goal>test</goal>
                                </goals>
                                <configuration>
                                    <skip>false</skip>
                                    <systemProperties>
                                        <property>
                                            <name>group-unittest</name>
                                            <value>true</value>
                                        </property>
                                        <property>
                                            <name>group-integrationtest</name>
                                            <value>true</value>
                                        </property>
                                    </systemProperties>
                                </configuration>      

Il existe sûrement d’autres manières de procéder. A vous de choisir selon les besoins et le contexte du projet. Vous trouvez pour chacun des points abordés ici un exemple dans un projet maven dans ce Zip.

Nous n’avons pas parler des tests fonctionnels, peut-être dans un autre article ;-)

Mots-clefs : , ,