I. Introduction▲
La pratique des tests unitaires est l'un des principes des méthodes agiles et c'est avec JUnit4 et Spring2.5+ que les tests, en particulier ceux d'intégration, deviennent aisés. L'un des avantages des tests est d'avoir un retour (feedback) rapide et beaucoup moins cher sur les réglages à apporter au logiciel et ainsi d'anticiper les anomalies.
Le second avantage des tests (unitaires et d'intégration) est de limiter le nombre d'itérations (en phase recette/production) de mise en conformité du logiciel et, par conséquent, de réduire son coût total. Signalons qu'en phase de mise en recette/production les personnes impliquées sont de diverses compétences d'où le coût économique élevé d'une itération à ce stade !
Avant de rentrer dans le vif du sujet, rappelons un critère permettant de mesurer la suffisance des tests :
« L'investissement fait en tests doit être égal à celui passé sur le design. Et si le design répond facilement au changement alors les tests sont suffisants. »
Liste des prérequis :
- connaissance des applications Web dans le monde JEE (servlet, jsp…) ;
- connaissance sommaire de Spring, Spring MVC avec ses annotations ;
- connaissance sommaire de JUnit4.x avec ses annotations ;
- connaissance sommaire de la notion de transaction.
II. ÉTAPE 1. Création de l'application Web Spring MVC sous Maven2▲
Pour toute la suite, l'application Web exemple sera nommée «spring-mvc-webapp». C'est le nom de la servlet frontale dans le fichier web.xml.
Avant de revenir sur web.xml, nous allons créer ensemble, étape par étape, un projet sous Eclipse et Maven2 (sous Windows).
Avec la console Dos, se positionner dans le répertoire workspace d'Eclipse puis taper la commande :
$ mvn archetype:create -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-webapp -DgroupId=com.netapsys.springmvc -DartifactId=spring-mvc-webapp
Ce qui génère la structure du projet Maven dans le répertoire spring-mvc-webapp.
S'assurer enfin que le contenu du fichier généré pom.xml pointe sur la version java 1.5 ou plus : pour cela, vérifier dans le pom.xml les lignes suivantes :
2.
3.
4.
5.
<artifactid>
maven-compiler-plugin</artifactid>
<configuration>
<source>
1.5</source>
<target>
1.5</target>
</configuration>
Il reste à faire de ce projet Maven un projet Eclipse à l'aide de la commande :
$ mvn eclipse:eclipse
Pour finir, importer ce projet dans Eclipse.
Passons à la configuration des dépendances Maven pour SpringMVC :
Nous ajoutons au fichier pom.xml les dépendances nécessaires à SpingMVC en insérant entre les deux tags :
2.
<dependencies>
</dependencies>
les lignes suivantes :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
<dependency>
<groupid>
org.springframework</groupid>
<artifactid>
spring</artifactid>
<version>
2.5.6</version>
</dependency>
<dependency>
<groupid>
org.springframework</groupid>
<artifactid>
spring-web</artifactid>
<version>
2.5.6</version>
</dependency>
<dependency>
<groupid>
org.springframework</groupid>
<artifactid>
spring-webmvc</artifactid>
<version>
2.5.6</version>
</dependency>
<dependency>
<groupid>
org.springframework</groupid>
<artifactid>
spring-test</artifactid>
<version>
2.5.6</version>
</dependency>
Nous ajoutons les dépendances pour JUnit et log4j en insérant également les lignes suivantes :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
<dependency>
<groupid>
junit</groupid>
<artifactid>
junit</artifactid>
<version>
4.4</version>
<scope>
runtime</scope>
</dependency>
<dependency>
<groupid>
commons-logging</groupid>
<artifactid>
commons-logging</artifactid>
<version>
1.1</version>
</dependency>
<dependency>
<groupid>
log4j</groupid>
<artifactid>
log4j</artifactid>
<version>
1.2.14</version>
</dependency>
Revenons au web.xml dont voici le contenu (que le strict minimum utile à ce stade) :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
<web-app>
<display-name>
Spring MVC et JUnit4 </display-name>
<context-param>
<param-name>
contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring-mvc-webapp-servlet.xml,
classpath:/spring.xml
</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<!-- déclare la servlet frontale centrale -->
<servlet>
<servlet-name>
spring-mvc-webapp</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>
1</load-on-startup>
</servlet>
<!-- Toutes les requêtes se terminant par .html seront servies par la servlet principale -->
<servlet-mapping>
<servlet-name>
spring-mvc-webapp</servlet-name>
<url-pattern>
*.html</url-pattern>
</servlet-mapping>
</web-app>
Ci-après quelques brèves explications :
dans le fichier web.xml ci-dessus, la variable «contextConfigLocation» pointe, entre autres, vers le fichier « spring.xml » afin que le contexte de l'application Web charge aussi d'autres beans nécessaires.
Attention, en l'absence de cette indication, vous seriez en face d'exceptions difficiles à déchiffrer !
Nous poursuivons ci-dessous avec la classe controller de SpringMVC, qui elle est chargée de traiter les requêtes transmises par la servlet dispatcher, en réponse aux requêtes http du client de l'application Web.
Le bloc
<servlet>
...</servlet>
permet d'identifier la servlet frontale de Spring MVC chargée de répondre à toutes les requêtes (*.html) d'un client de l'application Web.
La section suivante détaille le fichier «spring-mvc-webapp-servlet.xml», nommé ainsi conformément à la convention.
III. ÉTAPE 2. Configuration de Spring MVC▲
Le fichier spring-mvc-webapp-servlet.xml doit contenir ces lignes :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns
=
"http://www.springframework.org/schema/beans"
xmlns
:
xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
xmlns
:
p
=
"http://www.springframework.org/schema/p"
xmlns
:
context
=
"http://www.springframework.org/schema/context"
xsi
:
schemaLocation
=
"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
default-autowire
=
"byName"
>
<
context
:
component-scan
base-package
=
"com.netapsys.fr.springmvc.web"
/>
<
context
:
annotation-config/>
<!--
- Les controllers de cette application fournissent une annotation @RequestMapping
- Qui peut être déclarée de deux manières différentes:
- Au niveau de la classe :
- par exemple @RequestMapping("/addVisit.html")
- Pour ce type de controllers on peut annoter les méthodes pour une requête Post ou Get,
- Au niveau de chaque méthode, différents exemples seront fournis.
-->
<bean
class
=
"org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"
/>
<!--
Ceci est le view resolver, il permet de définir la technologie de vue utilisée et comment
sélectionner une vue. Ici on prendra la solution la plus simple elle permet de mapper
le nom de la vue retournée avec la sélection d'une jsp.
Ex. si le nom de la vue retournée est "hello" alors on utilisera le fichier
WEB-INF/jsp/hello.jsp pour construire la vue.
-->
<bean
class
=
"org.springframework.web.servlet.view.InternalResourceViewResolver"
p
:
prefix
=
"/WEB-INF/jsp/"
p
:
suffix
=
".jsp"
/>
</beans>
À noter la présence au début de ce fichier de l'instruction default-autowire=« byName ». Celle-ci est systématiquement utilisée dans tous les fichiers de configuration de Spring, afin d'autoinjecter les beans de Spring en utilisant plutôt leur nom et non leur type (valeur par défaut).
Depuis la version 2.5, Spring a étendu l'annotation @Resource en introduisant l'annotation @Autowired pour augmenter le niveau de contrôle de l'autoinjection. L'annotation @Autowired devient active dès que le bean AutowiredAnnotationBeanPostProcessor est défini dans le fichier xml ou, encore plus simplement, dès que le namespace 'context' est introduit avec l'instruction <context:annotation-config/> comme illustré ci-dessus.
Signalons que l'emploi des namespaces (par exemple mlns:context) réduit énormément la verbosité de la configuration XML de Spring.
En dehors des commentaires et explications, ce fichier contient peu de lignes. Si le projet continue à grossir, ce fichier de configuration n'évolue que très peu. Spring va scanner les packages dans <context:component-scan>, à la recherche des beans à utiliser.
La dernière ligne, très bien commentée, définit la vue retournée en réponse à une requête http.
Classe du Controller de spring MVC
Le code de la classe multicontroller SpringMVC nommée ClientControllerSpringMVC.java est comme suit :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
package
com.netapsys.fr.springmvc.web;
import
org.apache.log4j.Logger;
import
org.springframework.beans.factory.annotation.Autowired;
import
org.springframework.stereotype.Controller;
import
org.springframework.ui.ModelMap;
import
org.springframework.validation.BindingResult;
import
org.springframework.web.bind.annotation.*;
import
org.springframework.web.bind.support.SessionStatus;
import
com.netapsys.fr.springmcv.entites.Client;
import
com.netapsys.fr.springmvc.service.MyService;
import
static
com.netapsys.fr.springmvc.tb.constants.Constants;
@Controller
(
"clientControllerSpring"
)
public
class
ClientControllerSpringMVC {
private
MyService myService;
@Autowired
public
void
setMyService
(
MyService myService) {
this
.myService =
myService;
}
@RequestMapping
(
value=
"/getClient.html"
,method =
RequestMethod.GET)
public
String getClient
(
@RequestParam
(
value=
ATTRIBUTE_NAME,required=
true
) String nom,
@RequestParam
(
value=
ATTRIBUTE_LASTNAME,required=
false
) String prenom,
ModelMap model) {
Client client=
myService.getClient
(
nom, prenom);
if
(
client!=
null
) {
model.addAttribute
(
"client"
,client);
return
SUCCESS;
}
else
{
model.addAttribute
(
"errorMsg"
, "Client '"
+
prenom+
" "
+
nom+
"' inexistant"
);
return
ECHEC;
}
}
@RequestMapping
(
value=
"/createClient.html"
,method =
RequestMethod.GET)
public
String createClient
(
@RequestParam
(
value=
ATTRIBUTE_NAME,required=
true
) String nom,
@RequestParam
(
value=
ATTRIBUTE_LASTNAME,required=
false
) String prenom,
ModelMap model) {
Client client=
myService.createClient
(
nom, prenom);
model.addAttribute
(
"client"
,client);
return
SUCCESS;
}
@RequestMapping
(
value=
"/updateClient.html"
,method =
RequestMethod.POST)
public
String updateClient
(
@ModelAttribute
(
"client"
) Client client,
BindingResult result, SessionStatus status) {
myService.updateClient
(
client.getCliId
(
), client.getCliNom
(
),
client.getCliPrenom
(
));
status.setComplete
(
);
return
null
;
}
}
Notez que la classe est annotée avec le stéréotype @Controller. Donc chacune de ses méthodes va être analysée par la servlet dispatcher pour traiter les requêtes (*.html) client selon la valeur de l'annotation @RequestMapping. Le reste des stéréotypes @repository ou @Service sont commentés dans le code.
Mises à part les annotations @RequestMapping et @RequestParam, le 'controller' ne fait qu'appeler les méthodes de la couche service détaillée à l'étape 4 ci-après.
Mais avant cela, l'étape qui suit revient sur le contenu du fichier spring.xml.
IV. ÉTAPE 3. Configuration Spring des beans des couches DAO et Service▲
Le fichier de configuration Spring nommé spring.xml (pas de convention ici !) sert à déclarer les beans métier qui seront consommés par l'application Web. Il indique les packages Java à scanner afin d'autoinjecter ces beans. Il est important de noter qu'il déclare une dataSource pour la couche DAO.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns
=
"http://www.springframework.org/schema/beans"
xmlns
:
xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
xmlns
:
p
=
"http://www.springframework.org/schema/p"
xmlns
:
context
=
"http://www.springframework.org/schema/context"
xmlns
:
tx
=
"http://www.springframework.org/schema/tx"
xmlns
:
aop
=
"http://www.springframework.org/schema/aop"
xsi
:
schemaLocation
=
"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/tx/spring-aop-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
default-autowire
=
"byName"
>
<
context
:
annotation-config/>
<
context
:
component-scan
base-package
=
"com.netapsys.fr.springmvc"
/>
<bean
id
=
"dataSource"
class
=
"org.apache.commons.dbcp.BasicDataSource"
>
<property
name
=
"driverClassName"
value
=
"com.mysql.jdbc.Driver"
>
</property>
<property
name
=
"url"
value
=
"jdbc:mysql://localhost:3306/test"
>
</property>
<property
name
=
"username"
value
=
"root"
></property>
<property
name
=
"password"
value
=
"root"
></property>
<!-- Pensez à modifier ici votre login mysql -->
</bean>
</beans>
Le fichier n'a pas besoin d'être commenté. La seule chose à adapter étant la définition du bean dataSource.
L'étape suivante présente les classes DAO qui font appel à la dataSource.
V. ÉTAPE 4. Classes DAO et Service▲
Couche DAO
Notez que cette couche utilise deux classes beans (POJO) d'entités (Client.java et Personne.java). Le code simple n'est pas montré ici.
Par contre, le code de l'interface IDao.java contient ces lignes :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
package
com.netapsys.fr.springmvc.dao;
import
com.netapsys.fr.springmcv.entites.Client;
public
interface
IDao {
boolean
isExistId
(
String id);
boolean
findByName
(
String nom);
Client getClient
(
String nom);
Client getClient
(
String nom,String prenom);
Client getClient
(
long
id);
Client createClient
(
String nom, String prenom) throws
SQLException;
Client updateClient
(
long
id,String nom, String prenom);
void
deleteClient
(
long
id);
}
Et l'implémentation de cette interface est faite dans la classe DaoImpl.java qui contient les lignes suivantes :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
package
com.netapsys.fr.springmvc.dao;
import
java.sql.*;
import
org.apache.log4j.Logger;
import
org.springframework.beans.factory.annotation.Autowired;
import
org.springframework.dao.DataAccessException;
import
org.springframework.jdbc.core.simple.*;
import
org.springframework.stereotype.Repository;
import
com.netapsys.fr.springmcv.entites.Client;
import
static
com.netapsys.fr.springmvc.tb.constants.Constants;
@Repository
/** marqueur Spring pour auto-translation d'exception**/
public
class
DaoImpl implements
IDao{
final
private
Logger logger =
Logger.getLogger
(
getClass
(
).getName
(
));
private
SimpleJdbcTemplate jt=
null
;
private
DataSource dataSource;
@Autowired
public
void
setDataSource
(
DataSource dataSource) {
//Init SimpleJdbcTemplate ici si une datasource
jt=
new
SimpleJdbcTemplate
(
dataSource);
}
public
Client getClient
(
String nom) {
return
getClient
(
nom,null
);
}
public
Client getClient
(
long
id) {
return
getClient
(
String.valueOf
(
id) );
}
public
Client getClient
(
String nom, String prenom) {
String sql=
SQL_REQUETE_CLIENT +
" WHERE upper(CLINOM)='"
+
nom.toUpperCase
(
)+
"'"
;
if
(
prenom!=
null
&
amp;&
amp; !
""
.equals
(
prenom))
sql+=
" AND upper(CLIPRENOM)='"
+
prenom.toUpperCase
(
)+
"'"
;
if
(!
findByName
(
nom) ) {
return
null
; //Si nom n existe pas renvoie null
}
ParameterizedRowMapper<
Client>
mapper=
new
ParameterizedRowMapper<
Client>(
){
public
Client mapRow
(
ResultSet rs,int
rowNm) throws
SQLException{
return
populateClient
(
rs);
}
}
;
Client client=
jt.queryForObject
(
sql, mapper);
return
client;
}
private
Client populateClient
(
final
ResultSet rs) throws
SQLException {
if
(
rs==
null
) return
null
;
Client client=
new
Client
(
);
client.setCliId (
rs.getLong
(
"cliId"
) );
client.setNom (
rs.getString
(
"cliNom"
) );
client.setPrenom (
rs.getString
(
"cliPrenom"
));
return
client;
}
public
Client createClient
(
String nom, String prenom) {
Client client=
new
Client
(
);
client.setNom
(
nom);
client.setPrenom
(
prenom);
try
{
jt.update
(
SQL_REQUETE_INSERT_CLIENT+
"'"
+
nom+
"' , '"
+
prenom+
"'"
+
")"
);
long
id=
jt.queryForLong
(
"select LAST_INSERT_ID()"
);
client.setCliId
(
id);
return
client;
}
catch
(
DataAccessException e){
logger.error
(
e.getMessage
(
));
return
null
;
}
}
public
boolean
isExistId
(
String id) {
final
String sql =
SQL_REQUETE_COUNT_CLIENT+
" WHERE cliId='"
+
id +
"'"
;
int
count =
jt.queryForInt
(
sql);
return
count >
0
? true
: false
;
}
public
boolean
findByName
(
String nom) {
return
findByName
(
nom,null
);
}
public
boolean
findByName
(
final
String nom,final
String prenom) {
String sql =
SQL_REQUETE_COUNT_CLIENT+
" WHERE UPPER(cliNom)='"
+
nom.toUpperCase
(
) +
"'"
;
if
(
prenom!=
null
&
amp;&
amp; !
""
.equals
(
prenom))
sql+=
" AND UPPER(CLIPRENOM)='"
+
prenom.toUpperCase
(
) +
"'"
;
int
count =
jt.queryForInt
(
sql);
return
count >
0
? true
: false
;
}
public
void
deleteClient
(
long
id) {
final
String sql=
SQL_DELETE_ALL_CLIENT+
" WHERE cliId='"
+
id +
"'"
;
jt.update
(
sql);
}
public
Client updateClient
(
long
id, String nom, String prenom) {
Client client=
new
Client
(
);
client.setCliId
(
id);
client.setCliNom
(
nom);
client.setCliPrenom
(
prenom);
final
String sql=
"update Client set clinom='"
+
nom+
"', cliprenom='"
+
prenom+
"' WHERE cliId='"
+
id+
"'"
;
return
client;
}
}
L'apport de Spring est manifeste. L'emploi de SimpleJdbcTemplate simplifie l'accès par jdbc. Le mappage d'un ResultSet vers l'entité Client est immédiat.
C'est du code court, propre et simple.
Couche service
La couche service contient, en dehors de son interface identique à celle de IDao, l'implémentation nommée MyService.java dont voici le code :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
package
com.netapsys.fr.springmvc.service;
import
org.springframework.beans.factory.annotation.Autowired;
import
org.springframework.stereotype.Service;
import
com.netapsys.fr.springmcv.entites.Client;
import
com.netapsys.fr.springmvc.dao.DaoImpl;
@Service
public
class
MyService implements
IService{
private
DaoImpl daoImpl;
@Autowired
public
void
setDaoImpl
(
DaoImpl daoImpl) {
this
.daoImpl =
daoImpl;
}
public
Client getClient
(
String nom, String prenom) {
return
daoImpl.getClient
(
nom, prenom);
}
public
boolean
isExistId
(
String id) {
return
daoImpl.isExistId
(
id);
}
public
boolean
findByName
(
String nom) {
return
daoImpl.findByName
(
nom);
}
public
Client getClient
(
String nom) {
return
daoImpl.getClient
(
nom);
}
public
Client createClient
(
String nom, String prenom) throws
SQLException {
return
daoImpl.createClient
(
nom, prenom);
}
public
void
deleteClient
(
long
id) {
daoImpl.deleteClient
(
id);
}
public
Client getClient
(
long
id) {
return
daoImpl.getClient
(
id);
}
public
Client updateClient
(
long
id, String nom, String prenom) {
return
daoImpl.updateClient
(
id, nom, prenom);
}
}
La couche service sert à appeler les méthodes de la couche DAO. Et c'est dans celle-ci que les aspects transactionnels, les logs et les mesures de temps d'exécution sont gérés. Les règles métier spécifiques doivent être également observées dans cette couche.
Observons enfin l'autoinjection de daoImpl via la méthode setter comme ailleurs dans tout le code.
VI. ÉTAPE 5. Fichiers jsp▲
Le fichier index.jsp redirige la requête vers l'url /createClient.html.
La requête /createClient.html est interceptée par la servlet frontale de SpringMVC, qui à son tour décide de l'action (méthode) à appeler dans le « controller » nommé « ClientControllerSpringMVC » et qui décide ensuite de la vue à rendre en réponse à cette requête.
Dans notre cas, c'est la méthode « public String createClient » de la classe « ClientControllerSpringMVC.java » qui sera appelée. Celle-ci en cas de succès renvoie la chaîne « success » stockée dans Constants.SUCCESS de la classe utilitaire Constants. C'est cette chaîne qui permet de traduire la vue gérant la présentation de la réponse, qui est dans ce cas success.jsp.
Voici donc les quelques lignes de index.jsp :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
<html>
<!-- index.jsp -->
<head>
<title>
Spring mvc sample</title>
</head>
<body>
<%
final
String urlAction=
"/createClient.html?"
;
final
String nom =
request.getParameter
(
"name"
);
final
String prenom =
request.getParameter
(
"lastName"
);
if
(
nom !=
null
&
amp;&
amp; !
""
.equals
(
nom))
{
response.sendRedirect
(
request.getContextPath
(
)
+
urlAction+
"name="
+
nom +
"&lastName"
+
prenom);
}
else
{
response.sendRedirect
(
request.getContextPath
(
)
+
urlAction +
"name=nom007&lastName=prenom007"
);
}
%>
</body>
</html>
Le fichier success.jsp :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
<%@
page
language
=
"java"
contentType
=
"text/html; charset=ISO-8859-1"
pageEncoding=
"ISO-8859-1"
%>
<%@
page
isELIgnored =
"false"
%>
<%@
taglib
prefix
=
"form"
uri
=
"http://www.springframework.org/tags/form"
%>
<html>
<!-- success.jsp -->
<head>
<meta http-equiv=
"Content-Type"
content=
"text/html; charset=ISO-8859-1"
>
<title>
Success:Spring MVC sample-</title>
</head>
<body>
<h2>
Edition de la fiche client créée </h2><br />
<%
final
String url=
request.getContextPath
(
)+
"/updateClient.html"
;
%>
<form
:
form
commandName=
"client"
action=
"
<%=
url %>
"
method=
"post"
>
<table>
<tr><td></td>
<td><form
:
hidden
path=
"cliId"
/></td>
</tr>
<tr><td>
Nom:</td>
<td><form
:
input
tabindex=
"1"
autocomplete=
"true"
path=
"cliNom"
/></td>
</tr>
<tr><td>
Prénom:</td>
<td><form
:
input
tabindex=
"2"
autocomplete=
"true"
path=
"cliPrenom"
/></td>
</tr>
<tr<td colspan=
"2"
>
<input type=
"submit"
value=
"Valider"
/>
</td>
</tr>
</table>
</form
:
form
>
</body>
</html>
Dans success.jsp, l'action du tag « <form:form » pointe sur /updateClient.html avec la méthode http « POST ». La méthode « public String updateClient » du « controller » renvoie constamment « null ». Ainsi, la vue utilisée dans ce cas est /WEB-INF/jsp/updateClient.jsp conformément aux déclarations du fichier spring-mvc-webapp-servlet.xml.
Le fichier updateClient.jsp contient ces lignes :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
<%@
page
language
=
"java"
contentType
=
"text/html; charset=ISO-8859-1"
pageEncoding=
"ISO-8859-1"
%>
<%@
page
isELIgnored =
"false"
%>
<%@
taglib
prefix
=
"form"
uri
=
"http://www.springframework.org/tags/form"
%>
<!
DOCTYPE html PUBLIC
"-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd"
>
<html>
<!-- updateClient.jsp -->
<head>
<meta http-equiv=
"Content-Type"
content=
"text/html; charset=ISO-8859-1"
>
<title>
Spring MVC sample</title>
</head>
<body>
<h2>
Page Modification client</h2></paragraph><paragraph>
<%
final
String path=
request.getContextPath
(
);
final
String urlAction=
path+
"/updateClient.html"
;
%>
<form
:
form
commandName=
"client"
action=
"
<%=
urlAction %>
"
method=
"post"
>
<table border=0>
<tr>
<td colspan=
"2"
><form
:
hidden
path=
"cliId"
id=
"id"
/></td>
</tr>
<tr>
<td>
Nom:</td>
<td><form
:
input
autocomplete=
"true"
path=
"cliNom"
/></td>
</tr>
<tr>
<td>
Prénom:</td>
<td><form
:
input
autocomplete=
"true"
path=
"cliPrenom"
/></td>
</tr>
<tr>
<td colspan=
"2"
>
<input type=
"submit"
value=
"Valider"
/>
</td>
</tr>
</table>
</form
:
form
>
</body>
</html>
VII. ÉTAPE 6. Tests de l'application Web▲
Pour tester l'application Web ainsi complétée, nous allons lancer une console Dos, puis se positionner dans le répertoire du projet et lancer la commande :
$ mvn jetty:run
S'assurer que la base Mysql nommée test (contenant une table client avec trois champs cliId, cliNom et cliPrenom) est en service.
Puis lancer le navigateur Web avec l'URL « http://localhost:8080/spring-mvc-webapp/index.jsp ».
VIII. ÉTAPE 7. Classe de test du controller SpringMVC▲
La classe JUnit4, nommée « ClientControllerSpringMVCTest.java », permet de tester le « controller » de SpringMVC. Elle est constituée des lignes suivantes :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
package
com.netapsys.tests.springmvc.web.tests;
import
org.apache.log4j.Logger;
import
org.junit.*;
import
org.springframework.beans.factory.annotation.Autowired;
import
org.springframework.test.context.ContextConfiguration;
import
org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import
org.springframework.ui.ModelMap;
import
static
com.netapsys.fr.springmvc.tb.constants.Constants;
import
com.netapsys.fr.springmvc.web.ClientControllerSpringMVC;
@ContextConfiguration
(
locations={
"classpath:/config/spring-mvc-webapp-tests.xml"
,"classpath:/config/spring-test.xml"
}
)
@RunWith
(
SpringJUnit4ClassRunner.class
)
public
class
ClientControllerSpringMVCTest {
final
String NAME2TEST=
"Agent007"
;
final
String LASTNAME2TEST=
"007"
;
private
static
ModelMap model;
protected
ClientControllerSpringMVC clientControllerSpring;
@Autowired
public
void
setClientControllerSpring
(
ClientControllerSpringMVC clientControllerSpring) {
this
.clientControllerSpring =
clientControllerSpring;
}
@BeforeClass
(
)
public
static
void
testAvantTout
(
){
model =
new
ModelMap
(
);
}
@AfterClass
(
)
public
static
void
apresTousLesTests
(
){
model.clear
(
);
}
@Before
public
void
initAvant
(
){
Assert.assertTrue
(
clientControllerSpring!=
null
)
}
@Test
public
void
testGetClient
(
) {
final
String str=
clientControllerSpring.getClient
(
NAME2TEST, LASTNAME2TEST, model);
Assert.assertequals
(
SUCCESS,str) ;
Assert.assertTrue
(
model.get
(
"client"
) !=
null
);
}
}
Les parties importantes pour la compréhension des lanceurs de spring sont bien documentées dans le code.
L'annotation @RunWith définit le lanceur spring qui enrichit considérablement les tests JUnit4 avec les fonctionnalités supplémentaires (ex. l'auto-injection) offertes par Spring.
Spring offre aussi des annotations inexistantes dans JUnit4 utiles à l'exécution de ces tests.
Signalons que @ContextConfiguration emploie deux fichiers de configuration de SpringMVC et de Spring spécifiques aux tests. Ces deux fichiers sont identiques, à une déclaration près, aux fichiers spring-mvc-webapp-servlet.xml et spring.xml explicités auparavant. La seule différence est l'ajout de la déclaration de la transaction dans spring-test.xml :
2.
3.
4.
<!-- transaction -->
<bean
id
=
"transactionManager"
class
=
"org.springframework.jdbc.datasource.DataSourceTransactionManager"
>
<property
name
=
"dataSource"
ref
=
"dataSource"
/>
</bean>
Ainsi, avec le bean « transactionManager » les tests en mode transactionnel ci-après deviennent possibles comme illustré à l'étape suivante.
IX. ÉTAPE 8. Classe de test en mode transactionnel▲
Voici le code de la classe MyServiceTest.java. Elle comporte toutes les indications pour exécuter les méthodes en mode transactionnel :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
package
com.netapsys.tests.springmvc.web.tests;
import
java.util.Random;
import
org.apache.log4j.Logger;
import
org.junit.*;
import
org.springframework.test.context.*;
import
org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import
org.springframework.transaction.annotation.*;
import
com.netapsys.fr.springmcv.entites.*;
import
com.netapsys.fr.springmvc.service.MyService;
@ContextConfiguration
(
locations={
"classpath:/config/spring-mvc-webapp-tests.xml"
,"classpath:/config/spring-test.xml"
}
)
@RunWith
(
value=
SpringJUnit4ClassRunner.class
) /**indispensable**/
@TransactionConfiguration
(
transactionManager=
"transactionManager"
,defaultRollback=
true
)
public
class
MyServiceTest {
private
MyService myService;
@Autowired
public
void
setMyService
(
MyService myService) {
this
.myService =
myService;
}
@Test
@Transactional
(
propagation=
Propagation.REQUIRED)
public
void
testCreateClient
(
) {
final
String nom =
giveRandomName
(
"Agent007test"
);
final
String prenom=
giveRandomName
(
"007test"
);
Client client=
null
;
client=
myService.createClient
(
nom, prenom);
Assert.assertTrue
(
client!=
null
);
}
private
String giveRandomName
(
final
String prefix) {
final
Random random=
new
Random
(
);
return
prefix+
random.nextInt
(
100
);
}
}
Pour tester, lancer la commande « mvn test ».
Vous pouvez modifier le paramètre defaultRollback à false afin d'insérer des clients dans la base.
X. ÉTAPE 9. Conclusion▲
Combiner Spring 2.5+ et JUnit4 permet d'avoir sous la main un framework de test puissant facilitant la mise en place des tests unitaires et d'intégration.
Bien que l'apprentissage exige un léger effort, une fois ces deux frameworks maîtrisés, l'efficacité et le gain économique sont énormes, et la qualité du livrable ne sera que meilleure.
Observons, en particulier, dans les classes de tests en mode transactionnel, le confort qu'apporte Spring à JUnit4. Les annotations Spring @TransactionConfiguration et @Transactional rendent les transactions à la portée de tous.
Enfin, mes sincères remerciements vont à mlny84, Jacques Jean et Ricky81 pour leur relecture.