Vous êtes sur la page 1sur 31

6

Test des applications Spring


Les tests sont une des activits fondamentales du dveloppement logiciel. Ce chapitre montre
comment tester une application reposant sur Spring. Les types de tests abords sont les tests
unitaires et les tests dintgration.
Par tests unitaires, nous entendons les tests portant sur un composant unique isol du reste de
lapplication et de ses composants techniques (serveur dapplications, base de donnes, etc.).
Par tests dintgration, nous entendons les tests portant sur un ou plusieurs composants, avec
les dpendances associes. Il existe bien entendu dautres types de tests, comme les tests de
performance ou les tests fonctionnels, mais Spring ne propose pas doutil spcique dans ces
domaines.
Nous nous arrterons de manire synthtique sur deux outils permettant de raliser des tests
unitaires, le framework JUnit et EasyMock, ce dernier permettant de raliser des simulacres
dobjets, ou mock objects. Ces deux outils nous fourniront loccasion de dtailler les concepts
fondamentaux des tests unitaires. Nous verrons que lutilisation de ces outils est toute natu-
relle pour les applications utilisant Spring, puisque le code de ces dernires ne comporte pas
de dpendance lgard de ce framework du fait de linversion de contrle. Spring propose
dailleurs ses propres simulacres pour muler une partie de lAPI Java EE.
Pour les tests dintgration, nous nous intresserons aux extensions de JUnit fournies par
Spring et par le framework DbUnit, spcialis dans les tests de composants de persistance des
donnes. L encore, les tests savrent aiss implmenter, ces extensions prenant en charge
les problmatiques techniques les plus courantes.
Spring Livre Page 149 Lundi, 15. juin 2009 5:57 17
Les fondations de Spring
PARTIE I
150
Pourquoi crire des tests ?
Lcriture de tests peut paratre paradoxale au premier abord : crire un programme pour en
tester un autre. De plus, lcriture de test peut paratre consommatrice de temps, puisquelle
correspond lcriture de code supplmentaire. Cependant, les tests prsentent un ensemble
davantages qui les rendent indispensables tout dveloppement informatique se voulant
robuste et volutif.
Les tests permettent de guider la conception. En effet, un code difcilement testable est en
gnral un code mal conu. Le rendre facilement testable, unitairement ou via un test dint-
gration, est sans conteste une avance vers un code mieux conu.
Les tests crits sous forme de programme rsistent lpreuve du temps, contrairement aux
tests manuels. Cest pour cette raison que les tests font compltement partie dun projet et
doivent tre sauvegards sur un dpt de sources, tout comme le code de lapplication.
Les tests sont enn un lment essentiel dans la vie dune application car ils lui permettront
dvoluer et de sadapter aux nouveaux besoins. Il est rare quune application ne doive pas
tre modie au cours de sa vie an de rpondre de nouveaux besoins. Sans tests unitaires,
les modications ncessaires se feront gnralement dans la crainte de rgressions ( casser
des fonctionnalits existantes en en rajoutant dautres). Cette crainte ne facilite pas un raison-
nement global, qui privilgierait des modications de conception plutt que des modications
se rapprochant du bricolage . Avec des tests unitaires offrant une bonne couverture, les
modications se font plus sereinement. Des modications plus profondes sont favorises par
les tests unitaires qui permettent de mettre en vidence les rgressions. Avec une telle appro-
che, la qualit de lapplication et son adaptabilit ne se dgradent pas avec le temps mais
samliorent.
Les tests ne sont pas la panace ; ils ne permettront pas systmatiquement une application
dtre sans faille, car ils doivent tre crits avec soin. Ils constituent cependant un engrenage
essentiel dans la mcanique globale de tout dveloppement informatique.
Les tests unitaires avec JUnit
JUnit est un framework Java Open Source cr par Erich Gamma et Kent Beck. Il fournit un
ensemble de fonctionnalits permettant de tester unitairement les composants dun logiciel
crit en Java. Dautres frameworks suivant la mme philosophie sont disponibles pour
dautres langages ou pour des technologies spciques, comme HTTP. Ils constituent la
famille des frameworks xUnit.
Initialement conu pour raliser des tests unitaires, JUnit peut aussi tre utilis pour raliser
des tests dintgration, comme nous le verrons plus loin dans ce chapitre.
Dans les sections suivantes, nous indiquons comment manipuler les diffrents lments four-
nis par JUnit an de crer des tests pour le module core de Tudu Lists. Dans cette applica-
tion, lensemble des cas de tests est regroup dans le rpertoire src/test/java. Les diffrents
tests sont organiss selon les classes quils ciblent. Ainsi, ils reproduisent la structure de
packages de Tudu Lists.
Spring Livre Page 150 Lundi, 15. juin 2009 5:57 17
Test des applications Spring
CHAPITRE 6
151
La version de JUnit tudie ici est la 4.5. Elle privilgie lutilisation dannotations pour dnir
les tests plutt que lhritage dune classe de test (et des conventions de nommage), comme sa
version 3.8, encore certainement la plus utilise. Cette approche par annotations favorise le
modle POJO puisque le framework est relativement peu intrusif (pas de ncessit dhri-
tage). Elle est aussi plus souple et savre trs bien supporte par Spring. Cest pour cet
ensemble de raisons que nous avons prfr ltude de cette nouvelle version.
Les cas de test
Les cas de test sont une des notions de base de JUnit. Il sagit de regrouper dans une entit
unique, en loccurrence une classe Java annote, un ensemble de tests portant sur une classe
de lapplication.
Chaque test est matrialis sous la forme dune mthode sans paramtre, sans valeur de retour
et portant lannotation org.junit.Test. Le nom de la classe regroupant les tests dune classe
est conventionnellement celui de la classe teste sufxe par Test (par exemple,
TodosManagerImplTest).
Squelette dun cas de test
Pour introduire la notion de cas de test, nous allons utiliser la classe Todo dnie dans le
package tudu.domain.model. Cette classe dnit deux mthodes, compareTo (mthode de
linterface java.lang.Comparable) et equals (hrite de java.lang.Object).
Pour tester ces deux mthodes, nous allons crer plusieurs instances de la classe Todo et effec-
tuer des comparaisons ainsi que des tests dgalit. Nous dnissons pour cela une classe
TodoTest ayant deux mthodes, compareTo et equals :
package tudu.domain.model;
import org.junit.Test;
public class TodoTest {
(...)
@Test
public void compareTo() {
(...)
}
@Test
public void equals() {
(...)
}
}
Spring Livre Page 151 Lundi, 15. juin 2009 5:57 17
Les fondations de Spring
PARTIE I
152
Notons que la classe de test ne dpend daucune classe par un hritage ou dune interface par
une implmentation. De plus, les mthodes de tests portent directement le nom des mthodes
testes.
La notion de xture
Dans JUnit, la notion de xture, ou contexte, correspond un ensemble dobjets utiliss par
les tests dun cas. Typiquement, un cas est centr sur une classe prcise du logiciel. Il est donc
possible de dnir un attribut ayant ce type et de lutiliser dans tous les tests du cas. Il devient
alors une partie du contexte. Le contexte nest pas partag par les tests, chacun deux poss-
dant le sien, an de leur permettre de sexcuter indpendamment les uns des autres. Il est
important de noter quavec JUnit, le contexte est initialis pour chacune des mthodes dune
classe de test.
Avec JUnit, il est possible de mettre en place le contexte de plusieurs manires. La manire la
plus simple consiste annoter des mthodes avec lannotation org.junit.Before. Ces mtho-
des doivent avoir une signature de type public void. Si linitialisation du contexte alloue des
ressources spciques, il est ncessaire de librer ses ressources. Cela se fait en annotant des
mthodes avec lannotation org.junit.After. Les mthodes annotes pour la destruction du
contexte ont les mmes contraintes que son initialisation. Il est donc frquent que toute
mthode dinitialisation ait son pendant pour la destruction du contexte.
Dans JUnit 3, le cycle de vie du contexte tait gr par la surcharge de deux mthodes : setUp
et tearDown. Il est donc frquent dadopter cette nomenclature avec lapproche annotations.
La gestion du contexte peut donc se dnir de la manire suivante :
public class TodoTest {
@Before
public void setUp() {
// Cration du contexte
}
@After
public void tearDown() {
// Destruction du contexte
}
(...)
}
Pour les tests de la classe Todo, nous pouvons crer un ensemble dattributs de type Todo qui
nous serviront de jeu dessai pour nos mthodes de test :
public class TodoTest {
private Todo todo1;
private Todo todo2;
private Todo todo3;
Spring Livre Page 152 Lundi, 15. juin 2009 5:57 17
Test des applications Spring
CHAPITRE 6
153
@Before
public void setUp() {
todo1 = new Todo();
todo1.setTodoId("01");
todo1.setCompleted(false);
todo1.setDescription("Description");
todo1.setPriority(0);

todo2 = new Todo();
todo2.setTodoId("02");
todo2.setCompleted(true);
todo2.setDescription("Description");
todo2.setPriority(0);

todo3 = new Todo();
todo3.setTodoId("01");
todo3.setCompleted(false);
todo3.setDescription("Description");
todo3.setPriority(0);
}
(...)
}
Les assertions et lchec
Dans JUnit, les assertions sont des mthodes permettant de comparer une valeur obtenue lors
du test avec une valeur attendue. Si la comparaison est satisfaisante, le test peut se poursuivre.
Dans le cas contraire, il choue, et un message derreur safche dans loutil permettant
dexcuter les tests unitaires.
Les assertions peuvent tre effectues avec des appels des mthodes statiques de la classe
org.junit.Assert. Leur nom est prx par assert.
Pour les boolens, les assertions suivantes sont disponibles :
assertFalse (boolean obtenu);
assertTrue (boolean obtenu);
Elles testent le boolen obtenu sur les deux valeurs littrales possibles, faux ou vrai.
Pour les objets, les assertions suivantes sont disponibles, quel que soit leur type :
assertEquals (Object attendu,Object obtenu);
assertSame (Object attendu,Object obtenu);
assertNotSame (Object attendu,Object obtenu);
assertNull (Object obtenu);
assertNotNull (Object obtenu);
assertEquals teste lgalit de deux objets tandis quassertSame teste que attendu et obtenu
font rfrence un seul et mme objet. Par exemple, deux objets de type java.util.Date
peuvent tre gaux, cest--dire contenir la mme date, sans tre pour autant un seul et mme
Spring Livre Page 153 Lundi, 15. juin 2009 5:57 17
Les fondations de Spring
PARTIE I
154
objet. assertNotSame vrie que deux objets sont diffrents. Les deux dernires assertions
testent si lobjet obtenu est nul ou non.
Pour les diffrents types primitifs (long, double, etc.), une mthode assertEquals est dnie,
permettant de tester lgalit entre une valeur attendue et une valeur obtenue. Dans le cas des
types primitifs correspondant des nombres rels (double), un paramtre supplmentaire, le delta,
est ncessaire, car les comparaisons ne peuvent tre tout fait exactes du fait des arrondis.
Il existe une variante pour chaque assertion prenant une chane de caractres en premier para-
mtre (devant les autres). Cette chane de caractres contient le message afcher si le test
choue au moment de son excution. Si lchec dune assertion nest pas vident, il est en
effet important de prciser la raison de lchec avec un message.
Les assertions ne permettent pas de capter tous les cas dchec dun test. Pour ces cas de
gure, JUnit fournit la mthode fail sous deux variantes : une sans paramtre et une avec un
paramtre, permettant de donner le message derreur afcher sous forme de chane de carac-
tres. Lappel cette mthode entrane larrt immdiat du test en cours et lafche en erreur
dans loutil dexcution des tests unitaires.
Les tests unitaires nhritant daucune classe, ils doivent appeler les mthodes dassertions
directement, par exemple :
Assert.assertTrue(someBooleanValue);
Cette syntaxe peut tre simplie via un import statique :
import static org.junit.Assert.*;
(...)
// dans une mthode de test :
assertTrue(someBooleanValue);
Si nous reprenons notre exemple TodoTest, il se prsente dsormais de la manire suivante :
public class TodoTest {
(...)
@Test
public void compareTo() {
// Vrifie la consistance avec la mthode equals
// Cf. JavaDoc de l'API J2SE
assertTrue(todo1.compareTo(todo3)==0);
// Vrifie le respect de la spec de Comparable pour null
// Cf. JavaDoc de lAPI J2SE
try {
todo1.compareTo(null);
fail();
}
catch(NullPointerException e){
Spring Livre Page 154 Lundi, 15. juin 2009 5:57 17
Test des applications Spring
CHAPITRE 6
155
// OK
}
// todo1 n'est pas ferm donc < todo2
assertTrue(todo1.compareTo(todo2)<0);
// Vrifie que l'inverse est vrai aussi
assertTrue(todo2.compareTo(todo1)>0);
}
@Test
public void equals() {
assertEquals(todo1,todo3);
assertFalse(todo1.equals(todo2));
}
}
Excution des tests
Une fois les cas de test et les suites de tests dnis, il est ncessaire de les excuter pour vrier
le logiciel.
Le lanceur standard de JUnit
JUnit introduit la notion de lanceur pour excuter les tests et propose un lanceur par dfaut.
Il est textuel et utilise en fait une classe crite pour JUnit 3, le motif de conception adaptateur
tant utilis pour que des tests JUnit 4 puissent tre lancs.
Pour utiliser ce lanceur en mode texte dans un cas de test (ici la classe TodoTest), il suft dy
ajouter une mthode main de la manire suivante :
public static void main(String[] args) {
junit.textui.TestRunner.run(
new JUnit4TestAdapter(TodoTest.class)
);
}
La classe junit.textui.TestRunner est appele en lui passant en paramtre la classe du cas de
test (et non une instance) excuter, via un adaptateur pour JUnit 4. Cette mthode main peut
tre crite soit directement dans la classe du cas de test, comme dans cet exemple, soit dans
une classe spcique.
Si nous excutons TodoTest, nous obtenons le rsultat suivant dans la console Java :
..
Time: 0,047
OK (2 tests)
Ce rsultat indique de manire laconique que les deux tests dnis dans TodoTest se sont bien
excuts.
Spring Livre Page 155 Lundi, 15. juin 2009 5:57 17
Les fondations de Spring
PARTIE I
156
Le lanceur intgr JUnit a le mrite dexister, mais il sert principalement illustrer lutilisation
du framework et on lui prfre gnralement dautres lanceurs, selon les besoins.
Le lanceur JUnit intgr Eclipse
Eclipse propose son propre lanceur JUnit, parfaitement intgr lenvironnement de dvelop-
pement.
Pour lutiliser, il nest pas ncessaire de crer une mthode main spcique dans les cas de test,
la diffrence des lanceurs standards. Il suft de slectionner lexplorateur de package et le
chier du cas de test par clic droit et de choisir Run dans le menu contextuel. Parmi les choix
proposs par ce dernier, il suft de slectionner JUnit Test.
Une vue JUnit souvre alors pour nous permettre de consulter le rsultat de lexcution des
tests. Si tel nest pas le cas, nous pouvons louvrir en choisissant Window puis Show view et
Other. Une liste hirarchise safche, dans laquelle il suft de slectionner JUnit dans le
dossier Java et de cliquer sur le bouton OK (voir gure 6-1).
Lintrt de ce lanceur rside dans sa gestion des checs aprs lexcution des tests.
Si nous modions TodoTest de manire que deux tests chouent (il suft pour cela de mettre
une valeur attendue absurde, qui ne sera pas respecte par la classe teste), nous obtenons
dans le lanceur le rsultat illustr la gure 6-2.
Figure 6-1
Vue JUnit intgre
Eclipse
Figure 6-2
Gestion des checs dans
la vue JUnit dEclipse
Spring Livre Page 156 Lundi, 15. juin 2009 5:57 17
Test des applications Spring
CHAPITRE 6
157
Les checs (failures) sont indiqus par une croix bleue et les succs par une marque verte. En
cliquant sur un test ayant chou, la trace dexcution est afche dans la zone Failure Trace.
En double-cliquant sur le test, son code source est immdiatement afch dans lditeur de
code dEclipse.
partir des rsultats des tests, le dveloppeur peut naviguer directement dans son code et le
corriger dans Eclipse, ce qui nest pas possible avec les lanceurs standards de JUnit.
Lanceurs intgrs la construction dun projet
Les tests unitaires font compltement partie du code source dun projet ; il ny a donc aucune
raison pour que leur excution ne fasse pas partie du processus de construction ( build )
dun projet.
Les principaux outils de construction en Java (Ant, Maven) disposent donc de systmes
dexcution des tests unitaires. La construction de Tudu Lists est faite avec Maven 2, outil qui
intgre le lancement des tests unitaires via le plugin Surere. Maven 2 lance tous les tests se
trouvant dans un rpertoire donn (gnralement src/test/java). Le plugin Surere est capable
de lancer diffrents types de tests (JUnit 3 et 4, TestNG). Les rsultats des tests sont stocks
sous forme de chiers (texte et XML), qui sont ensuite transforms pour gnrer des rapports
plus lisibles, au format HTML.
La gure 6-3 illustre des rapports de tests unitaires pour la partie core de Tudu Lists.
Figure 6-3
Rapports de tests unitaires avec Maven 2
Spring Livre Page 157 Lundi, 15. juin 2009 5:57 17
Les fondations de Spring
PARTIE I
158
Quel que soit le systme utilis, il est essentiel dincorporer le lancement des tests unitaires au
processus de construction et davoir un moyen de communiquer les rsultats, an davoir une
ide prcise de la sant du projet.
En rsum
Lcriture de tests avec JUnit nest pas intrinsquement complique. Cependant, il faut
garder lesprit que cette simplicit dutilisation dpend fortement de la faon dont lappli-
cation est conue. La mise en uvre de tests unitaires dans une application mal conue et
qui, par exemple, ne montre aucune structuration peut savrer trs difcile, voire parfaite-
ment impossible.
Lutilisation de JUnit est rendue efcace grce aux principes architecturaux que doivent appliquer
les applications Spring, savoir linversion de contrle apporte par le conteneur lger et la
sparation claire des couches de lapplication. Grce ces principes, les composants se
rvlent plus simples tester, car ils se concentrent dans la mesure du possible sur une seule
proccupation.
En complment de JUnit, il est ncessaire dutiliser des simulacres dobjets an disoler des
contingences extrieures le composant tester unitairement. Cest ce que nous allons voir la
section suivante.
Les simulacres dobjets
Le framework JUnit constitue un socle pour raliser des tests, mais il noffre aucune fonction-
nalit spcique pour tester les relations entre les diffrents objets manipuls lors dun test.
Il est de ce fait difcile disoler les dysfonctionnements de lobjet test de ceux quil mani-
pule, ce qui nest pas souhaitable lorsque nous ralisons des tests unitaires. Lide est en effet
de tester seulement une classe et de ne pas dpendre des classes quelles utilisent.
An de combler ce vide, il est possible dutiliser des simulacres dobjets (mock objects).
Comme leur nom lindique, ces simulacres simulent le comportement dobjets rels. Il leur
suft pour cela dhriter de la classe ou de linterface de lobjet rel et de surcharger chaque
mthode publique utile au test. Le comportement de ces mthodes est ainsi redni selon un
scnario conu spciquement pour le test unitaire et donc parfaitement matris.
Par exemple, si nous testons un objet faisant appel un DAO, nous pouvons remplacer ce
dernier par un simulacre. Ce simulacre ne se connectera pas la base de donnes, mais
adoptera un comportement qui lui aura t dict. Ce comportement consistera gnrale-
ment retourner certaines donnes en fonction de certains paramtres. Nous isolons de la
sorte lobjet test des contingences spciques au DAO (connexion la base de donnes,
etc.).
Diffrences entre simulacres et bouchons
Lide dutiliser des implmentations de test en lieu et place de vritables implmentations est
une pratique assez commune. Cependant ces implmentations de tests se prsentent sous
Spring Livre Page 158 Lundi, 15. juin 2009 5:57 17
Test des applications Spring
CHAPITRE 6
159
diffrentes formes. De faon gnrale, les implmentations destines seulement aux tests sont
appeles des doublures (en rfrence aux doublures dans les lms daction). Il existe
diffrents types de doublures, notamment les simulacres et les bouchons (respectivement
mock et stubs).
Les bouchons sont gnralement de vritables implmentations, dans le sens o du code a t
crit pour les implmenter. Leur comportement est limit ce qua prvu le dveloppeur pour
le test. Ils peuvent maintenir des informations sur les appels auxquels ils ont d rpondre (par
exemple, une variable permettant de dire quun e-mail a t envoy).
Les simulacres sont des objets prprogramms avec un ensemble dexpectations qui consti-
tuent une spcication de la squence dappels quils sont censs recevoir. Il sagit, par exem-
ple, de dire que la mthode getProperty doit tre appele en tout deux fois, la premire avec
le paramtre smtp.host et la seconde avec le paramtre smtp.user. Le simulacre peut aussi tre
programm pour renvoyer une certaine valeur pour chacun des appels. Les comportements
des simulacres pouvant tre complexes, des implmentations ges (bouchons) ne peuvent
convenir, do la ncessit dun outillage pour gnrer ces objets.
Lcriture de bouchons est gnralement spcique un projet et ncessite plus de bon sens
que de technique. Notre tude se limitera donc aux simulacres.
Les simulacres dobjets avec EasyMock
Plusieurs frameworks permettent de crer facilement des simulacres au lieu de les dvelopper
manuellement. Pour les besoins de louvrage, nous avons choisi EasyMock, qui est lun des
plus simples dutilisation.
EasyMock est capable de crer des simulacres partir dinterfaces. Une extension permet
den fournir partir de classes, mais nous ne labordons pas ici, puisque, dans le cas dappli-
cations Spring, nous utilisons essentiellement des interfaces.
Cette section nintroduit que les fonctionnalits principales dEasyMock. Nous utilisons la
version 2.4, qui fonctionne avec Java 5. Pour plus dinformations, voir le site Web ddi au
framework, ladresse http://www.easymock.org.
Les simulacres simples
Les simulacres dits simples renvoient gnralement des valeurs prdnies. Leur comportement
se rapproche de celui des bouchons. Leur avantage rside dans leur caractre dynamique, car ils
sont facilement modiables. Ils ne ncessitent pas non plus de crer de classe spcique.
Supposons que nous dsirions tester de faon unitaire la classe ConfigurationManagerImpl en
charge de la conguration des paramtres de lapplication Tudu Lists. Cette classe possde
une dpendance vis--vis de linterface PropertyDAO. Pour pouvoir lisoler des contingences
lies limplmentation fournie par Tudu Lists de cette interface, il est ncessaire de crer un
simulacre dont nous contrlons trs exactement le comportement.
Spring Livre Page 159 Lundi, 15. juin 2009 5:57 17
Les fondations de Spring
PARTIE I
160
Avant de programmer notre simulacre, dnissons rapidement son comportement au moyen
de la mthode getProperty, seule utile pour notre test :
Si le paramtre de getProperty vaut key, renvoyer un objet Property dont lattribut key vaut
key et dont lattribut value vaut value.
Dans tous les autres cas, renvoyer par dfaut un objet Property dont les attributs key et value
valent tous les deux default.
Maintenant que nous avons dni les comportements du simulacre, nous pouvons le dnir au
sein dun cas de test :
package tudu.service.impl;
import static org.easymock.EasyMock.*;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import tudu.domain.dao.PropertyDAO;
import tudu.domain.model.Property;
public class ConfigurationManagerImplECTest {
private PropertyDAO propertyDAO = null;
private ConfigurationManagerImpl configurationManager = null;
@Before
public void setUp() throws Exception {
propertyDAO = createMock(PropertyDAO.class);
configurationManager = new ConfigurationManagerImpl();
configurationManager.setPropertyDAO(propertyDAO);
}
@Test
public void getProperty() {
Property property = new Property();
property.setKey("key");
property.setValue("value");
expect(propertyDAO.getProperty("key"))
.andStubReturn(property);

Property defaultProperty = new Property();
defaultProperty.setKey("default");
Spring Livre Page 160 Lundi, 15. juin 2009 5:57 17
Test des applications Spring
CHAPITRE 6
161
defaultProperty.setValue("default");
expect(propertyDAO.getProperty((String)anyObject()))
.andStubReturn(defaultProperty);
replay(propertyDAO);
Property test = configurationManager.getProperty("key");
assertEquals("value", test.getValue());
test = configurationManager.getProperty("anything");
assertEquals("default",test.getValue());
verify(propertyDAO);
}
}
Pour simplier lcriture du code ncessaire la dnition des simulacres, des imports stati-
ques, nouveaut introduite avec Java 5, sont utiliss (). Les imports statiques permettent de
ne plus avoir spcier la classe (en loccurrence la classe org.easymock.EasyMock) lors des
appels ses mthodes statiques.
Nous avons dni une mthode de gestion de contexte en utilisant lannotation org.junit.Before.
Par convention, nous avons appel cette mthode setUp. Elle cre le simulacre et linjecte dans le
manager. La cration du simulacre consiste crer une instance dobjet implmentant linterface
PropertyDAO en utilisant la mthode createMock (). Cette mthode prend en paramtre linterface
que le simulacre doit implmenter, en loccurrence PropertyDAO.
Une fois le simulacre cr, nous linjectons manuellement dans le manager, puisque nous
sommes dans le cadre de tests unitaires, cest--dire sans conteneur ().
Pour que le simulacre fonctionne, il est ncessaire de spcier le comportement de chacune de
ses mthodes publiques utilises dans le cadre du test. Cest ce que nous faisons au dbut de
la mthode getProperty (, ) en utilisant la mthode statique expect de la classe EasyMock.
Au repre , nous spcions le comportement de la mthode getProperty de propertyDAO
lorsque celle-ci a comme paramtre key, cest--dire la valeur que la mthode doit renvoyer,
en loccurrence lobjet property. Pour cela, nous passons en paramtre expect lappel
getProperty proprement dit. Nous utilisons ensuite la mthode andStubReturn de lobjet
renvoy par expect pour spcier la valeur de retour correspondant au paramtre key.
Au repre , nous spcions le comportement par dfaut de getProperty, cest--dire quel
que soit son paramtre. Pour indiquer que le paramtre attendu peut tre nimporte lequel,
nous utilisons la mthode anyObject dEasyMock. La valeur de retour est, l encore, spcie
avec la mthode andStubReturn.
Pour pouvoir excuter le test unitaire proprement dit, il suft dappeler la mthode replay en
lui passant en paramtre le simulacre (). Nous indiquons ainsi EasyMock que la phase
denregistrement du comportement du simulacre est termine et que le test commence.
Spring Livre Page 161 Lundi, 15. juin 2009 5:57 17
Les fondations de Spring
PARTIE I
162
Une fois le test termin, un appel la mthode verify () permet de vrier que le simulacre
a t correctement utilis (cest--dire que toutes les mthodes ont bien t appeles).
Si nous excutons notre test unitaire, nous constatons que tout se droule sans accroc, comme
lillustre la gure 6-4.
Notons que si une mthode dont le comportement nest pas dni est appele, une erreur est
gnre. Il est possible daffecter un comportement par dfaut chaque mthode en utilisant
la mthode createNiceMock au lieu de createMock. Son intrt est toutefois limit, car ce
comportement consiste renvoyer 0, false ou null comme valeur de retour.
Les simulacres avec contraintes
Le framework EasyMock permet daller plus loin dans les tests dune classe en spciant la
faon dont les mthodes du simulacre doivent tre utilises.
Au-del de la simple dnition de mthodes bouchons, il est possible de dnir des contrain-
tes sur la faon dont elles sont utilises, savoir le nombre de fois o elles sont appeles et
lordre dans lequel elles le sont.
Dnition du nombre dappels
EasyMock permet de spcier pour chaque mthode bouchon des attentes en termes de
nombre dappels. La manire la plus simple de dnir cette contrainte consiste ne pas dnir
de valeur par dfaut, autrement dit supprimer le code au niveau du repre et remplacer
la mthode andStubReturn par la mthode andReturn.
EasyMock sattend alors ce quune mthode soit appele une seule fois pour une combinai-
son de paramtres donne. Si, dans lexemple prcdent, aprs ces modications, nous appe-
lons la mthode getProperty avec le paramtre key deux fois dans notre test, une erreur
dexcution est gnre, comme lillustre la gure 6-5.
Figure 6-4
Excution du cas de test
avec bouchon
Spring Livre Page 162 Lundi, 15. juin 2009 5:57 17
Test des applications Spring
CHAPITRE 6
163
Lerreur afche indique que lappel la mthode getProperty est inattendu (unexpected). Le
nombre dappel de ce type attendu (expected) et effectif (actual) est prcis. En loccurrence,
le nombre attendu dappel est 1, et le nombre effectif 2 (1 + 1).
De mme, si la mthode getProperty nest pas appele avec le paramtre key, une erreur est
gnre puisque EasyMock sattend ce quelle soit appele une fois de cette manire.
Pour dnir le nombre de fois o une mthode doit tre appele, nous disposons des quatre
mthodes suivantes, utiliser sur le rsultat produit par andReturn :
times(int nbre) : spcie un nombre dappels exact.
times(int min,int max) : spcie un nombre dappels born.
atLeastOnce : spcie que la mthode doit tre appele au moins une fois.
anyTimes : le nombre dappels est quelconque (y compris 0).
Le code ci-dessous spcie un nombre dappels gal 2 :
expect(propertyDAO.getProperty("key"))
.andReturn(property).times(2);
Si nous excutons notre test appelant deux fois getProperty("key") avec ce nouveau simulacre,
nous nobtenons plus derreur.
Dnition de lordre dappel des mthodes
Lordre dappel des mthodes peut tre important pour tester la faon dont un objet manipule
les autres. Avec EasyMock, nous pouvons dnir lordre dans lequel les mthodes dun simu-
lacre doivent tre appeles.
Pour tenir compte de lordre, il suft de remplacer la mthode createMock du contrleur par
createStrictMock. Lordre dappel des mthodes est alors dni par la squence de leur appel
pour la dnition du simulacre.
Pour tester leffet de cette modication, modions le code de notre test. Juste aprs le
repre , dnissons le comportement de getProperty avec le paramtre another :
anotherProperty.setKey("another");
anotherProperty.setValue("something");
expect(propertyDAO.getProperty("another"))
.andReturn(anotherProperty);
Figure 6-5
Appel inattendu la
mthode getProperty
Spring Livre Page 163 Lundi, 15. juin 2009 5:57 17
Les fondations de Spring
PARTIE I
164
Introduisons ensuite le test suivant juste aprs le premier test de la mthode getProperty :
test = configurationManager.getProperty("another");
assertEquals("something", test.getValue());
Si nous effectuons cette modication sur le code prcdent sans autre modication, nous obte-
nons le rsultat illustr la gure 6-6.
Lerreur indique que la valeur de retour attendue tait celle du comportement par dfaut, puis-
que le comportement avec le paramtre another a t dni aprs le comportement avec le
paramtre key, qui na pas encore t appel.
Les simulacres dobjets de Spring
Spring ntant pas un framework ddi aux tests unitaires, il ne fournit pas de services similai-
res ceux dEasyMock. Cependant, an de faciliter lcriture de tests unitaires, Spring fournit
un ensemble de simulacres prts lemploi. Ces simulacres simulent des composants standards
de lAPI Java EE souvent utiliss dans les applications.
Lensemble de ces simulacres est dni dans des sous-packages de org.spring-
framework.mock.
Les simulacres Web
Dans le package org.springframework.mock.web, sont dnis les simulacres ncessaires pour
tester des composants Web. Nous y trouvons des simulacres pour les principales interfaces de
lAPI servlet, notamment les suivants :
HttpServletRequest
HttpServletResponse
HttpSession
Dans Tudu Lists, nous disposons dune servlet pour effectuer la sauvegarde du contenu dune liste
de todos. Le code suivant, extrait de la classe BackupServletTest du package tudu.web.servlet,
montre comment les simulacres Web MockHttpServletRequest, MockHttpServletResponse et
MockHttpSession sont utiliss pour tester cette servlet dans un cas de test JUnit classique :
@Test
public void doGet() throws Exception {
Document doc = new Document();
Element todoListElement = new Element("todolist");
Figure 6-6
Rsultat du non-respect
de lordre dappel
Spring Livre Page 164 Lundi, 15. juin 2009 5:57 17
Test des applications Spring
CHAPITRE 6
165
todoListElement.addContent(
new Element("title").addContent("Backup List"));
doc.addContent(todoListElement);


assertTrue(xmlContent.indexOf("<title>Backup List</title>")>0);
}
Le code en gris, qui concentre lutilisation des simulacres, montre que lemploi de ces
derniers est trs ais. Leur cration ne ncessite aucun paramtrage particulier, et toutes les
mthodes pour dnir leur contenu puis le rcuprer sont disponibles.
Autres considrations sur les simulacres
Pour terminer cette section consacre aux simulacres dobjets, il nous semble important
dinsister sur deux points fondamentaux pour bien utiliser les simulacres :
Un cas de test portant sur un objet utilisant des simulacres ne doit pas tester ces derniers.
Lobjectif des simulacres est non pas dtre tests, mais disoler un objet des contingences
extrieures an de faciliter sa vrication.
Un simulacre doit tre conu de manire produire des donnes permettant de simuler
lensemble du contrat de lobjet tester. Ce contrat peut consister retourner certaines
donnes selon tel paramtre, mais aussi lancer une exception selon tel autre paramtre. Le
passage des tests ne dmontre pas quun objet fonctionne correctement, mais seulement
quil a su passer les tests.
EasyMock rpond des besoins moyennement complexes en termes de simulacres. Pour des
besoins plus complexes, il est conseill de dvelopper spciquement les simulacres sans
laide dun framework.
En rsum
Comme pour les tests avec JUnit, les principes architecturaux de Spring facilitent la cration
de simulacres. Dune part, la sparation des interfaces et de leurs implmentations permet
dutiliser nativement EasyMock sans passer par une extension permettant de raliser des
simulacres partir de classes. Dautre part, linjection des dpendances dans le composant

MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpSession session = new MockHttpSession();
session.setAttribute("todoListDocument", doc);
request.setSession(session);

MockHttpServletResponse response =
new MockHttpServletResponse();

BackupServlet backupServlet = new BackupServlet();
backupServlet.doGet(request, response);

String xmlContent = response.getContentAsString();
Spring Livre Page 165 Lundi, 15. juin 2009 5:57 17
Les fondations de Spring
PARTIE I
166
pouvant tre faite manuellement, donc sans le conteneur lger, il est ais dinitialiser les colla-
borateurs du composant tester, non pas avec des implmentations, mais avec des simulacres
permettant de lisoler des contingences extrieures.
Les simulacres prts lemploi fournis par Spring sont trs utiles en ce quils simulent des
classes standards de lAPI Java, trs utilises dans les applications Java EE.
Les tests dintgration
JUnit et EasyMock sufsent dans la plupart des cas raliser des tests unitaires. Les tests
unitaires sont ncessaires mais pas sufsants pour tester le bon fonctionnement dune applica-
tion. Les tests dintgration viennent en complment pour garantir que lintgration entre les
composants de lapplication seffectue correctement.
Dans cette section, nous allons mettre en place le test dun DAO de Tudu Lists. Pour cela,
nous utiliserons dune part les extensions de Spring pour JUnit, an de bncier de linjec-
tion de dpendances du conteneur lger, et dautre part le framework DbUnit pour initialiser
le contenu de la base de donnes de tests.
Les extensions de Spring pour JUnit
Comme nous lavons dj indiqu, JUnit peut tre utilis pour faire des tests unitaires aussi
bien que des tests dintgration. Cest donc tout fait naturellement que Spring fournit des
extensions JUnit an de pouvoir tester lintgration des composants de lapplication. Ces
extensions ont pour vocation dinstancier le conteneur lger an de bncier de linjection de
dpendances et de la POA.
Ces extensions se prsentent sous forme dannotations, contenues dans le package
org.springframework.test.context et ses sous-packages. Ces annotations font partie du
Spring TestContext Framework, qui propose les fonctionnalits suivantes :
Chargement dun contexte Spring pour chaque test.
Mise en cache automatique des contextes Spring partags entre plusieurs tests (acclrant
ainsi lexcution globale des tests).
Injection de dpendances dans les tests.
Possibilit dajout dobjets ragissant au cycle de vie des tests.
Lutilisation dannotations va dans le mme sens que JUnit 4.x et permet aussi une meilleure
rutilisabilit des composants de tests, contrairement lhritage de classes de test.
Spring et les versions de JUnit
Spring 2.5 supporte JUnit dans sa version 4.4 mais pas dans sa version 4.5, cela cause de change-
ments dans lAPI interne. JUnit 4.5 est en revanche support par Spring 3.0. Pour le dveloppeur de tests,
il ny a aucune diffrence, les deux versions de JUnit sutilisent de la mme manire.
Spring Livre Page 166 Lundi, 15. juin 2009 5:57 17
Test des applications Spring
CHAPITRE 6
167
Voici comment le test du DAO permettant de grer les proprits de Tudu Lists peut bncier
du support de Spring :
package tudu.domain.dao.jpa;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import
org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class PropertyDAOJpaTest {
(...)
}
Lannotation @RunWith est en fait une annotation JUnit qui indique au lanceur utilis de dl-
guer lexcution du test la classe de lanceur prcise en argument. Le SpringJUnit4-
ClassRunner implmente ainsi toute la logique dexcution du framework de test de Spring.
Utiliser ce lanceur nayant pas dimpact direct sur lexcution des tests, on lance toujours
ceux-ci avec les moyens habituels (lanceurs texte de JUnit, Eclipse ou outils de construction).
Le lanceur Spring va notamment extraire des informations de lannotation
@ContextConfiguration. Celle-ci prcise quun contexte Spring est ncessaire au test et peut
accepter des paramtres prcisant quels sont les chiers dnissant ce contexte. Si aucun
paramtre nest pass @ContextConfiguration, le nom du chier de conguration sera dter-
min partir du nom de la classe de test. Dans notre cas, la classe de test sappelle
tudu.domain.dao.jpa.PropertyDAOJpaTest et le contexte Spring sera charg partir de
classpath:/tudu.domain.dao.jpa.PropertyDAOJpaTest-context.xml.
Ce comportement par dfaut est commode, mais nous prfrons prciser les chiers dnissant le
contexte de notre test. Ces chiers seront au nombre de deux :
/tudu/conf/jpa-dao-context.xml : dclare le contexte JPA et les DAO. Il nest pas auto-
nome, car il ncessite dautres beans, notamment la connexion la base de donnes (sous
forme dun DataSource) et ladaptateur JPA. Ces deux beans ne sont pas directement
dclars dans ce chier, car ils varient selon les environnements (tests dintgration ou utili-
sation de lapplication en production).
/tudu/domain/dao/jpa/test-context.xml : complte le chier prcdent en dnissant la
connexion la base de tests et ladaptateur JPA (qui utilise Hibernate). Il dnit aussi une
politique de transaction car pour les tests dintgration des DAO, en labsence de services
mtier, les transactions sont ramenes sur ceux-ci.
Le chier jpa-dao-context.xml est exactement le mme que celui dni dans la partie sur la
persistance des donnes, car il fait partie des chiers de congurations xes de Tudu Lists.
Voici lessentiel du chier test-context.xml :
<bean id="dataSource" class="org.springframework.jdbc.datasource.Single
ConnectionDataSource"
Spring Livre Page 167 Lundi, 15. juin 2009 5:57 17
Les fondations de Spring
PARTIE I
168
>
<property name="driverClassName" value="org.hsqldb.jdbcDriver" />
<property name="url" value="jdbc:hsqldb:mem:tudu-test" />
<property name="username" value="sa" />
<property name="password" value="" />
<property name="suppressClose" value="true" />
</bean>
<bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.Hibernate
JpaVendorAdapter">
<property name="showSql" value="false" />
<property name="generateDdl" value="true" />
<property name="databasePlatform"
value="org.hibernate.dialect.HSQLDialect" />
</bean>
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager">
<property
name="entityManagerFactory"
ref="entityManagerFactory" />
</bean>
<aop:config>
<aop:pointcut
id="dao"
expression="execution(* tudu.domain.dao.jpa..*DAO*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="dao"/>
</aop:config>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
Le chier commence par la dnition de la connexion la base de donnes sous la forme dun
DataSource, au repre . Cette dnition est particulirement adapte une conguration de
test : le DataSource ne cre quune connexion qui est rutilise systmatiquement, et la base de
donnes est cre en mmoire, il nest donc pas ncessaire de disposer dun serveur et de le
lancer.
Ladaptateur JPA est ensuite dni au repre . Une implmentation Hibernate est utilise,
qui permet notamment de crer les tables la vole. Cela se rvle particulirement utile puis-
que la base de donnes est en mmoire et ne peut tre initialise en-dehors de lexcution du
test.
Enn, la politique de transaction est dnie au repre . Il sagit l dune dnition purement
dclarative, ramenant les transactions au niveau des DAO pour le besoin des tests.
Spring Livre Page 168 Lundi, 15. juin 2009 5:57 17
Test des applications Spring
CHAPITRE 6
169
Il faut maintenant congurer le test unitaire pour quil utilise ces chiers. Cela se fait en
passant un tableau de chanes de caractres au paramtre locations de lannotation
@TestConfiguration :
package tudu.domain.dao.jpa;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import \
org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={
"/tudu/domain/dao/jpa/test-context.xml",
"/tudu/conf/jpa-dao-context.xml"
})
public class PropertyDAOJpaTest {
(...)
}
Le contexte de notre test va donc sinitialiser parfaitement en rutilisant un chier de congu-
ration de lapplication et en le compltant avec un chier ddi aux tests dintgration des
DAO.
Le test va travailler sur le DAO grant les proprits, PropertyDAO. Il faut donc que cet objet
soit une proprit du test. Il suft alors de dclarer cette proprit dans le test et de faire en
sorte quelle soit injecte. Linjection de dpendances dans le test unitaire est bien sr prise en
charge par le lanceur Spring. La concision tant de rigueur, nous utilisons lannotation
@Autowired :
package tudu.domain.dao.jpa;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import \
org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import tudu.domain.dao.PropertyDAO;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={
"/tudu/domain/dao/jpa/test-context.xml",
"/tudu/conf/jpa-dao-context.xml"
})
public class PropertyDAOJpaTest {
@Autowired
private PropertyDAO propertyDAO;
}
Spring Livre Page 169 Lundi, 15. juin 2009 5:57 17
Les fondations de Spring
PARTIE I
170
Il est possible de tester rapidement linitialisation de notre test en vriant que le chargement
se fait correctement et en sassurant que le DAO a t inject. Il suft dajouter la mthode de
test suivante :
@Test
public void initOK() {
Assert.assertNotNull(propertyDAO);
}
Il est clair que cette mthode ne teste pas le DAO, mais elle a le mrite de nous renseigner sur
notre avancement.
Nous venons de voir comment Spring peut nous aider dans linitialisation des tests dintgra-
tion. Cest dans ces tests que le conteneur Spring prend toute son ampleur, car il nous permet
dlaborer trs nement notre conguration de test.
Lapport de Spring ne sarrte pas l : avec seulement quelques annotations, le contexte
Spring est automatiquement charg (et mis en cache), et le test peut se voir injecter les dpen-
dances dont il a besoin. Ltape suivante consiste tester proprement dit le DAO, mais cela
ncessite des manipulations avec la base de donnes et cest l quintervient DbUnit.
Utilisation de DbUnit avec Spring
DbUnit est une extension de JUnit facilitant les tests effectuant des oprations en base de
donnes. DbUnit supporte la plupart des bases de donnes. Ce framework est disponible
ladresse http://dbunit.sourceforge.net/.
DbUnit permet de mettre dans un tat connu une base de donnes avant deffectuer un test.
Ce raisonnement plutt simple est en effet le meilleur moyen dviter les problmes dinitiali-
sation et de corruption des donnes lors de lexcution de tests sur une base.
DbUnit sera dautant plus facile intgrer dans une infrastructure de tests si une approche
agile est adopte. Voici donc un ensemble de bonnes pratiques pour les tests mettant en jeu
une base de donnes :
Une instance de base de donnes par dveloppeur. Chaque dveloppeur doit disposer de sa
propre instance de base de donnes. Lide est que chaque dveloppeur peut effectuer ses
tests sans perturber les autres. Linstance peut se trouver sur la machine du dveloppeur. En
effet, la plupart des bases gratuites sont simples installer et peu consommatrices en
ressources. La plupart des bases de donnes payantes proposent une dition pour le dve-
loppement (Oracle propose par exemple Oracle Express Edition). Il est aussi possible de
crer autant dinstances de tests quil y a de dveloppeurs sur un serveur central.
Utiliser des jeux de donnes minimalistes. Les donnes doivent seulement permettre de
tester toutes les ventualits, pas de mesurer la rapidit dexcution. Il est donc prfrable
de remplir les tables avec au maximum quelques dizaines denregistrements, et ce an que
les tests sexcutent le plus rapidement possible. Loptimisation des requtes (jointures,
index) doit plutt tre faite avec de vritables donnes, bien que la question des perfor-
mances doive toujours tre lesprit des dveloppeurs !
Spring Livre Page 170 Lundi, 15. juin 2009 5:57 17
Test des applications Spring
CHAPITRE 6
171
Tous les tests doivent tre indpendants. Il ne faut pas quun test dpende des donnes dun
autre test. Chaque test doit donc avoir son propre jeu de donnes, et aucune autre phase
dinitialisation des donnes ne doit tre ncessaire.
Au cur de DbUnit se trouve la notion de jeu de donnes (dataset, qui correspond linter-
face org.dbunit.dataset.IDataSet). DbUnit synchronise la base de donnes partir dimpl-
mentations de IDataSet. La manire la plus commune de crer un jeu de donnes est dcrire
un chier XML et de laisser DbUnit crer lobjet correspondant.
Prenons, par exemple, la table contenant les proprits de lapplication Tudu Lists. Les anno-
tations JPA poses sur lentit nous indiquent que la table sappelle property et que la
proprit key correspond au champ pkey. La proprit value est automatiquement mise en
correspondance avec la colonne de mme nom. Voici le chier XML correspondant :
<?xml version='1.0' encoding='UTF-8'?>
<dataset>
<property pkey="smtp.host" value="some.url.com" />
</dataset>
Ce format est appel plat (at) dans la nomenclature des IDataSet de DbUnit. On remar-
que quau nom de la table correspond le nom de la balise et quaux proprits correspondent
les attributs de la balise. Chaque balise property correspond un enregistrement dans la table
correspondante.
Si ce chier sappelle property.xml, le code suivant permet de charger ce chier sous forme
de IDataSet :
IDataSet dataSet = new FlatXmlDataSet(new File("property.xml"));
partir dun objet IDataSet, il est possible de compter le nombre denregistrements de
chacune des tables, de rcuprer les valeurs pour chaque colonne de chaque enregistrement
de chaque table, etc. Lopration qio nous intresse consiste mettre le contenu du jeu de
donnes dans notre base. Linsertion doit passer par une connexion JDBC ou un DataSource.
La plupart de nos composants utilisant un DataSource, nous optons pour cette solution.
Voici un exemple de code pour effectuer linsertion de notre jeu de donnes :
import javax.sql.DataSource;
import org.dbunit.database.DatabaseDataSourceConnection;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.xml.FlatXmlDataSet;
import org.dbunit.operation.DatabaseOperation;
(...)
IDataSet dataSet = new FlatXmlDataSet(
new File("property.xml")
);
DataSource dataSource;
Spring Livre Page 171 Lundi, 15. juin 2009 5:57 17
Les fondations de Spring
PARTIE I
172
// rcupration du DataSource
(...)
DatabaseDataSourceConnection datasourceConnection =
new DatabaseDataSourceConnection(
dataSource
);
DatabaseOperation.CLEAN_INSERT.execute(
datasourceConnection,
dataSet
);
Il faut dans un premier temps effectuer un ensemble dimports de packages, principalement de
DbUnit ().
Le repre reprend la cration du IDataSet. Nous initialisons ensuite le DataSource ().
Cette partie nest pas dtaille pour linstant (Spring nous permettra de combler cette lacune
par la suite). Il faut ensuite crer une connexion telle que lattend DbUnit, et ce partir du
DataSource. Cest ce qui est effectu au repre , en utilisant DatabaseDataSourceConnection,
qui fait partie de lAPI DbUnit.
Le repre constitue la partie la plus intressante de notre exemple. Cest l que seffectue
linjection du jeu de donnes dans la base de donnes. Pour cela, un appel statique sur
DatabaseOperation nous permet deffectuer une insertion propre (clean insert). Dans notre
cas, cela consistera vider la table property et la remplir avec les enregistrements dcrits
dans le chier XML. Lopration de clean insert est lopration dinjection de donnes par
dfaut effectue par DbUnit : elle vide toute table implique dans le jeu de donnes.
DbUnit propose une API trs riche et des fonctionnalits plus intressantes les unes que les
autres, mais nous avons vu l lessentiel qui nous intresse. Lide est maintenant deffectuer
cette injection de donnes juste avant nos mthodes de test. Le code prcdent a donc sa place
dans une mthode annote avec @Before an dtre excute avant chaque mthode de test.
Reprenons notre test unitaire o nous lavons laiss :
package tudu.domain.dao.jpa;
(...)
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={
"/tudu/domain/dao/jpa/test-context.xml",
"/tudu/conf/jpa-dao-context.xml"
})
public class PropertyDAOJpaTest {
@Autowired
private PropertyDAO propertyDAO;
@Autowired
private DataSource dataSource
Spring Livre Page 172 Lundi, 15. juin 2009 5:57 17
Test des applications Spring
CHAPITRE 6
173
@Before
public void setUp() {
IDataSet dataSet = new FlatXmlDataSet(
new File("property.xml")
);
DatabaseOperation.CLEAN_INSERT.execute(
new DatabaseDataSourceConnection(dataSource),
dataSet
);
}
@Test
public void getPropertyOK() {
Property prop = propertyDAO.getProperty("smtp.host");
Assert.assertNotNull(prop);
Assert.assertEquals("smtp.host",prop.getKey());
Assert.assertEquals("some.url.com",prop.getValue());
}
(...)
}
Cette mthode dinitialisation sintgre parfaitement avec le fonctionnement de nos tests
dintgration, rendant lutilisation de DbUnit trs facile. Lexemple montre aussi le test de la
mthode getProperty pour une cl existant en base. Malgr son aspect plutt simple, rendu
possible notamment par le support JUnit de Spring, ce test nous assure que le chier de con-
guration des DAO de Tudu Lists est correct et que la mthode getProperty fonctionne correc-
tement.
Il est aussi possible de tester un autre contrat de notre mthode, celui qui consiste dire
quelle renvoie un objet nul si la cl nexiste pas en base :
@Test
public void getPropertyNOK() {
Property prop = propertyDAO.getProperty("some.dummy.key");
Assert.assertNull(prop);
}
Ce test parat anodin, mais renvoyer une exception serait un comportement tout aussi possible.
Un quelconque changement de comportement sera donc immdiatement dtect grce ce
test.
Deux autres mthodes sont tester dans le DAO : saveProperty et updateProperty. DbUnit est
encore dune aide prcieuse pour sassurer du bon droulement des mthodes. Voici le test
pour la mthode saveProperty :
@Test
public void saveProperty() throws Exception {
Spring Livre Page 173 Lundi, 15. juin 2009 5:57 17
Les fondations de Spring
PARTIE I
174
Property toSave = new Property();
toSave.setKey("some.key");
toSave.setValue("some.value");
propertyDAO.saveProperty(toSave);
DatabaseDataSourceConnection dsConnection =
new DatabaseDataSourceConnection(
dataSource
);
IDataSet databaseDataSet = dsConnection.createDataSet();
ITable tablePersonne = databaseDataSet.getTable("property");
assertEquals(2,tablePersonne.getRowCount());
}
Le test commence par la cration de lobjet proprit persister (). Au repre , la
mthode tester est appele. Il faut ensuite, comme pour linjection du jeu de donnes,
travailler avec lAPI de DbUnit. Cela passe par la cration de la connexion DbUnit, via le
DataSource (inject dans le test par Spring), au repre . Lobjet de connexion permet de
crer un IDataSet partir du contenu de la base de donnes (). partir de ce IDataSet, il est
possible de rcuprer la table qui nous intresse () et de connatre le nombre denregistre-
ments dans cette table an deffectuer une vrication (). Notre jeu de donnes XML injec-
tant une seule ligne dans la table property, il faut quil y ait deux enregistrements aprs lappel
la mthode saveProperty. DbUnit pourrait nous permettre de pousser plus loin nos vrica-
tions en allant jusqu vrier les valeurs des colonnes pour chacun des enregistrements. Nous
nirons pas jusque-l, la vrication du nombre denregistrements dans la table tant suf-
sante.
Lensemble de ces vrications permet de tester efcacement notre DAO ; cependant, les
appels lAPI DbUnit sont un peu fastidieux. Tudu Lists contient donc une classe utilitaire
permettant de faire rapidement les oprations les plus courantes (injection dun jeu de
donnes, rcupration du nombre denregistrements dans une table, etc.). Cette classe
sappelle DbUnitHelper ; elle ncessite seulement un DataSource comme dpendance. Un Bean
dbUnitHelper est dclar dans le contexte Spring. Nous ne dtaillerons pas limplmentation
de cette classe, qui encapsule des appels basiques mais quelque peu verbeux lAPI DbUnit.
Cette classe peut aussi tre utilise dans la mthode setUp an deffectuer linjection du jeu de
donnes.
Voici maintenant lapparence de notre test unitaire :
(...)
public class PropertyDAOJpaTest {
@Autowired
private PropertyDAO propertyDAO;
@Autowired
private DbUnitHelper helper;
@Before
public void setUp() throws Exception {
Spring Livre Page 174 Lundi, 15. juin 2009 5:57 17
Test des applications Spring
CHAPITRE 6
175
IDataSet dataSet = new FlatXmlDataSet("property.xml");
helper.doCleanInsert(dataSet);
}
@Test
public void saveProperty() throws Exception {
Property toSave = new Property();
toSave.setKey("some.key");
toSave.setValue("some.value");
propertyDAO.saveProperty(toSave);
assertEquals(2,helper.getTable("property").getRowCount());
}
}
Lutilisation du DbUnitHelper rend le test unitaire un peu moins verbeux et moins tributaire de
DbUnit.
Le test de la mthode updateProperty est lui aussi trs simple :
@Test
public void updateProperty() throws Exception {
Property toUpdate = new Property();
toUpdate.setKey("smtp.host");
toUpdate.setValue("some.other.host");
propertyDAO.updateProperty(toUpdate);
ITable table = helper.getTable("property");
Assert.assertEquals(
"some.other.host",
table.getValue(0, "value").toString()
);
}
En rsum
Les tests dintgration utilisant JUnit sont facilits pour les applications qui utilisent Spring.
Le Spring TestContext Framework permet dajouter du comportement aux classes de tests
unitaires. Il gre alors le chargement du contexte Spring, sa mise en cache sil est partag par
plusieurs classes de tests et linjection de dpendances dans le test unitaire.
Toutes ces fonctionnalits permettent de recrer un contexte dexcution pour les tests
dintgration. Une division judicieuse des chiers Spring permet de rutiliser ceux-ci pour
diffrents environnements. Les tests des DAO de Tudu Lists travaillent ainsi sur une base
de donnes en mmoire, sans que le chier de dnition des DAO nait eu subir de modi-
cations.
La puissance du conteneur lger Spring permet dintgrer lgamment DbUnit. Il est alors trs
ais de mettre la base de donnes dans un tat adapt pour tous les tests et donc de tester de
faon able tout composant interagissant avec la base de donnes.
Spring Livre Page 175 Lundi, 15. juin 2009 5:57 17
Les fondations de Spring
PARTIE I
176
Ragir lexcution des tests
Le Spring TestContext Framework propose un systme dcouteur lexcution des classes
de tests. Cest par ce biais que linjection de dpendances dans un test est possible. Ce
mcanisme trs puissant permet dajouter facilement du comportement autour de
lexcution des tests. Limpact sur la classe de test unitaire est minime, et seul lajout
dune annotation paramtre correctement est ncessaire (pas dhritage ou dinterface
implmenter).
Voici la dnition de linterface dcouteur :
package org.springframework.test.context;
public interface TestExecutionListener {
void prepareTestInstance(TestContext testCtx) throws Exception;
void beforeTestMethod(TestContext testCtx) throws Exception;
void afterTestMethod(TestContext testCtx) throws Exception;
}
Les mthodes sont excutes suite la cration de lobjet de test puis avant et aprs chaque
mthode de test.
Chaque test dispose de sa propre pile dcouteurs. Il est possible de la paramtrer avec lanno-
tation org.springframework.test.context.TestExecutionListeners laquelle on passe un
tableau de TestExecutionListeners :
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@TestExecutionListeners({MyTestExecutionListener.class})
public class MyTest {
(...)
}
Lannotation @TestExecutionListeners ne peut videmment fonctionner que si les annotations
@RunWith et @ContextConfiguration sont positionnes. Si lannotation @TestExe-
cutionListeners nest pas positionne, lcouteur dinjection de dpendances (fourni par
Spring) est positionn par dfaut. Cette annotation nest donc utiliser que dans le cas o lon
souhaite positionner ces propres couteurs.
Ce principe dcouteur peut sembler bien complexe et surtout redondant compte tenu de
lexistence du systme dannotations JUnit ragissant lexcution du test (@Before et
@After). Son utilit est toutefois justie par sa plus grande rutilisabilit. Un couteur peut
encapsuler un code complexe qui alourdirait un test si ce code apparaissait directement dans
une mthode annote avec @Before ou @After. Une fois lcouteur cod, il peut tre ajout
simplement via lannotation @TestExecutionListeners, ce qui est trs peu intrusif.
Spring Livre Page 176 Lundi, 15. juin 2009 5:57 17
Test des applications Spring
CHAPITRE 6
177
Nous allons illustrer lcriture dun couteur par lamlioration de notre infrastructure de test
dintgration. Nous avons prcdemment effectu linjection dun jeu de donnes en base de
donnes dans une mthode annote avec @Before. Cela convenait parfaitement au test de notre
DAO. Mais imaginons que nous voulions tester dautres composants interagissant avec la base
de donnes. Le test de ces composants ncessiterait aussi davoir une base de donnes dans un
tat connu, et cela passerait bien videmment par DbUnit.
Ce besoin de rutilisabilit nous conduit penser que lcriture dun TestExecutionListener
serait tout fait adapte. Cet couteur serait positionn sur des tests qui prcisent un jeu de
donnes injecter. Nous pouvons, par exemple, dnir une interface que ces tests devraient
implmenter pour localiser le chier XML de ce jeu de donnes :
public interface DataSetLocator {
public String getDataSet();
}
Linterface DataSetLocator dnit (sous la forme dune chane de caractres) la localisation
du IDataSet. Nous aurions pu dnir une annotation ou adopter un fonctionnement par dfaut
(localiser le chier XML en fonction du nom de la classe de test), mais cette approche par
interface nous semble un bon compromis.
Nous pouvons dnir lcouteur effectuant linjection de la manire suivante :
(...)
public class CleanInsertTestExecutionListener
implements TestExecutionListener {
public void beforeTestMethod(TestContext ctx) throws Exception {
if(ctx.getTestInstance() instanceof DataSetLocator) {
DataSetLocator test = (DataSetLocator) ctx.getTestInstance();
ApplicationContext appCtx = ctx.getApplicationContext();
DbUnitHelper helper = null;
if(appCtx.containsBean("dbUnitHelper")) {
helper = (DbUnitHelper) appCtx.getBean("dbUnitHelper");
} else {
DataSource ds = (DataSource) appCtx.getBean("dataSource");
helper = new DbUnitHelper(ds);
}
IDataSet dataSet = helper.getDataSet(test.getDataSet());
helper.doCleanInsert(dataSet);
}
}
public void prepareTestInstance(TestContext ctx) throws Exception
{
// aucune opration
}
Spring Livre Page 177 Lundi, 15. juin 2009 5:57 17
Les fondations de Spring
PARTIE I
178
public void afterTestMethod(TestContext ctx) throws Exception {
// aucune opration
}
}
Linjection du jeu de donnes se fait avant chaque mthode de test : cest pourquoi la mthode
beforeTestMethod est implmente. Celle-ci vrie que le test unitaire implmente bien
linterface DataSetLocator an de pouvoir localiser le jeu de donnes (). Le code prcdant
le repre permet de disposer dun DbUnitHelper, soit en le rcuprant dans le contexte
Spring, soit en en crant un en utilisant le DataSource. Le jeu de donnes est ensuite cr
partir de linformation donne par le test (), puis inject dans la base de donnes ().
Le DbUnitHelper nous assiste bien dans toutes ces tches.
Les deux autres mthodes de linterface TestExecutionListener nayant pas dutilit dans
notre cas, elles ont une implmentation vide (, ).
Voici maintenant le test unitaire du DAO proprit :
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={
"/tudu/domain/dao/jpa/test-context.xml",
"/tudu/conf/jpa-dao-context.xml"
})
@TestExecutionListeners{
CleanInsertTestExecutionListener.class,
DependencyInjectionTestExecutionListener.class
})
public class PropertyDAOJpaTest implements DataSetLocator {
(...)
public String getDataSet() {
return "/tudu/domain/dao/dataset/property.xml";
}
}
La mthode setUp, effectuant linjection du jeu de donnes, nest plus ncessaire. Le test
unitaire dnit dans sa pile dcouteurs non seulement lcouteur effectuant linjection de
donnes (), mais aussi celui faisant linjection de dpendances (). Celui-ci est positionn
par dfaut en labsence de lannotation @TestExecutionListeners, mais il nest pas ajout
automatiquement si lon prcise sa propre pile.
Enn, le test unitaire implmentant linterface DataSetLocator dnit la mthode getDataSet,
qui doit retourner la localisation du jeu de donnes au format XML plat de DbUnit ().
Cette plonge dans les mcanismes du Spring TestContext Framework nous a permis dcrire
un composant facilement rutilisable pour linjection de donnes en base de donnes. Cette
approche est particulirement lgante pour intgrer des composants tels que DbUnit et ajouter
du comportement de faon compltement transversale des tests unitaires.
Spring Livre Page 178 Lundi, 15. juin 2009 5:57 17
Test des applications Spring
CHAPITRE 6
179
Conclusion
Ce chapitre a montr comment tester une application Spring avec JUnit. Du point de vue des
tests unitaires, le code fond sur Spring ne ncessite pas dextension particulire JUnit,
hormis lutilisation le cas chant dun framework spcique, comme EasyMock, pour simuler
les objets dont dpend le composant tester.
Spring fournit par ailleurs des simulacres prts lemploi pour certains composants techni-
ques de lAPI Java EE. Pour les tests unitaires, seul le code applicatif est utilis, sans laide du
conteneur lger de Spring. Linjection des dpendances est effectue manuellement dans les
tests.
Pour les tests dintgration, JUnit ncessite plusieurs extensions, car, dans ce cas, le conteneur
lger doit tre utilis. cette n, Spring fournit le TestContext Framework qui se charge de la
gestion du contexte des tests. Un utilitaire tel que DbUnit peut alors facilement tre intgr
pour tester, par exemple, les composants interagissant avec la base de donnes.
Spring Livre Page 179 Lundi, 15. juin 2009 5:57 17

Vous aimerez peut-être aussi