AGILE & MORE EFFICIENT : Test JUnit, EasyMock & Spring

Ce billet aborde l'aspect purement technique de mise en oeuvre de l'agilité dans le développement d'applications robustes.
Il s'inscrit dans la continuité du séminaire de Netapsys sur le thème "Agile & more efficient".
Il présente les tests JUnit 4.x et EasyMock sous Spring afin de concrétiser "être agile".
EasyMock permet de simuler l'accès aux fonctionnalités des couches applicatives, par exemple la couche DAO.
Un des piliers de l'agilité est TDD (Test Driven Development).
Le TDD est une approche évolutive de réalisation de projets basés sur les tests avant même de produire du code effectif.
TDD combine le TFD (Test First development) et le refactoring afin d'arriver à affiner / définir les spécifications.
Easymock et JUnit constituent donc les briques afin de réaliser le TDD via les tests unitaires et d'intégration.
En fait, les tests unitaires sont faciles à mettre en place mais les tests d'intégration restent encore difficiles.
Et le coût de réaliser les tests d'intégration est pesant.
Et EasyMock permet la mise en place des tests sans pour autant avoir développé une ligne de code d'implémentation des couches applicatives.
Tous ces aspects vont être explicités ci-dessous.

Nous démarrons avec un projet exemple simple qui permet de comprendre les notions importantes dans EasyMock.

1- Le projet exemple:

Comme notre objectif est de faire du TDD, nous allons écrire uniquement les interfaces puis enchaîner avec les tests sur des classes simulées (moquées) par EasyMock.

/***interface couche dao**/
public IContactDao{
  	void save(Contact contact) ;
}

Et celle de service:

/***interface couche service*****/
public IService{
  	void save(Contact contact) ;
      void setContactDao(IDao dao);
}

Et l'entité Contact est un POJO simple:

package fr.netapsys.easymock.entites;
 
import java.io.Serializable;
 
import fr.netapsys.easymock.common.BaseObject;
 
public class Contact implements Serializable {
	private static final long serialVersionUID = -1511337412528984583L;
	private int id;
	private String nom;
	private String prenom;
	private String mail;
	public Contact() {
		super();
	}	
 
	//Setters/getters omis		
}

Donc nous avons en place les briques techniques qui vont servir à pratiquer le TDD et favoriser les échanges avec le client.
Écrivons les tests avant de commencer les développement pur et dur des couches dao, service et web(back & front).

La classe de test est simple néanmoins consistante afin d'illustrer réellement l'apport des test mock.

2- Classe JUnit4 test

package fr.netapsys.easymock.tests;
 
import org.apache.log4j.Logger;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import fr.netapsys.easymock.entites.Contact;
import fr.netapsys.easymock.interfaces.*;
//**** notez cet import *****
import static org.easymock.EasyMock.*;
 
@RunWith (SpringJUnit4ClassRunner.class)
@ContextConfiguration (locations = { "classpath:spring.xml" }) 
public class TestEasyMock {
	private final Logger logger=Logger.getLogger(getClass());
	private IContactDao contactDao;
	private IContactService contactService;
	@Before public void setUp() {
		contactDao=createMock(IContactDao.class);
		contactService= createMock(IContactService.class);
	}
	@SuppressWarnings("nls")
	@Test public void testSave() {
			Contact contact=new Contact();
			//************** createMock, expect, replay, call & verify		
			expect(contactDao.save(contact)).andReturn(saveContactInDao(contact));			
			replay(contactDao);
			contactDao.save(contact);
			verify(contactDao);			
			//appel au service
			expect(contactService.save(contact)).andReturn(saveContactInService(contact));
			replay(contactService);
			Contact c=contactService.save(contact);
			logger.info(">>>>Contact retrieved by service :"+contact.toString());
			verify(contactService);
			Assert.assertTrue(  c.getNom().equals(contact.getNom()) && c.getId()==1 && c.getMail().equals("mail@mail.fr"));
	}
	@SuppressWarnings("nls")
	private Contact saveContactInDao(Contact contact) {
		Contact contact2Return=contact;
		contact.setNom("Easymock");
		contact.setPrenom("test");
		//setter id 
		contact2Return.setId(1);
		return contact2Return;
	}	
	@SuppressWarnings("nls")
	private Contact saveContactInService(Contact contact) {
		Contact contact2Return=contact;//contactDao.save(contact);
		contact2Return.setMail("mail@mail.fr");		
		return contact2Return;
	}
}

NB. Le fichier spring.xml est pour l'instant vide. Il ne contient que les déclarations des namespaces.
La seconde partie de ce billet revient sur ce fichier de configuration pour aller plus loin dans l'utilisation de l'api easymock combiné avec spring.

3- Exécutions des tests

La première capture illustre que le test JUnit répond bien:

captureJunit0

Avec une sortie sur la console qui ressemble à:

[INFO  (TestEasyMock.java) >>>>Contact retrieved by service :fr.netapsys.easymock.entites.Contact@17050f5[
  id=1
  nom=Easymock
  prenom=test
  mail=mail@mail.fr
]]}

Easymock est utilisé par spring et le projet apache strutstestcase. Néanmoins, à mon avis, il est plus ou moins intuitif à pratiquer.
Son concurrent MOCKITO séduit par la simplicité d'emploi.
EasyMock se comporte comme un "recorder" et par conséquent nécessite généralement les étapes suivantes:

createMock, ( C'est ce que a été fait dans la méthode annotée par @Before )
expect, (C'est ce qui a été fait dans le corps de la méthode testSave)
replay, (idem)
call, (idem)
verify. (idem. A noter que cet appel peut générer une exception incompréhensible si l'ordre des étapes n'est pas respecté)

La prochaine fois, nous introduirons l'auto-injection Spring des beans de la couche service en simulant (en moquant) uniquement la couche DAO.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.