IV. Chapitre 3 - Tests fonctionnels▲
Dans ce chapitre, notre travail sera, d'une part de produire et livrer l'application sur un environnement d'intégration et, d'autre part d'exécuter les tests fonctionnels (appelés également tests utilisateur ou tests de cas d'utilisation).
IV-A. Préparation du livrable▲
La préparation d'un livrable consiste à empaqueter l'application afin qu'elle puisse être installée dans un environnement cible (intégration, recette, etc.). Cela peut être un exécutable Windows ou Unix, un fichier jar, ear ou war. Dans notre cas, nous allons modifier notre projet pour produire un livrable de type application web (war).
IV-A-1. Mise à jour du pom▲
Nous allons modifier les propriétés suivantes.
- Le packaging sera de type war
- Le nom final lors du build sera mynotes (cela évite d'avoir la version accolée au nom de l'application genre mynotes-1.0-SNAPSHOT).
- Les paramètres de connexion à la base de données seront configurables via l'utilisation d'un profile maven : soit développement (par défaut), soit intégration.
<project
xmlns
=
"http://maven.apache.org/POM/4.0.0"
xmlns
:
xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
xsi
:
schemaLocation
=
"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"
>
<modelVersion>
4.0.0</modelVersion>
<groupId>
com.developpez.mynotes</groupId>
<artifactId>
mynotes</artifactId>
<version>
1.0-SNAPSHOT</version>
<packaging>
war</packaging>
...
<name>
mynotes</name>
<properties>
<!-- Database connection parameters for dev -->
<database.driver>
org.hsqldb.jdbcDriver</database.driver>
<database.url>
jdbc:hsqldb:mem:mynotes</database.url>
<database.username>
sa</database.username>
<database.password></database.password>
</properties>
<profiles>
<profile>
<id>
integration</id>
<activation>
<property>
<name>
environment</name>
<value>
integration</value>
</property>
</activation>
<properties>
<!-- Database connection parameters for integration -->
<database.driver>
org.hsqldb.jdbcDriver</database.driver>
<database.url>
jdbc:hsqldb:hsql://localhost/mynotes</database.url>
<database.username>
sa</database.username>
<database.password></database.password>
</properties>
</profile>
</profiles>
<build>
<finalName>
mynotes</finalName>
...
</build>
...
</project>
Afin que le fichier database.properties final puisse être généré par Maven, il devra être placé dans le répertoire src/main/resources-filtered :
driver
=
${database.driver}
url
=
${database.url}
username
=
${database.username}
password
=
${database.password}
Dans le profile développement nous obtiendrons :
driver
=
org.hsqldb.jdbcDriver
url
=
jdbc:hsqldb:mem:mynotes
username
=
sa
password
=
Dans le profile integration, nous obtiendrons :
driver
=
org.hsqldb.jdbcDriver
url
=
jdbc:hsqldb:hsql://localhost/mynotes
username
=
sa
password
=
IV-A-2. Écriture du fichier web.xml▲
L'application a besoin de fournir un descripteur au container de servlet afin que ce dernier puisse l'installer et la démarrer. Ce descripteur s'exprime sous la forme d'un fichier xml standardisé (web.xml) et selon la norme Maven, il devra être placé dans le répertoire src/main/webapp/WEB-INF.
Dans le fichier web.xml, nous déclarerons notre classe Wicket MyNotesApplication et nous configurerons le contexte Spring :
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<display-name>
mynotes</display-name>
<servlet>
<servlet-name>
mynotes</servlet-name>
<servlet-class>
org.apache.wicket.protocol.http.WicketServlet</servlet-class>
<init-param>
<param-name>
applicationClassName</param-name>
<param-value>
com.developpez.mynotes.web.MyNotesApplication</param-value>
</init-param>
<load-on-startup>
1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>
mynotes</servlet-name>
<url-pattern>
/*</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>
-1</session-timeout>
</session-config>
<context-param>
<param-name>
contextConfigLocation</param-name>
<param-value>
classpath:com/developpez/mynotes/dao/applicationContext-dao.xml
classpath:com/developpez/mynotes/business/applicationContext-business.xml
</param-value>
</context-param>
<context-param>
<param-name>
log4jConfigLocation</param-name>
<param-value>
classpath:log4j.properties</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
Enfin, pour que notre servlet Wicket puisse utiliser Spring, il faut surcharger la méthode init de MyNotesApplication :
public
class
MyNotesApplication extends
WebApplication {
...
@Override
protected
void
init
(
) {
super
.init
(
);
addComponentInstantiationListener
(
new
SpringComponentInjector
(
this
));
}
}
Comme nous avons modifié notre code, il faut que nous relancions les tests unitaires de la couche web :
Nous constatons que le test testRender de HomePageTest ne passe plus. Effectivement, nous instancions MyNotesApplication sans spécifier un contexte Spring. Il y a 2 solutions pour y remédier :
La première est la plus simple et la plus immédiate : il suffit de surcharger la méthode init lors de l'instanciation de MyNotesApplication afin de l'empêcher de charger le contexte Spring (le corps de la méthode sera vide) :
public
class
HomePageTest {
...
@Before
public
void
setUp
(
) throws
Exception {
MyNotesApplication webApplication =
new
MyNotesApplication
(
) {
@Override
protected
void
init
(
) {
//surcharge pour ne pas charger le contexte Spring
}
}
;
tester =
new
WicketTester
(
webApplication);
}
}
L'inconvénient est que cela n'est pas flexible. Si jamais nous devions ajouter des dépendances de services qui seraient exécutés lors du chargement de HomePage, nous serions obligés de préciser un contexte Spring dans notre classe de test.
La deuxième solution consiste à extraire une classe de test abstraite qui serait héritée par HomePageTest et LoggedInPage. Cela permet d'homogénéiser l'écriture et d'éviter la redondance de code. Nous allons partir de la classe LoggedInPageTest puisqu'elle est la plus riche des deux.
- WicketTester est présent dans les deux classes, cette fixture sera remontée.
- La méthode setUp sera remontée, mais seulement la partie qui initialise le mock du contexte Spring. Ce dernier sera accessible dans les classes concrètes (visibilité protected) si jamais nous devions l'utiliser ou le modifier pour un besoin spécifique.
- L'initialisation des mocks de service étant spécifique à chaque test, nous écrirons une méthode doSetUp qui sera surchargée dans les classes concrètes de test.
Il en résulte des classes suivantes :
public
abstract
class
AbstractWicketTestCase {
protected
WicketTester tester;
protected
ApplicationContextMock applicationContextMock;
@Before
public
final
void
setUp
(
) throws
Exception {
applicationContextMock =
new
ApplicationContextMock
(
);
doSetUp
(
);
MyNotesApplication webApp =
new
MyNotesApplication
(
) {
@Override
public
void
init
(
) {
addComponentInstantiationListener
(
new
SpringComponentInjector
(
this
, applicationContextMock));
}
}
;
tester =
new
WicketTester
(
webApp);
}
protected
abstract
void
doSetUp
(
) throws
Exception;
}
public
class
HomePageTest extends
AbstractWicketTestCase {
@Test
public
void
testRender
(
) throws
Exception {
...
}
@Override
protected
void
doSetUp
(
) throws
Exception {
SecurityService securityServiceMock =
Mockito.mock
(
SecurityService.class
);
applicationContextMock.putBean
(
"securityService"
, securityServiceMock);
}
}
public
class
LoggedInPageTest extends
AbstractWicketTestCase {
//méthodes de tests
...
@Override
protected
void
doSetUp
(
) throws
Exception {
SecurityService securityServiceMock =
Mockito.mock
(
SecurityService.class
);
Mockito.stub
(
securityServiceMock.authenticate
(
"JohnDoe"
, "seven"
))
.toReturn
(
UserFactory.buildUser
(
"JohnDoe"
, "John"
, "Doe"
, "johndoe@abc.com"
, "seven"
, false
));
applicationContextMock.putBean
(
"securityService"
, securityServiceMock);
}
}
Le point positif de cette écriture est que cela allège la lecture du test. Le point négatif est que l'on doit initialiser nos mocks et les mettre dans le contexte Spring dans chaque classe de test alors que si nous étions passés par des mocks "fait maison", nous aurions simplement écrit une configuration Spring (voir solution 1 du mock dans la couche Service - chapitre 2).
IV-B. Configurer l'environnement d'intégration▲
L'environnement d'intégration représente l'environnement complet sur lequel les tests fonctionnels seront exécutés. L'application sera donc testée de bout en bout, impliquant l'interaction entre l'action utilisateur sur le client (navigateur dans notre cas), toutes les couches de l'application et la base de données (pas de mock). Nous allons configurer cet environnement en effectuant les tâches suivantes :
- Installation et configuration du serveur de base de données
- Installation et configuration du container de servlet
- Pilotage du cycle d'exécution du serveur de Bdd et du container de servlet par Maven
IV-B-1. Installation et configuration du serveur de base de données▲
Nous allons installer et configurer Hsqldb en mode serveur autonome. Nous mettrons la distribution jar et les fichiers de commandes adéquats dans un répertoire de notre environnement d'intégration (par exemple c:\dev\bdd\hsqldb). Dans notre tutoriel, notre machine de développement fait office de machine d'intégration.
echo "Running HSQLDB server"
java -cp hsqldb.jar org.hsqldb.Server -database.0
file:mynotes -dbname.0
mynotes
echo "Stopping HSQLDB server"
java -cp hsqldb.jar org.hsqldb.util.SqlTool --sql "shutdown;" localhost-sa
Le script stopDBServer.cmd utilise un identifiant de connexion à la base de données pour pouvoir interagir avec le serveur (localhost-sa). Cet identifiant est défini dans un fichier d'authentification (sqltool.rc) qui sera lu par la classe org.hsqldb.util.SqlTool. Par ailleurs, il devra être déposé dans le répertoire utilisateur (exemple : C:\Documents and Settings\USER):
# This is for a hsqldb Server running with default settings on your local
urlid localhost-sa
url jdbc:hsqldb:hsql://localhost/mynotes
username sa
password
Ces deux fichiers seront exécutés à la main. Dans un cas standard, la base de données d'intégration est active 24h/24 afin que l'on puisse construire à tout moment l'application (principe de l'Intégration Continue).
IV-B-2. Installation et configuration du container de servlet▲
Dans l'idéal, le container Jetty devrait être installé sur la machine d'intégration (comme Hsqldb) et les applications web déployées sur celle-ci. Pour les besoins de notre tutoriel, nous allons simplifier la gestion de ce serveur en déléguant le contrôle de son cycle d'exécution par Maven. Jetty démarrera lorsque le profile sera integration et lors de la phase pre-integration-test. Ensuite, il pointera sur le war généré par Maven et l'accès se fera via le port classique 8080. Enfin, l'arrêt du serveur sera effectué lors de la phase post-integration-test.
<build>
<plugins>
<plugin>
<groupId>
org.mortbay.jetty</groupId>
<artifactId>
maven-jetty-plugin</artifactId>
<version>
6.1.11</version>
<configuration>
<webApp>
${basedir}/target/mynotes.war</webApp>
<contextPath>
/mynotes</contextPath>
<port>
8080</port>
<stopKey>
arret</stopKey>
<stopPort>
9090</stopPort>
</configuration>
<executions>
<execution>
<id>
start-jetty</id>
<phase>
pre-integration-test</phase>
<goals>
<goal>
run-war</goal>
</goals>
<configuration>
<scanIntervalSeconds>
0</scanIntervalSeconds>
<daemon>
true</daemon>
</configuration>
</execution>
<execution>
<id>
stop-jetty</id>
<phase>
post-integration-test</phase>
<goals>
<goal>
stop</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
Dans la configuration du plugin jetty, nous avons mis les balises <stopKey> et <stopPort>. Ces deux balises sont obligatoires pour que le plugin puisse lancer le goal stop lors de la phase post-integration-test
IV-B-3. Initialisation du schéma de la base de données▲
Cette opération s'effectuera à l'aide du plugin maven-antrun-plugin. Nous allons configurer la tache <java/> pour qu'il exécute la classe org.hsqldb.util.SqlTool avec comme argument l'identifiant de la base (cf fichier d'authentification mynotes.rc) et le fichier d'initialisation du schéma mynotes.ddl. Le déclenchement sera effectué lors de la phase compile afin que les tests unitaires de DAO puissent être exécutés sur la base de données d'intégration.
<build>
<plugins>
...
<plugin>
<artifactId>
maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>
init-db</id>
<phase>
compile</phase>
<configuration>
<tasks>
<java
classname
=
"org.hsqldb.util.SqlTool"
classpathref
=
"maven.runtime.classpath"
>
<arg
value
=
"localhost-sa"
/>
<arg
value
=
"${project.build.directory}/test-classes/mynotes.ddl"
/>
</java>
</tasks>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
Le démarrage et l'arrêt du serveur de la base de données ont été omis du cycle pour des raisons technique et pratique.
- Technique, car le lancement de hsqldb doit se faire dans un processus séparé (au sens système). S'il faisait partie d'une phase Maven, il serait tué lors de l'exécution de la phase suivante ce qui serait plutôt gênant pour l'exécution des tests.
- Pratique, car en plus de la remarque évoquée à la fin de la section II-A, ce serveur de base de données peut très bien accueillir d'autres projets. De plus, arrêter puis couper le serveur après chaque build serait très coûteux en temps surtout si l'on décidait de passer à MySql, Oracle ou autre SGBD.
IV-C. Écriture et exécution du premier test fonctionnel▲
La phase d'écriture du test fonctionnel (en Anglais functional test ou release test) est cruciale pour valider le besoin à implémenter. Il devra couvrir l'ensemble des cas (séquençage des actions, contrôle des règles de gestion) décrits dans la spécification fonctionnelle. Selon le principe de la méthodologie XP, les tests fonctionnels doivent représenter la(les) spécification(s) fonctionnelle(s) du projet et c'est au client (MOA) de l'écrire avec le binôme de développeurs.
IV-C-1. Introduction de Canoo Webtest▲
La librairie WebTest permet d'écrire des tests web automatisés dans lesquels des actions utilisateur seront décrites. Ils peuvent être écrits soit en utilisant soit une syntaxe XML, soit Groovy. Par exemple, l'utilisateur veut aller sur la page principale de Google. Il écrira le code suivant :
<invoke
url
=
"http://www.google.com"
description
=
"Aller sur la page de Google"
/>
<verifyTitle
text
=
"Google"
description
=
"Verification du titre de la page de google"
/>
ou bien
invoke "http://www.google.com"
, description: "Aller sur la page de Google"
verifyTitle "Google"
description: "Verification du titre de la page de google"
Nous voyons que la syntaxe tente de se rapprocher du langage naturel, c'est pourquoi cette plateforme de test semble assez bien convenir pour l'écriture et la lecture de test par une personne non développeur.
Concernant le mode opératoire, Webtest se basant sur Ant, l'exécution s'effectue en arrière-plan (pas de navigateur affiché) ce qui permet de gagner énormément de temps sur l'exécution d'une suite de tests. À chaque nouveau rendu de page, une capture d'écran est créée et le résultat du test est décrit sous la forme d'un fichier XML qui pourra ultérieurement être interprété pour générer des états.
Quant à l'intégration de ce framework dans le projet, il peut être intégré dans le projet soit de manière externe via une distribution complète, soit par un artefact Maven 2.
IV-C-2. Test de connexion utilisateur▲
Nous allons créer un package webtest/usecase sous le répertoire test/resources dans lequel nous écrirons notre premier test. Il contiendra 3 étapes : insertion des données de test, séquence d'actions de l'utilisateur et assertion des données de la base de données à la fin.
La syntaxe utilisée sera la version XML et nous décrirons les étapes suivantes :
- Accès à l'application via l'url http://localhost:8080/mynotes.
- Saisie des paramètres de connexion et validation du formulaire
- Asserter que le message d'accueil est affiché.
La balise verifyText possède une limitation. Lorsqu'elle asserte le texte, les balises html sont également prises en compte alors qu'idéalement elle devrait asserter ce qui est affiché sur la page.
<verifyText
text
=
"Bonjour John Doe et bienvenue dans l'application MyNotes."
/>
<!-- Ne marche pas, car Canoo trouve "Bonjour <span wicket:id="user">John Doe</span> et bienvenue dans l'application MyNotes." -->
Du coup nous sommes obligés de scinder l'assertion en 3.
<?xml version="1.0"?>
<project
default
=
"test"
>
<target
name
=
"test"
>
<webtest
name
=
"Test connexion into MyNotes"
>
<group
description
=
"Login"
>
<invoke
url
=
"http://localhost:8080/mynotes"
description
=
"Go to MyNotes"
/>
<setInputField
name
=
"userId"
value
=
"JohnDoe"
/>
<setInputField
name
=
"password"
value
=
"seven"
/>
<clickButton
label
=
"Connexion"
/>
</group>
<group
description
=
"Verify welcome text"
>
<verifyText
text
=
"Bonjour"
/>
<verifyText
text
=
"John Doe"
/>
<verifyText
text
=
"et bienvenue dans l'application MyNotes."
/>
</group>
</webtest>
</target>
</project>
Nous allons ajouter l'étape d'insertion des données en base en utilisant la tâche Ant de DBUnit.
<?xml version="1.0"?>
<project
default
=
"test"
>
<target
name
=
"test"
>
<dbunit
driver
=
"org.hsqldb.jdbcDriver"
url
=
"jdbc:hsqldb:hsql://localhost/mynotes"
userid
=
"sa"
password
=
""
datatypeFactory
=
"org.dbunit.ext.hsqldb.HsqldbDataTypeFactory"
>
<operation
type
=
"CLEAN_INSERT"
src
=
"usecase/UtilisateurNormalInputDataTest.xml"
format
=
"xml"
/>
</dbunit>
<webtest ...>
</project>
Le fichier UtilisateurNormalInputDataTest.xml sera similaire à celui du test unitaire de SecurityDao. Il nous reste l'assertion des données de la base de données après la séquence des actions de l'utilisateur.
<?xml version="1.0"?>
<project
default
=
"test"
>
<target
name
=
"test"
>
<dbunit ...>
<webtest ...>
<dbunit
driver
=
"org.hsqldb.jdbcDriver"
url
=
"jdbc:hsqldb:hsql://localhost/mynotes"
userid
=
"sa"
password
=
""
datatypeFactory
=
"org.dbunit.ext.hsqldb.HsqldbDataTypeFactory"
>
<compare
src
=
"usecase/UtilisateurNormalOutputDataTest.xml"
format
=
"xml"
/>
</dbunit>
</project>
Par rapport aux actions de l'utilisateur, les données de la base ne doivent pas changer. Le fichier DBUnit sera identique à UtilisateurNormalInputDataTest.xml. Cela peut sembler superflu au premier abord, mais il aura l'occasion d'évoluer dans les prochains scénarii. Notre premier test étant terminé, il ne reste plus qu'à configurer Maven pour l'exécuter.
IV-C-3. Pilotage de l'exécution de tests fonctionnels par Maven▲
Nous rappelons les étapes suivantes qui seront exécutées par Maven lors du lancement de la construction du build avec le profile integration :
- Initialisation du schéma de la base de données au préalable
- Construction du war incluant l'exécution des tests unitaires
- Lancement du container de servlet Jetty qui démarrera l'application
- Exécution des tests fonctionnels
- Arrêt du container de servlet Jetty
Comme Canoo se base sur Ant, nous allons réutiliser le plugin maven-antrun-plugin pour lancer notre test lors de la phase integration-test. La dépendance vers Canoo sera ajoutée dans la configuration du plugin. Il faut noter que la distribution Canoo Maven n'est pas présente dans les dépôts publics Maven. Comme pour iBatis, il faut la télécharger depuis le site officiel et l'installer dans le dépôt local (Attention au numéro de version de l'artefact).
<plugin>
<artifactId>
maven-antrun-plugin</artifactId>
<executions>
...
<execution>
<id>
run-webtest</id>
<phase>
integration-test</phase>
<configuration/>
<goals>
<goal>
run</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>
com.canoo.webtest</groupId>
<artifactId>
webtest</artifactId>
<version>
R_1689</version>
<exclusions>
<exclusion>
<groupId>
javax.xml</groupId>
<artifactId>
jsr173</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</plugin>
Le module jsr173 a été exclu de la dépendance, car il est obsolète depuis qu'il a été intégré dans la plateforme JDK5.
Ensuite, nous allons définir une propriété Ant runtime_classpath qui pointera vers le classpath de Maven en scope runtime. Enfin nous invoquerons la balise <ant> afin d'exécuter le fichier de lancement des tests allTests.xml qui contiendra notre test. Les attributs inheritAll et inheritRefs de <ant> permettront d'indiquer à Ant que tous les sous-fichiers Ant appelés par allTests.xml accèderont aux propriétés définies dans la configuration.
<plugin>
<artifactId>
maven-antrun-plugin</artifactId>
<executions>
...
<execution>
<id>
run-webtest</id>
<phase>
integration-test</phase>
<configuration>
<property
name
=
"runtime_classpath"
refid
=
"maven.runtime.classpath"
/>
<property
name
=
"webtest.tests"
value
=
"${project.build.directory}/test-classes/webtest"
/>
<ant
antfile
=
"${webtest.tests}/allTests.xml"
inheritRefs
=
"true"
dir
=
"${webtest.tests}"
inheritAll
=
"true"
/>
</configuration>
...
Il nous reste à écrire le fichier allTests.xml. Par défaut, Canoo ne reconnaît pas les balises Ant de DBUnit, aussi, il faut déclarer la tâche dans notre fichier (d'où l'utilité de notre propriété runtime_classpath). Par ailleurs pour ceux qui ont des problèmes avec les proxys installés sur leur réseau (entreprise ou particulier), il faut créer une petite configuration avec SetProxy. Nous pouvons également surcharger certaines propriétés de Canoo. Dans notre cas, nous voulons que le build stoppe dès la première erreur d'exécution (Exception levée par exemple) et dès le premier test en échec.
<project
default
=
"runTests"
>
<taskdef
resource
=
"webtest_base_relaxed.taskdef"
>
<classpath
path
=
"${runtime_classpath}"
/>
</taskdef>
<taskdef
name
=
"setproxy"
classname
=
"org.apache.tools.ant.taskdefs.optional.net.SetProxy"
classpath
=
"${runtime_classpath}"
/>
<taskdef
name
=
"dbunit"
classname
=
"org.dbunit.ant.DbUnitTask"
classpath
=
"${runtime_classpath}"
/>
<setproxy
nonproxyhosts
=
"localhost"
proxyhost
=
"myproxy"
proxyport
=
"80"
/>
<property
name
=
"wt.config.haltonerror"
value
=
"true"
/>
<property
name
=
"wt.config.haltonfailure"
value
=
"true"
/>
<property
name
=
"wt.config.resultpath"
value
=
"../webtest-results"
/>
<target
name
=
"runTests"
description
=
"runs all the tests"
>
<ant
antfile
=
"usecase/UtilisateurNormalTest.xml"
/>
</target>
</project>
Exécutons notre build avec la commande mvn clean install -Denvironment=integration (ne pas oublier de lancer le script startDBServer.cmd avant)
Nous pouvons voir que webtest a bien exécuté notre test fonctionnel.
Canoo "capture" les pages HTML lorsqu'une nouvelle page est rendue. Aussi, en jetant un coup d'oeil, nous nous rendons compte, que d'une part c'est atrocement moche, et que d'autre part il y a des soucis d'encodage de caractères (le lien Déconnexion est mal affiché). Nous y reviendrons dans le scénario suivant.
Vérifions que notre build échoue bien en modifiant notre test :
...
<group
description
=
"Verify welcome text"
>
<verifyText
text
=
"Bonjour"
/>
<verifyText
text
=
"Robert"
/>
<verifyText
text
=
"et bienvenue dans l'application MyNotes."
/>
</group>
...
En ouvrant le rapport d'exécution, nous constatons que l'assertion a échoué.
<failure
message
=
"Step[verifyText (2/2)]: Text not found in page. Expected <Robert>"
>
IV-C-4. Génération du rapport au format html▲
Dans la distribution de Webtest, la génération de rapport de test au format HTML est proposée afin que l'on puisse mieux exploiter les données. Afin de conserver la simplicité d'installation et configuration de notre projet, nous allons seulement "récupérer" les fichiers utiles à la génération et l'inclure dans notre répertoire webtest/report/resources.
Ensuite nous allons récupérer les taches Ant liées à la génération HTML du fichier webtest.xml de la distribution et les adapter à nos besoins. 2 propriétés seront modifiées : wt.config.resultpath et ~wt.WebTest.dir. Elles pointeront vers des variables définies dans notre pom.xml. Par ailleurs il faudra définir la tâche groovy utilisée pour générer le fichier récapitulant les résultats des tests (en sortant ces cibles Ant de leur contexte d'exécution d'origine, certaines tâches ne seront pas reconnues par défaut lors du lancement de la génération du rapport).
<?xml version="1.0" encoding="ISO-8859-1"?>
<project
name
=
"wt.webtest"
default
=
"wt.htmlReports"
>
<target
name
=
"wt.init"
description
=
"Initialisation work"
>
<property
name
=
"wt.config.resultpath"
value
=
"${webtest.results}"
/>
<dirname
property
=
"~wt.WebTest.dir"
file
=
"${webtest.report}/generateReport.xml"
description
=
"the directory of the WebTest distribution used"
/>
<taskdef
name
=
"groovyScript"
classname
=
"org.codehaus.groovy.ant.Groovy"
loaderref
=
"wt.defineTasks.loader"
classpath
=
"${runtime_classpath}"
/>
...
</target>
<target
name
=
"wt.htmlReports.init"
...>
<target
name
=
"wt.htmlReports"
depends
=
"wt.init, wt.htmlReports.init"
...>
</project
Il ne reste plus qu'à définir les valeurs de webtest.results et webtest.report ainsi que l'exécution de generateReport.xml dans notre pom.
<plugin>
<artifactId>
maven-antrun-plugin</artifactId>
<executions>
...
<execution>
<id>
run-webtest</id>
<phase>
integration-test</phase>
<configuration>
<tasks>
...
<property
name
=
"webtest.tests"
value
=
"${project.build.directory}/test-classes/webtest"
/>
<property
name
=
"webtest.report"
value
=
"${webtest.tests}/report"
/>
<property
name
=
"webtest.results"
value
=
"${project.build.directory}/test-classes/webtest-results"
/>
<ant
antfile
=
"${webtest.tests}/allTests.xml"
inheritRefs
=
"true"
dir
=
"${webtest.tests}"
inheritAll
=
"true"
/>
<ant
antfile
=
"${webtest.tests}/report/generateReport.xml"
inheritRefs
=
"true"
dir
=
"${webtest.tests}"
inheritAll
=
"true"
/>
...
Réexécutons notre commande maven :
Nous observons que la cible Ant wt.htmlReport de génération a été exécutée. Il ne reste plus qu'à ouvrir la page index.html sous mynotes\target\test-classes\webtest-results.
IV-C-5. Remaniement du test fonctionnel▲
Afin d'améliorer la lisibilité du test et de préparer le terrain pour les prochains tests, nous allons remanier quelques éléments. Nous allons créer un répertoire includes dans lequel nous allons déclarer nos tâches DBUnit d'insertion et d'assertion de données. Les noms de fichiers seront des variables positionnées dans le test et les paramètres de connexion à la base de données seront remontés dans le pom.xml.
<plugin>
<artifactId>
maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>
run-webtest</id>
<phase>
integration-test</phase>
<configuration>
<tasks>
<property
name
=
"runtime_classpath"
refid
=
"maven.runtime.classpath"
/>
<property
name
=
"sql.jdbcdriver"
value
=
"${database.driver}"
/>
<property
name
=
"sql.url"
value
=
"${database.url}"
/>
<property
name
=
"sql.username"
value
=
"${database.username}"
/>
<property
name
=
"sql.password"
value
=
"${database.password}"
/>
<property
name
=
"sql.datatypefactory"
value
=
"org.dbunit.ext.hsqldb.HsqldbDataTypeFactory"
/>
...
<dbunit
driver
=
"${sql.jdbcdriver}"
url
=
"${sql.url}"
userid
=
"${sql.username}"
password
=
"${sql.password}"
datatypeFactory
=
"${sql.datatypefactory}"
>
<compare
src
=
"${outputDataFile}"
format
=
"xml"
/>
</dbunit>
<dbunit
driver
=
"${sql.jdbcdriver}"
url
=
"${sql.url}"
userid
=
"${sql.username}"
password
=
"${sql.password}"
datatypeFactory
=
"${sql.datatypefactory}"
>
<operation
type
=
"CLEAN_INSERT"
src
=
"${inputDataFile}"
format
=
"xml"
/>
</dbunit>
Dans le fichier de test UtilisateurNormalTest.xml, nous déclarerons en entête les fragments AssertData et InsertData. Puis nous préciserons les valeurs des propriétés outputDataFile et inputDataFile.
<?xml version="1.0"?>
<!DOCTYPE project [
<!ENTITY insertData__xml
SYSTEM
"../includes/InsertData.xml"
>
<!ENTITY assertData__xml
SYSTEM
"../includes/AssertData.xml"
>
]>
<project
default
=
"test"
>
<property
name
=
"inputDataFile"
value
=
"usecase/UtilisateurNormalInputDataTest.xml"
/>
<property
name
=
"outputDataFile"
value
=
"usecase/UtilisateurNormalOutputDataTest.xml"
/>
<target
name
=
"test"
>
&insertData__xml;
<webtest
name
=
"Test connexion into MyNotes"
>
...
</webtest>
&assertData__xml;
</target>
</project>
Ensuite nous créerons un répertoire config dans lequel nous pourrons déclarer les balises non reconnues par défaut par Ant et modifier certaines propriétés générales de Canoo.
<taskdef
resource
=
"webtest_base_relaxed.taskdef"
>
<classpath
path
=
"${runtime_classpath}"
/>
</taskdef>
<taskdef
name
=
"setproxy"
classname
=
"org.apache.tools.ant.taskdefs.optional.net.SetProxy"
classpath
=
"${runtime_classpath}"
/>
<taskdef
name
=
"dbunit"
classname
=
"org.dbunit.ant.DbUnitTask"
classpath
=
"${runtime_classpath}"
/>
<setproxy
nonproxyhosts
=
"localhost"
proxyhost
=
"ehttp1"
proxyport
=
"80"
/>
<property
name
=
"wt.config.haltonerror"
value
=
"true"
/>
<property
name
=
"wt.config.haltonfailure"
value
=
"true"
/>
<property
name
=
"wt.config.resultpath"
value
=
"../webtest-results"
/>
Nous déclarerons Config.xml dans le fichier allTests.xml.
<?xml version="1.0"?>
<!DOCTYPE project [
<!ENTITY config__xml
SYSTEM
"config/Config.xml"
>
]>
<project
default
=
"runTests"
>
&config__xml;
<target
name
=
"runTests"
description
=
"runs all the tests"
>
<ant
antfile
=
"usecase/UtilisateurNormalTest.xml"
/>
</target>
</project>
Il en résulte alors la structure arborescente suivante :
IV-C-6. Remarque sur l'exécution d'une suite de tests fonctionnels▲
Pour l'instant nous n'avons qu'un test fonctionnel. Lors de l'exécution des scénarii suivants, nous serons amenés à en écrire davantage. L'inconvénient actuel de Canoo est que la configuration de l'exécution d'une suite de test est limitée. Soit il s'arrête au premier test qui échoue, soit il exécute tous les tests, mais le build sera marqué SUCCESSFULL même si certains tests ont échoué. L'idéal serait que la suite de tests s'exécute à la manière de JUnit : tous les tests sont exécutés, si un a échoué alors un message d'erreur (Exception) est envoyé. Nous aborderons cet aspect à la fin du chapitre suivant et nous verrons qu'un petit développement externe sera de rigueur si l'on veut obtenir ce que nous souhaitons.
IV-D. Conclusion▲
Nous venons de voir que préparer notre application à subir les tests fonctionnels a nécessité un certain effort. Cet effort payera lors de l'exécution des scénarii suivants, car nous verrons que le temps accordé au développement pur (implémentation du besoin) par rapport aux autres tâches augmentera de manière considérable et rendra le développeur plus productif. Notre application pourrait à présent être construite via un outil d'intégration continue (Hudson pour ne pas nommer).