Comment séparer ses tests d’intégration ?

Une question récurrente pour les équipes qui commencent à industrialiser leur build avec du Maven et qui utilisent de manière intensive JUnit. Au bout d’un moment, les tests d’intégrations ralentissent de manière conséquente le build et parfois découragent les développeurs à cause de leurs pré-requis plus importants que les tests unitaires.
Comment les séparer des tests unitaires et comment éviter qu’ils soient lancés à chaque build Maven?

Test unitaire ou test d’intégration

TestTypologie

La démarcation entre les tests unitaires et les tests d’intégrations est souvent floue et longuement débattue sur un projet.

Ce qui caractérise avant tout les tests unitaires, c’est leur périmètre réduit et surtout leur rapidité d’exécution (cf. les 10 commandements des tests unitaires). De même, les tests unitaires ne doivent pas avoir de pré-requis complexes et ne pas dépendre d’un système extérieur pouvant être indisponible (une base de donnée, un service fourni par une autre application, un broker JMS, etc.).

Il est très important de soigner ses tests unitaires pour que ceux-ci restent une véritable aide au développement. Si l’un d’eux échoue pour une raison extérieure au test et au code testé, ou bien s’ils deviennent de plus en plus lents, vous allez voir fleurir des  » @Ignore  » et les développeurs vont prendre l’habitude de builder en rajoutant l’option « -Dmaven.test.skip ». Enfin avec le temps, les tests vont devenir une contrainte plus qu’une aide et vont progressivement disparaître.

Cependant, nous ne voulons pas pour autant faire une croix sur les tests d’intégration. Ceux-ci sont très utiles pour vérifier le lien entre les différentes parties, ou faire des tests plus élaborés. De même, ils restent indispensables sur certaines couches, notamment les couches DAO ou WebServices où l’utilité de tests unitaires reste à voir. En effet quel est l’intérêt de tester le bon fonctionnement d’un mock ?

La question à laquelle nous allons tacher de répondre est donc : comment séparer de manière propre ces différents types de tests ?

Comment faire avec Maven

Séparer les tests d’intégration dans un module à part

Il existe plusieurs façons de procéder avec Maven, la plus simple étant de placer les tests d’intégration dans un module séparé du reste du projet. Bien que facile à mettre en place, cela a pour effet de décorreler les sources des tests et de multiplier les modules.

Dans le même ordre d’idées, on peut envisager de créer un seul module qui contiendrait tous les tests d’intégration. Cependant, cette approche n’est envisageable que si le projet contient un nombre très réduit de modules. Le code de test doit être traité avec autant d’égard que le reste du code. Tout comme on essaye de bien segmenter le code en parties logiques, on essaiera également de ne pas transformer ses tests en grand plat de spaghettis.

Utilisation des profiles maven

L’approche recommandée est la séparation via un profil dédié et la différenciation des tests d’intégration par pattern. Le but est de définir une convention pour cataloguer ses tests d’intégration :

  • soit sur le nom de la classe (ex : *ITest.java)
  • soit sur le nom du package (ex : **/integration/**)

Exemple de séparation sur le nom de package :

Tout d’abord on enlève toutes les classes contenues dans un package  » integration  » des tests.


	
        	
			org.apache.maven.plugins
			maven-surefire-plugin
			2.4.2
			
				true
			
			
				
					surefire-test
					test
					
						test
					
					
						false
						
							**/integration/**
						
					
				
			
        	
	

Puis, on redéfinit la phase  » integration-test  » en lui intégrant les classes contenues dans les package  » integration « .


	integration-testing
	
		
			
				org.apache.maven.plugins
				maven-surefire-plugin
				2.4.2
				
					true
				
				
					
						surefire-itest
						integration-test
						
							test
						
						
							false
							
								**/integration/**
							
						
					
				
			
		
	

Cette approche a l’avantage de garder dans un même module les tests d’intégration et le code, tout en les regroupant sous un même package. Cependant, le seul point faible de cette approche réside dans le fait que le test d’intégration ne se retrouve plus dans le même package que la classe testée. Ainsi, on ne pourra plus accéder aux variables et méthodes protected.

Futur?

L’approche idéale dans Maven serait de séparer les tests d’intégrations de la même manière que les tests sont séparés du code. On pourrait ainsi avoir :

+- main
  +-- java
  +-- resources
+- test
  +-- java
  +-- resources
+- it
  +-- java
  +-- resources

Cette possibilité est très attendue par les développeurs mais impossible avec Maven2.
Maven3 le permettra-t-il ? Pour l’instant aucune information n’a filtré à ce sujet…

Comment faire avec le runner JUnit de Spring

Spring propose une surcouche à JUnit en l’enrichissant de nouvelles fonctionnalités. Celle qui nous intéressera particulièrement est l’utilisation du runner JUnit : SpringJUnit4ClassRunner en plus de l’annotation @IfProfileValue . Celle-ci permet de catégoriser les tests. Ceux étant annotés ainsi ne seront exécutés que si une variable d’environnement équivalente au contenu de l’annotation est présente dans le classpath.

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

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"/test-configuration.xml"})
public class IntegrationTest {

	@IfProfileValue(name="test-group", value="integration")
	@Test
	public void test(){
		...
	}

}

De base, nos tests annotés ne sont pas exécutés. Pour qu’ils soient exécutés, il suffit de les lancer en rajoutant comme argument : -Dtest-group=integration.

Si vous utilisez Eclipse et que vous souhaitez activer vos tests d’intégrations par défaut, rien de plus simple :

En conclusion, cette solution offre l’avantage d’être assez souple et de pouvoir regrouper dans une même classe différents types de tests.

Comment faire avec TestNG

TestNG est un framework de tests offrant de nombreuses fonctionnalités supplémentaires par rapport à JUnit. Avec TestNG il est possible de spécifier le ou les groupes de tests concernés directement dans l’annotation @Test.

@Test(groups = "integrationTest")
public class Test {

  public foo() {}

  public bar() {}
}

Pour exclure des groupes de tests en utilisant TestNG, il suffit de lui rajouter l’option -excludegroups= »xxx,yyy ».
Si vous utilisez Maven il faut configurer Surefire de la sorte :


    org.apache.maven.plugins
    maven-surefire-plugin
    2.4.2
    
      integrationTest
    
  

Cette solution est surement la plus complète et la plus souple avec les notions de groupe de groupes, groupes partiels, etc. (cf. documentation)

Et pour JUnit?

Attendue depuis longtemps par la communauté, JUnit propose dans sa version 4.8 une fonctionnalité expérimentale, les @Category (Voir la présentation de David Saff) :

public interface SlowTests {}

public class A {
   @Category(SlowTests.class)
   @Test
   public void testA(){
   }
}

@RunWith(Categories.class)
@IncludeCategory(SlowTests.class)
@SuiteClasses( { A.class})
public class SlowTestSuite {
}

Cette solution n’est cependant pas encore très mature. En effet, il est nécessaire de créer une interface pour chaque type de test et il faut impérativement définir manuellement la suite de tests en y ajoutant une à une les classes de tests à inclure (même s’il existe quelques astuces). De plus, le lien avec le plugin Surefire de Maven n’existe pas encore.
Cette solution est donc une alternative à surveiller mais pas encore à utiliser.

Conclusion

Comme nous venons de le voir, il existe de nombreux moyens pour séparer nos différents types de tests. Attention cependant à ne pas utiliser cette technique pour masquer les test gênants. Si vous avez des soucis avec certains tests, mieux vaut s’attaquer à la source même du problème plutôt qu’à ses conséquences. De plus, cette séparation doit être une aide aux développeurs uniquement, il est primordial de demander au serveur d’intégration continue de lancer l’intégralité des tests. Ou bien de le paramétrer avec deux builds : un à la volée très rapide et un exécuté une fois par jour qui lancera l’intégralité des tests.

13 commentaires

  • Je pensais que les tests d’IHM ou de déploiement pouvaient être aussi appelé « d’intégration ». Dans tous les cas, ils peuvent être aussi automatisés et testé au sein d’un build maven ;)

    Dans le cas de test fonctionnels avec déploiement, nous préférons avoir un projet maven à part, contenant la configuration pour déployer l’application sur un environnement de test, et exécuter des test de simulation « client » (pour des IHM ou des web services).

    Je m’écarte un peu du sujet, mais j’ai pour ma part été un peu déçu par Cargo, et je recommanderais plutôt d’utiliser les plugins spécifiques à chaque servers, qui sont beaucoup plus stable, en particulier pour Geronimo …

  • A noter l’existence de plugin failesafe (chez mojo) qui est un fork de surefire dédie aux tests d’intégration et qui simplifie beaucoup la configuration

  • En effet, failesafe à l’air intéressant (je ne connaissais pas). Cela permet de bien utiliser les phases pre-integration-test et post-integration-test (pour lancer un jetty par exemple comme le suggère Sébastien).

    Les exemples sont ici : http://mojo.codehaus.org/failsafe-maven-plugin/usage.html
    Je ferais éventuellement un complément à l’article une fois testé.
    Merci pour l’info!

  • Lors du développement d’une application web (en Java ou pas) il est conseillé de créer des classes de tests afin de vérifier premièrement que le code écrit correspondant bien aux spécifications fonctionnelles demandées, et deuxièmement que la fonctionnalité (ou un ensemble de fonctionnalités) reste valide lorsque des demandes d’évolution sont intégrées à l’application. Si ça peut intéresser j’ai fait un petit article sur JUnit : Test unitaire hors conteneur J2EE avec Spring et JNDI ici : http://bit.ly/5jbOeC

  • Comment faire dans ce cas pour ne pas executer les tests unitaires ?
    Car « -DskipTests » ou « -Dmaven.test.skip=true » ne fonctionnent plus…

    Merci pour cet excellent article :-)

  • Ça dépend de la solution choisie.
    – avec un profil maven dédié il suffit de ne pas le mentionner.
    – avec la solution Spring il suffit de ne pas mentionner le « test-group »
    – avec testNG : -excludegroups= »xxx,yyy »
    – avec failesafe comme le conseillait Arnaud on peut rajouter ceci :
    <configuration>
    <skipTests>${skipITs}</skipTests>
    </configuration>

    et ainsi ajouter -DskipITs=true

    Cela répond à la question?

    Nathaniel (Xebia)

  • bjr, je ss etudiant informatique 2èmé année master et j’ai comme PFE les tests d’intégration sur un logiciel oriénté objet je doit le faire en java ..mais j’arrive pas a avancer car j’ai pas trouvé une bon documentation (gratuite b1sr)
    donc je sai pa quel outils je doit utiliser quel stratégie (top-down, bottom-up..) si vs pouver m’aider svp au moins par la documentation ..merci d’avance de votre aide

  • Il y a un tutoriel sur l’utilisation de Maven failsafe à l’adresse suivante : Maven Integration Test

  • comment ecrire des cas de test d’intégration en java??

  • Bonjour,

    Avec le plugin de surefire, il est possible de donner le répertoire avec l’option testSourceDirectory.
    Ma solution :

    - avec un profile « integration-test » je configure le plugin surefire grâce à l’attribut testSourceDirectory. J’indique que le répertoire de test est src/it/
    - j’utilise le plugin buildhelper pour rajouter le sous-répertoire java de ce répertoire en source et le sous-répertoire resources en ressource

    Ainsi mes tests d’intégration sont bien présent dans le répertoire src/it

  • Je profite du commentaire récent de Guillaumer pour indiquer un post récent d’Antonio Goncalves sur les tests d’intégration et l’utilisation du plugin Maven « failsafe »: http://antoniogoncalves.org/2012/12/13/lets-turn-integration-tests-with-maven-to-a-first-class-citizen/
    Et bonne année !

  • Bonjour, je viens de débuter dans la grande aventure des test unitaires & testsuite. J’ai exporté mes tests selenium au format webdriver backed. Deux choses me pose problème:
    1) comment garder la session de l’utilisateur sur le site active tout au long de la testsuite.
    2)comment garder le driver actif également (j’entend par la ne pas le terminer a chaque fin de cas de test et le relancer au suivant). Je précise que j’ai pas mal chercher avant de poster et que je n’ai pas trouver mon bonheur.

    D’avance merci pour les pistes que vous pourriez m’apporté.

Laisser un commentaire