Tests transactionnels JUnit4 combiné avec Spring et SpringMVC

| Abderrazek CHINE | Netapsys |

Ce billet a pour objectif d'illustrer, à l'aide d'un exemple assez complet et proche de cas réels, la mise en place des tests transactionnels pour l'ensemble des couches applicatives.

Il aborde JUnit4 enrichi avec les annotations de Spring 2.5+ et ses lanceurs pour exécuter facilement les tests. Des illustrations en mode transactionnel vous sont proposées à la fin de ce billet. Le framework JUnit est l'œuvre conjointe de Kent Beck (créateur de XP) et Erich Gamma (auteur des Design Patterns). Avec la version 4, JUnit tente de rattraper son retard sur TestNG tout en gardant la compatibilité avec JUnit3x ainsi qu'une parfaite intégration aux éditeurs Eclipse, Netbeans,…

Avec les lanceurs de Spring, les tests deviennent plus attrayants et facilitent l'introduction des transactions par une simple annotation.

Spring encourage ainsi à adopter l'approche TDD « Test Driven Design ».

L'utilisation de Spring avec JUnit4 n'exige, en aucun cas, de maîtriser Spring.

Dans l'exemple détaillé plus loin, les annotations de Spring 2.5 allègent considérablement la configuration XML.

La présentation de ce tutoriel est divisée en trois axes :

- créer une application Web JEE sous Eclipse/Maven2, SpringMVC et ses annotations, en particulier @Controller et @Autowired ;

- mettre en place, via le framework JUnit4.x, des tests unitaires et des tests d'intégration couvrant l'ensemble des couches applicatives (DAO, Service et Web) ;

- exécuter les tests en mode transactionnel.

Ces trois axes sont divisés ci-dessous en neuf étapes.

Pour réagir au contenu de cet article, un espace de dialogue vous est proposé sur le forum Commentez .

- Titre : Tests transactionnels JUnit4 combiné avec Spring et SpringMVC

- Auteurs : Abderrazek CHINE, Netapsys

- Parution : 9 juin 2010

- Mise à jour : 1 juin 2020

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2010 Abderrazek CHINE. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. ÉTAPE 1. Création de l'application Web Spring MVC sous Maven2

- III. ÉTAPE 2. Configuration de Spring MVC

- IV. ÉTAPE 3. Configuration Spring des beans des couches DAO et Service

- V. ÉTAPE 4. Classes DAO et Service

- VI. ÉTAPE 5. Fichiers jsp

- VII. ÉTAPE 6. Tests de l'application Web

- VIII. ÉTAPE 7. Classe de test du controller SpringMVC

- IX. ÉTAPE 8. Classe de test en mode transactionnel

- X. ÉTAPE 9. Conclusion

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 :

projet maven

$ 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 :

pom.xml

1. <artifactid>maven-compiler-plugin</artifactid>

2. <configuration>

3. <source>1.5</source>

4. <target>1.5</target>

5. </configuration>

Il reste à faire de ce projet Maven un projet Eclipse à l'aide de la commande :

projet eclipse:classpath

$ 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 :

1. <dependencies>

2. </dependencies>

les lignes suivantes :

pom.xml

1. <dependency>

2. <groupid>org.springframework</groupid>

3. <artifactid>spring</artifactid>

4. <version>2.5.6</version>

5. </dependency>

6. <dependency>

7. <groupid>org.springframework</groupid>

8. <artifactid>spring-web</artifactid>

9. <version>2.5.6</version>

10. </dependency>

11. <dependency>

12. <groupid>org.springframework</groupid>

13. <artifactid>spring-webmvc</artifactid>

14. <version>2.5.6</version>

15. </dependency>

16. <dependency>

17. <groupid>org.springframework</groupid>

18. <artifactid>spring-test</artifactid>

19. <version>2.5.6</version>

20. </dependency>

Nous ajoutons les dépendances pour JUnit et log4j en insérant également les lignes suivantes :

pom.xml

1. <dependency>

2. <groupid>junit</groupid>

3. <artifactid>junit</artifactid>

4. <version>4.4</version>

5. <scope>runtime</scope>

6. </dependency>

7. <dependency>

8. <groupid>commons-logging</groupid>

9. <artifactid>commons-logging</artifactid>

10. <version>1.1</version>

11. </dependency>

12. <dependency>

13. <groupid>log4j</groupid>

14. <artifactid>log4j</artifactid>

15. <version>1.2.14</version>

16. </dependency>

Revenons au web.xml dont voici le contenu (que le strict minimum utile à ce stade) :

web.xml

1. <web-app>

2. <display-name>Spring MVC et JUnit4 </display-name>

3. <context-param>

4. <param-name>contextConfigLocation</param-name>

5. <param-value>

6. /WEB-INF/spring-mvc-webapp-servlet.xml,

7. classpath:/spring.xml

8. </param-value>

9. </context-param>

10. <listener>

11. <listener-class>

12. org.springframework.web.context.ContextLoaderListener

13. </listener-class>

14. </listener>

15. <!-- déclare la servlet frontale centrale  -->

16. <servlet>

17. <servlet-name>spring-mvc-webapp</servlet-name>

18. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

19. <load-on-startup>1</load-on-startup>

20. </servlet>

21. <!--    Toutes les requêtes se terminant par .html seront servies par la servlet principale    -->

22. <servlet-mapping>

23. <servlet-name>spring-mvc-webapp</servlet-name>

24. <url-pattern>*.html</url-pattern>

25. </servlet-mapping>

26. </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

1. <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 :

spring-mvc-webapp-servlet.xml

1. <?xml version="1.0" encoding="UTF-8"?>

2. <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

3. xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context"

4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd

5. http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"

6. default-autowire="byName">

7. <context:component-scan base-package="com.netapsys.fr.springmvc.web"/>

8. <context:annotation-config/>

9. <!--

10. - Les controllers de cette application fournissent une annotation @RequestMapping

11. - Qui peut être déclarée de deux manières différentes:

12. -  Au niveau de la classe :

13. -      par exemple @RequestMapping("/addVisit.html")

14. -      Pour ce type de controllers on peut annoter les méthodes pour une requête Post ou Get,

15. - Au niveau de chaque méthode, différents exemples seront fournis.

16. -->

17. <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>

18. <!--

19. Ceci est le view resolver, il permet de définir la technologie de vue utilisée et comment

20. sélectionner une vue. Ici on prendra la solution la plus simple elle permet de mapper

21. le nom de la vue retournée avec la sélection d'une jsp.

22. Ex. si le nom de la vue retournée est "hello" alors on utilisera le fichier

23. WEB-INF/jsp/hello.jsp pour construire la vue.

24. -->

25. <bean

26. class="org.springframework.web.servlet.view.InternalResourceViewResolver"

27. p:prefix="/WEB-INF/jsp/" p:suffix=".jsp"/>

28. </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 :

ClientControllerSpringMVC.java

1. package com.netapsys.fr.springmvc.web;

2. import org.apache.log4j.Logger;

3. import org.springframework.beans.factory.annotation.Autowired;

4. import org.springframework.stereotype.Controller;

5. import org.springframework.ui.ModelMap;

6. import org.springframework.validation.BindingResult;

7. import org.springframework.web.bind.annotation.*;

8. import org.springframework.web.bind.support.SessionStatus;

9. import com.netapsys.fr.springmcv.entites.Client;

10. import com.netapsys.fr.springmvc.service.MyService;

11. import static com.netapsys.fr.springmvc.tb.constants.Constants;

12. @Controller("clientControllerSpring")

13. public class ClientControllerSpringMVC {

15. private MyService myService;

17. @Autowired

18. public void setMyService(MyService myService) {

19. this.myService = myService;

20. }

22. @RequestMapping(value="/getClient.html",method = RequestMethod.GET)

23. public  String getClient(

24. @RequestParam(value=ATTRIBUTE_NAME,required=true) String nom,

25. @RequestParam(value=ATTRIBUTE_LASTNAME,required=false) String prenom,

26. ModelMap model) {

28. Client client=myService.getClient(nom, prenom);

30. if(client!=null )  {

31. model.addAttribute("client",client);

32. return SUCCESS;

33. }else {

34. model.addAttribute("errorMsg", "Client '"+prenom+" "+nom+"' inexistant");

35. return ECHEC;

36. }

37. }

39. @RequestMapping(value="/createClient.html",method = RequestMethod.GET)

40. public String createClient(

41. @RequestParam(value=ATTRIBUTE_NAME,required=true) String nom,

42. @RequestParam(value=ATTRIBUTE_LASTNAME,required=false) String prenom,

43. ModelMap model) {

45. Client client=myService.createClient( nom, prenom);

46. model.addAttribute("client",client);

47. return SUCCESS;

48. }

50. @RequestMapping(value="/updateClient.html",method = RequestMethod.POST)

51. public String updateClient(@ModelAttribute("client") Client client,

52. BindingResult result, SessionStatus status) {

54. myService.updateClient(    client.getCliId(), client.getCliNom(),

55. client.getCliPrenom());

56. status.setComplete();

57. return null;

58. }

59. }

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.

spring.xml

1. <?xml version="1.0" encoding="UTF-8"?>

2. <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

3. xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context"

4. xmlns:tx="http://www.springframework.org/schema/tx"

5. xmlns:aop="http://www.springframework.org/schema/aop"

6. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd

7. http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd

8. http://www.springframework.org/schema/aop http://www.springframework.org/schema/tx/spring-aop-2.5.xsd

9. http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"

10. default-autowire="byName">

11. <context:annotation-config/>

12. <context:component-scan base-package="com.netapsys.fr.springmvc"/>

13. <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">

14. <property name="driverClassName"    value="com.mysql.jdbc.Driver">

15. </property>

16. <property name="url" value="jdbc:mysql://localhost:3306/test">

17. </property>

18. <property name="username" value="root"></property>

19. <property name="password" value="root"></property> <!-- Pensez à modifier ici votre login mysql -->

20. </bean>

21. </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 :

IDao.java

1. package com.netapsys.fr.springmvc.dao;

2. import com.netapsys.fr.springmcv.entites.Client;

3. public interface IDao  {

4. boolean isExistId(String id);

5. boolean findByName(String nom);

6. Client getClient(String nom);

7. Client getClient(String nom,String prenom);

8. Client getClient(long id);

9. Client createClient(String nom, String prenom) throws SQLException;

10. Client updateClient(long id,String nom, String prenom);

11. void deleteClient(long id);

12. }

Et l'implémentation de cette interface est faite dans la classe DaoImpl.java qui contient les lignes suivantes :

DaoImpl.java

1. package com.netapsys.fr.springmvc.dao;

2. import java.sql.*;

3. import org.apache.log4j.Logger;

4. import org.springframework.beans.factory.annotation.Autowired;

5. import org.springframework.dao.DataAccessException;

6. import org.springframework.jdbc.core.simple.*;

7. import org.springframework.stereotype.Repository;

8. import com.netapsys.fr.springmcv.entites.Client;

9. import static com.netapsys.fr.springmvc.tb.constants.Constants;

11. @Repository /** marqueur Spring pour auto-translation d'exception**/

12. public class DaoImpl implements IDao{

14. final private Logger logger = Logger.getLogger(getClass().getName());

16. private SimpleJdbcTemplate jt=null;

17. private DataSource dataSource;

18. @Autowired

19. public void setDataSource(DataSource dataSource) {

20. //Init SimpleJdbcTemplate ici si une datasource

21. jt=new SimpleJdbcTemplate(dataSource);

22. }

24. public Client getClient(String nom) {

25. return getClient(nom,null);

26. }

28. public Client getClient(long id) {

29. return getClient( String.valueOf(id)  );

30. }

32. public Client getClient(String nom, String prenom) {

33. String sql=SQL_REQUETE_CLIENT + "  WHERE upper(CLINOM)='"+nom.toUpperCase()+"'";

35. if(prenom!=null &amp;&amp; !"".equals(prenom))

36. sql+=" AND upper(CLIPRENOM)='"+prenom.toUpperCase()+"'";

38. if(!findByName(nom) ) {

39. return null;    //Si nom n existe pas renvoie null

40. }

42. ParameterizedRowMapper<Client>  mapper=new ParameterizedRowMapper<Client>(){

43. public Client mapRow(ResultSet rs,int rowNm) throws SQLException{

44. return populateClient(rs);

45. }

46. };

48. Client client=jt.queryForObject(sql, mapper);

50. return client;

51. }

52. private Client populateClient(final ResultSet rs)    throws SQLException {

53. if(rs==null) return null;

54. Client client=new Client();

55. client.setCliId (  rs.getLong("cliId")      );

56. client.setNom   (  rs.getString("cliNom")   );

57. client.setPrenom ( rs.getString("cliPrenom"));

58. return client;

59. }

61. public Client createClient( String nom, String prenom) {

62. Client client=new Client();

63. client.setNom(nom);

64. client.setPrenom(prenom);

65. try{

66. jt.update(SQL_REQUETE_INSERT_CLIENT+ "'"+nom+ "' , '"+prenom+"'" +")" );

67. long id=jt.queryForLong("select LAST_INSERT_ID()");

68. client.setCliId(id);

69. return client;

70. }catch(DataAccessException e){

71. logger.error( e.getMessage());

72. return null;

73. }

74. }

76. public boolean isExistId(String id) {

77. final String sql = SQL_REQUETE_COUNT_CLIENT+" WHERE cliId='" +id + "'";

78. int count = jt.queryForInt(sql);

79. return count > 0 ? true : false;

80. }

82. public boolean findByName(String nom) {

83. return findByName(nom,null);

84. }

86. public boolean findByName(final String nom,final String prenom) {

88. String sql = SQL_REQUETE_COUNT_CLIENT+

89. " WHERE UPPER(cliNom)='" +nom.toUpperCase() + "'";

91. if(prenom!=null &amp;&amp; !"".equals(prenom))

92. sql+=" AND UPPER(CLIPRENOM)='"+prenom.toUpperCase() + "'";

94. int count = jt.queryForInt(sql);

96. return count > 0 ? true : false;

97. }

98. public void deleteClient(long id) {

100. final String sql= SQL_DELETE_ALL_CLIENT+" WHERE cliId='"+ id + "'";

101. jt.update(sql);

103. }

104. public Client updateClient(long id, String nom, String prenom) {

105. Client client=new Client();

106. client.setCliId(id);

107. client.setCliNom(nom);

108. client.setCliPrenom(prenom);

110. final String sql="update Client set clinom='"+

111. nom+"', cliprenom='"+prenom+"' WHERE cliId='"+id+"'";

113. return client;

114. }

115. }

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 :

MyService.java

1. package com.netapsys.fr.springmvc.service;

2. import org.springframework.beans.factory.annotation.Autowired;

3. import org.springframework.stereotype.Service;

4. import com.netapsys.fr.springmcv.entites.Client;

5. import com.netapsys.fr.springmvc.dao.DaoImpl;

7. @Service

8. public class MyService implements IService{

10. private DaoImpl daoImpl;

12. @Autowired

13. public void setDaoImpl(DaoImpl daoImpl) {

14. this.daoImpl = daoImpl;

15. }

17. public Client getClient(String nom, String prenom) {

18. return daoImpl.getClient(nom, prenom);

19. }

21. public boolean isExistId(String id) {

22. return daoImpl.isExistId(id);

23. }

25. public boolean findByName(String nom) {

26. return daoImpl.findByName(nom);

27. }

29. public Client getClient(String nom) {

30. return daoImpl.getClient(nom);

31. }

33. public Client createClient( String nom, String prenom) throws SQLException {

34. return daoImpl.createClient( nom, prenom);

35. }

37. public void deleteClient(long id) {

38. daoImpl.deleteClient(id);

39. }

41. public Client getClient(long id) {

42. return daoImpl.getClient(id);

43. }

45. public Client updateClient(long id, String nom, String prenom) {

46. return daoImpl.updateClient(id, nom, prenom);

47. }

48. }

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 :

index.jsp

1. <html>

2. <!-- index.jsp  -->

3. <head>

4. <title>Spring mvc sample</title>

5. </head>

6. <body>

7. <%

9. final String urlAction="/createClient.html?";

10. final String nom = request.getParameter("name");

11. final String prenom = request.getParameter("lastName");

13. if (nom != null &amp;&amp; !"".equals(nom))

14. {

15. response.sendRedirect(request.getContextPath()

16. + urlAction+"name=" + nom + "&amp;lastName"

17. + prenom);

18. }

19. else

20. {

21. response.sendRedirect( request.getContextPath()

22. + urlAction +

23. "name=nom007&amp;lastName=prenom007");

24. }

25. %>

26. </body>

27. </html>

Le fichier success.jsp :

success.jsp

1. <%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>

2. <%@ page isELIgnored ="false" %>

3. <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

4. <html><!-- success.jsp -->

5. <head>

6. <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">

7. <title>Success:Spring MVC sample-</title>

8. </head>

9. <body>

10. <h2>Edition de la fiche client créée </h2><br />

11. <%

12. final String url=request.getContextPath()+"/updateClient.html";

13. %>

14. <form:form commandName="client" action="<%=url %>" method="post">

15. <table>

16. <tr><td></td>

17. <td><form:hidden  path="cliId" /></td>

18. </tr>

19. <tr><td>Nom:</td>

20. <td><form:input tabindex="1" autocomplete="true"  path="cliNom" /></td>

21. </tr>

22. <tr><td>Prénom:</td>

23. <td><form:input tabindex="2" autocomplete="true"  path="cliPrenom" /></td>

24. </tr>

25. <tr<td colspan="2">

26. <input type="submit" value="Valider" />

27. </td>

28. </tr>

29. </table>

30. </form:form>

31. </body>

32. </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 :

updateClient.jsp

1. <%@ page language="java" contentType="text/html; charset=ISO-8859-1"  pageEncoding="ISO-8859-1"%>

2. <%@ page isELIgnored ="false" %>

3. <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

4. <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

5. <html><!-- updateClient.jsp -->

6. <head>

7. <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">

8. <title>Spring MVC sample</title>

9. </head>

10. <body>

12. <h2>Page Modification client</h2></paragraph><paragraph>

13. <%

15. final String path=request.getContextPath();

16. final String urlAction=path+"/updateClient.html";

18. %>

20. <form:form commandName="client" action="<%=urlAction %>" method="post">

21. <table border=0>

22. <tr>

23. <td colspan="2"><form:hidden  path="cliId" id="id"/></td>

24. </tr>

25. <tr>

26. <td>Nom:</td>

27. <td><form:input  autocomplete="true"  path="cliNom" /></td>

28. </tr>

29. <tr>

30. <td>Prénom:</td>

31. <td><form:input  autocomplete="true"  path="cliPrenom" /></td>

32. </tr>

33. <tr>

34. <td colspan="2">

35. <input type="submit" value="Valider" />

36. </td>

37. </tr>

38. </table>

39. </form:form>

40. </body>

41. </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 :

ClientControllerSpringMVCTest.java

1. package com.netapsys.tests.springmvc.web.tests;

2. import org.apache.log4j.Logger;

3. import org.junit.*;

4. import org.springframework.beans.factory.annotation.Autowired;

5. import org.springframework.test.context.ContextConfiguration;

6. import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

7. import org.springframework.ui.ModelMap;

8. import static com.netapsys.fr.springmvc.tb.constants.Constants;

9. import com.netapsys.fr.springmvc.web.ClientControllerSpringMVC;

11. @ContextConfiguration(locations={"classpath:/config/spring-mvc-webapp-tests.xml","classpath:/config/spring-test.xml"})

12. @RunWith( SpringJUnit4ClassRunner.class)

13. public class ClientControllerSpringMVCTest  {

15. final String NAME2TEST="Agent007";

17. final String LASTNAME2TEST="007";

19. private static ModelMap model;

21. protected  ClientControllerSpringMVC clientControllerSpring;

23. @Autowired

24. public void setClientControllerSpring(

25. ClientControllerSpringMVC clientControllerSpring) {

26. this.clientControllerSpring = clientControllerSpring;

27. }

29. @BeforeClass()

30. public static void testAvantTout(){

31. model = new ModelMap();

32. }

34. @AfterClass()

35. public static void apresTousLesTests(){

36. model.clear();

37. }

39. @Before public void initAvant(){

40. Assert.assertTrue(clientControllerSpring!=null)

41. }

43. @Test

44. public void testGetClient() {

45. final String str=clientControllerSpring.getClient(

46. NAME2TEST, LASTNAME2TEST, model);

47. Assert.assertequals(SUCCESS,str)    ;

48. Assert.assertTrue(model.get("client") !=null );

49. }

50. }

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 :

DataSourceTransactionManager

1. <!-- transaction -->

2. <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

3. <property name="dataSource" ref="dataSource"/>

4. </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 :

MyServiceTest.java

1. package com.netapsys.tests.springmvc.web.tests;

2. import java.util.Random;

3. import org.apache.log4j.Logger;

4. import org.junit.*;

5. import org.springframework.test.context.*;

6. import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

7. import org.springframework.transaction.annotation.*;

8. import com.netapsys.fr.springmcv.entites.*;

9. import com.netapsys.fr.springmvc.service.MyService;

11. @ContextConfiguration(locations={"classpath:/config/spring-mvc-webapp-tests.xml","classpath:/config/spring-test.xml"})

12. @RunWith( value=SpringJUnit4ClassRunner.class ) /**indispensable**/

13. @TransactionConfiguration(transactionManager="transactionManager",defaultRollback=true)

15. public class MyServiceTest  {

17. private  MyService myService;

19. @Autowired

20. public void setMyService(MyService myService) {

21. this.myService = myService;

22. }

24. @Test @Transactional (propagation=Propagation.REQUIRED)

25. public void testCreateClient() {

26. final String nom =giveRandomName("Agent007test");

27. final String prenom=giveRandomName("007test");

28. Client client=null;

29. client=myService.createClient( nom, prenom);

30. Assert.assertTrue(client!=null);

32. }

34. private String giveRandomName(final String prefix) {

35. final Random random=new Random();

36. return prefix+random.nextInt(100);

37. }

38. }

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.