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-webappCe 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:eclipsePour 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 && !"".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 && !"".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 && !"".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:runS'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.




