JUnit : Test unitaire hors conteneur J2EE avec Spring et JNDI

par Mimie le 19 janvier 2010 dans Programmation

Principe

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.

Ces classes de tests nous permettent donc rapidement de savoir que le code à livrer est cohérent et correspondant aux spécifications fonctionnelles.

La bibliothèque JUnit est faite pour réaliser ce genre de tests dans un environnement Java, la version utilisée dans l’exemple est la 4.8.1.

(il est cependant rare que le développeur ait le temps d’écrire une classe de test pour chaque fonctionnalité qu’il code, par manque de temps principalement)

Besoins

Nous devons effectuer des tests unitaires de base de données en utilisant des services métier définis dans des fichiers de configurations Spring. Ces services métier accèdent à la base de données via une variable JNDI (Java Naming and Directory Interface) déclaré à la fois au sein de notre conteneur J2EE (serveur d’applications) et dans notre application de la façon suivante :

<!-- ###### JNDI Lookup ###### -->
<bean id="dbGeeks" class="org.springframework.jndi.JndiObjectFactoryBean" lazy-init="true">
	<property name="jndiName" value="jdbc/dbGeeks" />
</bean>

Comment lancer nos tests en mode batch (hors conteneur J2EE) utilisant notre fichier de contexte Spring déclaré ci-dessus sachant qu’il utilise un contexte JNDI propre au serveur d’application ?

Tel est l’enjeu de cet article, deux solutions s’offrent à nous :

  1. La première consiste à utiliser l’attribut defaultObject de la classe JndiObjectFactoryBean qui permet de changer de source de données lorsque l’appel JNDI échoue
  2. La seconde consiste à créer une classe qui va créer pour nous le contexte JNDI avant d’utiliser nos services dans les fichiers de configuration Spring

Base de données

  • Les tests réalisés dans l’exemple permettent simplement de se connecter à une base de données et de récupérer le contenu d’une table ‘user’ afin de vérifier que l’accès en base se fait correctement. La base de données utilisée est MySQL version 5.1.41.
  • Script de création de la table
  • CREATE TABLE IF NOT EXISTS `user` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `login` varchar(50) NOT NULL,
      `pass` varchar(200) NOT NULL,
      `address` varchar(500) DEFAULT NULL,
      PRIMARY KEY (`id`),
      UNIQUE KEY `login` (`login`)
    ) ENGINE=InnoDB;
    

Solution 1

La première solution consiste donc à passer vers une source de données de substitution lorsque l’appel JNDI échoue, tout se passe au niveau de la configuration Spring. Les paramètres de la source de données « local » prend donc des paramètres en dur ou par le biais d’un fichier de properties :

  • fichier de configuration Spring
<!-- ###### JNDI Lookup ###### -->
<bean id="dbGeeks" class="org.springframework.jndi.JndiObjectFactoryBean" lazy-init="true">
	<property name="jndiName" value="jdbc/dbGeeks" />
	<!--  fallback to a local datasource if we are not in the container -->
	<property name="defaultObject" ref="localDataSource" />
</bean>

<bean id="localDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
	<property name="driverClassName" value="com.mysql.jdbc.Driver" />
	<property name="url" value="jdbc:mysql://localhost:3306/des_geeks?autoReconnect=true" />
	<property name="username" value="root" />
	<property name="password" value="" />
</bean>
  • classe de test JUnit permettant la récupération du service Spring « userService » et son utilisation
public class TestUserServiceCase1 extends TestCase {
	UserService userService;

	@Override
	protected void setUp() throws Exception {
		super.setUp();
		String[] springFiles = { "applicationContext-metier-case1.xml" };
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext(springFiles);
		userService = (UserService) applicationContext.getBean("userService");
	}

	public void testGetAllUsers() throws Exception {
		List<UserBean> usersList = userService.getAllUsers();
		System.out.println(usersList);
	}
}

Solution 2

La seconde solution consiste à référencer le contexte JNDI avant l’instanciation des objets Spring, ce qui permet de ne pas toucher du tout aux fichiers de configurations Spring, une classe Java fait l’affaire :

  • fichier de configuration Spring d’origine
<!-- ###### JNDI Lookup ###### -->
<bean id="dbGeeks" class="org.springframework.jndi.JndiObjectFactoryBean" lazy-init="true">
	<property name="jndiName" value="jdbc/dbGeeks" />
</bean>
  • classe Java permettant d’initialiser le contexte JNDI
public class JndiDatasourceCreator {

	/** constants datasource */
	private static final String url = "jdbc:mysql://localhost:3306/des_geeks?autoReconnect=true";
	private static final String username = "xxxx";
	private static final String password = "yyyy";
	private static final String jndiName = "dbGeeks";

	public static void create() throws Exception {
		try {
			// initialisation du contexte
			System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.fscontext.RefFSContextFactory");
			InitialContext context = new InitialContext();

			// création d'une référence sur la DataSource
			Reference reference = new Reference("javax.sql.DataSource", "org.apache.commons.dbcp.BasicDataSourceFactory", null);
			reference.add(new StringRefAddr("driverClassName", "com.mysql.jdbc.Driver"));
			reference.add(new StringRefAddr("url", url));
			reference.add(new StringRefAddr("username", username));
			reference.add(new StringRefAddr("password", password));
			// liaison de la DataSource au contexte
			context.rebind("jdbc/" + jndiName, reference);
		} catch (NamingException ex) {
			ex.printStackTrace();
		}
	}
}
  • classe de test JUnit permettant la récupération du service Spring « userService » et son utilisation
public class TestUserServiceCase2 extends TestCase {
	UserService userService;

	@Override
	protected void setUp() throws Exception {
		super.setUp();
		JndiDatasourceCreator.create();
		String[] springFiles = { "applicationContext-metier-case2.xml" };
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext(springFiles);
		userService = (UserService) applicationContext.getBean("userService");
	}

	public void testGetAllUsers() throws Exception {
		List<UserBean> usersList = userService.getAllUsers();
		System.out.println(usersList);
	}
}

Conclusion

C’est important de pouvoir lancer ces test unitaires ou d’intégration en se détachant complètement du serveur d’applications. La mise en place d’une de ces méthodes permet donc de faire cela tranquillement sans avoir à bouleverser votre code (surtout la seconde solution).

Sources disponibles ici

Faîtes tourner l'article :
  • Twitter
  • StumbleUpon
  • Technorati
  • Reddit
  • Digg
  • Scoopeo
  • Fuzz
  • TapeMoi
  • Yahoo! Buzz
  • Blogasty

{ 5 comments… read them below or add one }

1 Greg 19 janvier 2010 à 21 h 26 min

Avec tout ça c’est quand que tu nous ponds un super site ?!

2 Mimie 19 janvier 2010 à 22 h 42 min

J’y travaille :-) et ce sera un site sur magic the gathering ^^

3 Mimie 21 janvier 2010 à 8 h 03 min

Article mis à jour, j’avais oublié de vous présenter la classe Java permettant de réaliser l’initialisation du contexte JNDI dans la seconde solution.
Néanmoins tout se trouve dans le source, ++

4 Rodrigo Hjort 8 février 2010 à 2 h 35 min

Super ! Mais j’ai eu l’impression qu’il fallait mieux introduire la classe « com.sun.jndi.fscontext.RefFSContextFactory » utilisée dans la deuxième solution.

5 Mimie 8 février 2010 à 9 h 55 min

Bonjour Rodrigo, comme toi la seconde solution me parait plus correcte en considérant la première solution plus comme une astuce qu’autre chose :-) néanmoins les deux solutions sont viables c’est pourquoi j’ai voulu les présenter dans cet article, merci à toi d’être passé, au plaisir.

Leave a Comment

Article précédent: Slap up party : un anime dans un univers loufoque

Article suivant: La clé USB Ironkey ultra-sécurisée et gestionnaire de mots de passe