IV. Lancement d'une classe de tests▲
Dans cette partie, nous allons pouvoir constater que l'organisation des tests est un des points forts du framework TestNG. En effet, les possibilités offertes sont très importantes. Par ailleurs nous verrons les techniques les plus utiles de lancement des tests, à savoir grâce au plugin Eclipse et grâce à une tâche Ant.
IV-A. Création du fichier testng.xml▲
Le fichier testng.xml est le fichier de configuration du lancement des tests. Il permet de configurer les tests à lancer en fonction de leurs packages, classes, groupes ou encore par groupes de groupes! La DTD de ce fichier .xml permet vraiment énormément de configurations.
Un fichier de configuration s'articule autour de deux éléments que sont la suite qui représente l'ensemble des tests à lancer, et les tests qui représentent chaque test devant être lancé. Un test peut contenir plusieurs classes ou groupes de tests.
Chacun de ces éléments peut avoir un nom. La suite comporte un attribut permettant de spécifier le niveau de log à afficher.
Exemple de fichier :
<!DOCTYPE suite SYSTEM
"http://testng.org/testng-1.0.dtd"
>
<suite
name
=
"Suite"
verbose
=
"5"
>
<test
name
=
"TestNum1"
>
...
</test>
<test
name
=
"TestNum2"
>
...
</test>
.
.
.
<test
name
=
"TestNumN"
>
...
</test>
</suite>
Schéma de la DTD du fichier testng.xml
IV-A-1. Les organisations possibles des tests▲
Nous pouvons organiser nos lancements de tests selon plusieurs axes :
- Les packages de tests
Nous pouvons demander à ce que tout un package soit inclus au lancement du test. Cela limite les lignes de configuration d'ajout de classes de tests.
<!DOCTYPE suite SYSTEM
"http://testng.org/testng-1.0.dtd"
>
<suite
name
=
"SuitePackage"
verbose
=
"5"
>
<test
name
=
"FirstTest"
>
<packages>
<package
name
=
"test.testPkg"
/>
</packages>
</test>
</suite>
Ici toutes les classes du package test.testPkg comportant des tests seront ajoutées au premier test nommé FirstTest.
- Les classes de tests
Ici nous ajouterons les classes contenant des tests TestNG à notre test.
<!DOCTYPE suite SYSTEM
"http://testng.org/testng-1.0.dtd"
>
<suite
name
=
"SuiteClass"
verbose
=
"5"
>
<test
name
=
"FirstTest"
>
<classes >
<class
name
=
"test.testClass.TestSample1"
/>
<class
name
=
"test.testClass.TestSample2"
/>
</classes >
</test>
</suite>
- Les méthodes de tests d'une classe
Ici nous spécifierons les méthodes de tests que nous voulons lancer sur les classes. Cela permet un affinement des exécutions.
<!DOCTYPE suite SYSTEM
"http://testng.org/testng-1.0.dtd"
>
<suite
name
=
"SuiteClass"
verbose
=
"5"
>
<test
name
=
"FirstTest"
>
<classes>
<class
name
=
"test.testClass.TestSample1"
>
<methods>
<include
name
=
".*enabledTestMethod.*"
/>
<exclude
name
=
".*brokenTestMethod.*"
/>
</methods>
</class>
</classes>
</test>
</suite>
Ici nous voyons que nous avons ajouté les tests des méthodes contenant le mot enabledTestMethod et exclu les méthodes contenant brokenTestMethod.
- Les groupes de tests
C'est sûrement la fonctionnalité la plus intéressante proposée par TestNG. Le lancement des tests en fonction des groupes permet d'avoir des fichiers très précis en ce qui concerne les tests à effectuer.
Nous pouvons regrouper les groupes dans d'autres groupes pour une question de lisibilité. Nous pouvons aussi exclure certains groupes des exécutions. Les tests seront alors notés comme Skipped dans les rapports.
Cette exclusion est utile pour les tests que nous voulons ignorer, en lieu et place de l'attribut enabled, qui ignore le test et l'empêche d'apparaître dans les rapports.
Exemple d'utilisation des groupes avec exclusion
<!DOCTYPE suite SYSTEM
"http://testng.org/testng-1.0.dtd"
>
<suite
name
=
"SuiteClass"
verbose
=
"5"
>
<test
name
=
"FirstTest"
>
<groups>
<run>
<include
name
=
"groupOk.*"
/>
<exclude
name
=
"groupNonOk.*"
/>
</run>
</groups>
<classes>
<class
name
=
"test.testClass.TestSample1"
/>
</classes>
</test>
</suite>
Ici nous voyons que nous lancerons les tests du groupe groupOk contenus dans la classe test.testClass.TestSample1 et nous exclurons les tests du groupe groupNonOk.
Exemple de groupes de groupes
Ici nous allons regrouper les groupes et nous demanderons de lancer le regroupement.
<!DOCTYPE suite SYSTEM
"http://testng.org/testng-1.0.dtd"
>
<suite
name
=
"SuiteClass"
verbose
=
"5"
>
<test
name
=
"FirstTest"
>
<groups>
<define
name
=
"multipleTestGroup"
>
<include
name
=
"firstGroupTest"
/>
<include
name
=
"secondGroupTest"
/>
</define>
<define
name
=
"allGroups"
>
<include
name
=
"multipleTestGroup"
/>
<include
name
=
"singleTestGroup"
/>
</define>
<run>
<include
name
=
"allGroups"
/>
</run>
</groups>
<classes>
<class
name
=
"test.testClass.TestSample1"
/>
</classes>
</test>
</suite>
Nous avons effectué plusieurs inclusions de groupes dans des groupes de plus haut niveau (multipleTestGroup puis allGroups). C'est ce dernier groupe-ci qui sera lancé à l'appel du test et donc ce sont les groupes contenus qui seront utilisés. Les méthodes lancées seront celles correspondant au groupe allGroups contenues dans la classe test.testClass.TestSample1.
Finalement, énormément de possibilités sont offertes; au programmeur de les utiliser à bon escient.
IV-A-2. Création par le plugin Eclipse▲
Le plugin Eclipse permet de créer son fichier testng.xml grâce à une interface graphique offrant globalement les mêmes fonctionnalités.
Aperçu d'une configuration par le plugin
Ainsi nous pouvons ajouter des classes ou des groupes ou encore nous baser sur un fichier déjà créé.
IV-B. Lancement des tests▲
TestNG permet que les tests soient lancés de plusieurs manières :
- Par le plugin Eclipse ;
- Par une classe Java ;
- En ligne de commandes ;
- Par une tâche Ant.
Nous allons maintenant voir comment lancer les tests grâce au plugin (pour son reporting) et grâce à une tâche Ant (pour l'automatisation des lancements).
IV-B-1. Lancement grâce au plugin Eclipse▲
Le plugin Eclipse permet de lancer par une interface graphique les tests définis. C'est la même interface que celle utilisée pour définir la suite de test contenue dans le fichier testng.xml. Une View Eclipse est disponible pour voir les résultats des tests. Elle permet aussi de relancer les tests préalablement échoués.
Lancement d'une suite de test :
Une fois le test exécuté, nous pouvons regarder les résultats dans la vue TestNG.
Nous pouvons voir beaucoup d'informations et effectuer quelques actions :
- Résumé des tests exécutés : nombre de tests lancés, réussis, échoués et ignorés ;
- Résumé de chaque méthode de test échouée : l'erreur Java qui a causé le plantage du test ;
- Relance de tous les tests du fichier de configuration testng.xml ;
- Relance des tests échoués uniquement.
Voici la vue de reporting générale :
Et la vue de reporting d'un test échoué :
IV-B-2. Lancement grâce à une tâche Ant▲
Nous venons de voir comment utiliser le plugin Eclipse. Mais ce n'est pas quelque chose d'automatique et encore moins portable.
En effet l'installation du plugin est nécessaire pour bénéficier de ces fonctionnalités. Nous allons voir une méthode plus propre et plus adaptée au déploiement et à des clients des applications.
Je veux bien entendu parler d'une tâche Ant.
La tâche Ant permet d'effectuer plus de choses à l'exécution des tests. En effet, nous pouvons configurer les rapports de tests afin d'avoir des rapports au format HTML notamment.
Cela est très utile pour avoir des traces de nos tests, et ces rapports sont très appréciés dans le cadre d'un projet.
Nous allons donc voir les éléments de base de la tâche Ant. La partie concernant les rapports sera traitée par la suite.
Voici les éléments concernant la configuration de TestNG, comme l'inclusion de la librairie pour Ant :
<!-- Création d'un projet Ant -->
<project
name
=
"testng"
default
=
"base"
>
<!-- Spécification de propriétés utilisées par la suite -->
<property
name
=
"bin.dir"
value
=
"bin/"
/>
<property
name
=
"lib.dir"
value
=
"lib/"
/>
<property
name
=
"src.dir"
value
=
"src/"
/>
<property
name
=
"src.dir.app"
value
=
"src/"
/>
<!-- Définition d'un path -->
<path
id
=
"class.path"
>
<fileset
dir
=
"${lib.dir}"
>
<!-- Ajout des jar de TestNG au path pour la suite de l'exécution -->
<include
name
=
"*.jar"
/>
</fileset>
<path
location
=
"${bin.dir}"
/>
>
</path>
...
</project>
Vient ensuite la partie concernant la définition des tâches Ant concernant les exécutions TestNG à proprement parler :
<!-- Création d'une tâche Ant -->
<target
name
=
"base"
>
<!-- Déclaration que c'est une tâche pour TestNG -->
<taskdef
name
=
"testng"
classname
=
"org.testng.TestNGAntTask"
classpathref
=
"class.path"
/>
<!-- Utilisation du path défini précédemment -->
<!-- On continue l'exécution malgré un test échoué -->
<testng
classpathref
=
"class.path"
haltOnfailure
=
"false"
>
<!-- Spécification de la localisation du fichier testng.xml à exécuter -->
<xmlfileset
dir
=
"${src.dir.app}/base"
includes
=
"testng.xml"
/>
<!-- Spécification d'un argument à la JVM : activation des annotations -->
<jvmarg
value
=
"-ea"
/>
</testng>
</target>
Nous avons vu comment écrire une tâche pour l'outil commun Ant. L'exécution se fait comme n'importe quelle tâche Ant.
Nous pouvons désormais passer aux différents exemples des fonctionnalités de TestNG décrites plus haut.
Nous voyons ici la vue Eclipse permettant le lancement des différents exemples.
V. Exemple simple de tests▲
Nous allons maintenant passer à un exemple permettant de mettre en œuvre les principes de base des tests unitaires avec TestNG.
V-A. Principes généraux du test▲
Dans cet exemple nous allons utiliser deux objets métiers, à savoir :
- Un Apprenti (Apprentice), caractérisé par un nom, un prénom et une filière ;
- Une Filière d'études (Stream), caractérisée par un nom d'école et le libellé de la filière.
Chaque apprenti est donc lié à une filière. Ceci se fait grâce à une référence d'un objet Stream stockée dans l'Apprentice.
Nous allons par la suite stocker des apprentis dans une HashMap afin de pouvoir stocker des éléments et les récupérer par la suite.
Cette HashMap est gérée par une autre classe qui nous permettra de la remplir rapidement, de la vider, de récupérer un élément et d'en récupérer la taille.
Ceci sera utile pour les méthodes de setUp(), tearDown() et pour les assertions.
V-B. Mise en œuvre des tests : TestNGApprenti.java▲
V-B-1. Les méthodes de tests▲
Dans la classe correspondant réellement aux tests, nous allons utiliser des méthodes annotées par @Configuration afin de mettre en place les données nécessaires à nos tests. Nous pouvons citer la création de la HashMap dans la classe ApprenticeData.java, son remplissage et son vidage à la fin de chaque test pour retourner à l'état initial.
Nous avons ensuite 6 méthodes de tests permettant chacune de vérifier les fonctionnements de nos objets :
- managerGetApprenti() pour la vérification du fonctionnement de la HashMap ;
- attributesTestingShouldBeOk() pour tester les valeurs des attributs d'un apprenti récupéré par rapport à l'original ;
- attributesTestingShouldFail() pour vérifier que si on modifie les valeurs alors elles ne seront plus correctes et le test échouera ;
- methodeignore() dont l'attribut enabled de l'annotation @Test est mis à false, pour vérifier que le test ne sera pas exécuté ;
- methodeExceptionOK() pour vérifier le fonctionnement des Exception avec TestNG ;
- methodeExceptionNonOK() qui vérifie que le test échoue si une Exception attendue ne se produit pas.
Exemple de code de méthodes (autres méthodes disponibles dans les sources) :
/**
* Vérifie les attributs de l'apprenti
*/
@Test
(
groups =
"ir"
, enabled=
true
)
public
void
attributesTestingShouldBeOk
(
){
Apprentice app =
_appmap.getApprenti
(
_appmap.size
(
)-
1
);
/* Vérification que les attributs sont non nuls */
assertNotNull
(
app.getName
(
), "Le nom ne devrait pas etre null"
);
assertNotNull
(
app.getFirstName
(
), "Le prenom ne devrait pas etre null"
);
assertNotNull
(
app.getStream
(
), "La filiere ne devrait pas etre null"
);
/* Vérification que les valeurs des attributs sont similaires à ceux attendus */
assertEquals
(
app.getName
(
), _apprenti.getName
(
), "Les noms devraient etre identiques"
);
assertEquals
(
app.getFirstName
(
), _apprenti.getFirstName
(
), "Les prenoms devraient etre identiques"
);
assertEquals
(
app.getStream
(
).getLibelle
(
), _apprenti.getStream
(
).getLibelle
(
), "Les filieres devraient avoir le meme nom"
);
assertEquals
(
app.getStream
(
).getSchoolName
(
), _apprenti.getStream
(
).getSchoolName
(
), "Les filieres devraient avoir la meme école d'attache"
);
}
/**
* Méthode qui attend une Exception.
*/
@Test
(
groups=
"ir"
)
/* On attend une NullPointerException ou une Exception en héritant */
@ExpectedExceptions
(
NullPointerException.class
)
public
void
methodeExceptionOK
(
){
throw
new
NullPointerException
(
);
}
V-B-2. Le fichier testng.xml associé▲
<!DOCTYPE suite SYSTEM
"http://testng.org/testng-1.0.dtd"
>
<suite
name
=
"IR"
verbose
=
"5"
>
<test
name
=
"Apprenti"
>
<classes>
<!-- Ajout de la classe de tests définie précédemment -->
<class
name
=
"base.TestNGApprenti"
/>
</classes>
</test>
</suite>
V-B-3. Exécution des tests▲
Nous allons maintenant pouvoir exécuter le test grâce à notre fichier Ant. Nous allons pouvoir voir que les traces Ant nous renseignent énormément sur le déroulement de l'exécution.
# On voit les méthodes de tests trouvées par le moteur de TestNG.
# Nos 6 méthodes sont présentes ainsi que les méthodes de "configuration".
[testng] TESTCLASS: base.TestNGApprenti
[testng] [TestClass] BeforeClass : base.TestNGApprenti.setUpData()
[testng] [TestClass] BeforeMethod: base.TestNGApprenti.beforeMethode()
[testng] [TestClass] Test : base.TestNGApprenti.attributesTestingShouldFail()
[testng] [TestClass] Test : base.TestNGApprenti.methodeignore()
[testng] [TestClass] Test : base.TestNGApprenti.methodeExceptionNonOK()
[testng] [TestClass] Test : base.TestNGApprenti.attributesTestingShouldBeOk()
[testng] [TestClass] Test : base.TestNGApprenti.methodeExceptionOK()
[testng] [TestClass] Test : base.TestNGApprenti.managerGetApprenti()
[testng] [TestClass] AfterMethod : base.TestNGApprenti.afterMethode()
[testng] [TestClass] AfterClass : base.TestNGApprenti.tearDownData()
[testng] [TestClass]
[testng] ======
# Nos méthodes seront exécutées de manière parallèle.
# On voit que la méthode ignorée a disparu de l'exécution.
[testng] [TestRunner] PARALLEL LIST:
[testng] [TestRunner] base.TestNGApprenti.methodeExceptionNonOK()
[testng] [TestRunner] base.TestNGApprenti.managerGetApprenti()
[testng] [TestRunner] base.TestNGApprenti.attributesTestingShouldFail()
[testng] [TestRunner] base.TestNGApprenti.methodeExceptionOK()
[testng] [TestRunner] base.TestNGApprenti.attributesTestingShouldBeOk()
[testng] [TestRunner] SEQUENTIAL LIST:
[testng] [TestRunner] ===
[testng] [TestRunner] Found 5 applicable methods
# Nous voyons ici l'ordre d'exécution des méthodes.
# Nous voyons bien les alternances entre tests et méthodes de "configuration".
[testng] [Invoker 5383406] Invoking base.TestNGApprenti.setUpData()
[testng] [Invoker 5383406] Invoking base.TestNGApprenti.beforeMethode()
[testng] [Invoker 5383406] Invoking base.TestNGApprenti.methodeExceptionNonOK
[testng] [Invoker 5383406] Invoking base.TestNGApprenti.afterMethode()
[testng] [Invoker 5383406] Invoking base.TestNGApprenti.beforeMethode()
[testng] [Invoker 5383406] Invoking base.TestNGApprenti.managerGetApprenti
[testng] [Invoker 5383406] Invoking base.TestNGApprenti.afterMethode()
...
[testng] ***********
# Nous avons ici le résumé de l'exécution et donc les résultats.
[testng] PASSED: managerGetApprenti
[testng] PASSED: methodeExceptionOK
[testng] PASSED: attributesTestingShouldBeOk
# Nous avons ici une erreur que nous attendions.
# Nous avons par ailleurs un message donnant la cause de l'erreur.
[testng] FAILED: base.TestNGApprenti.methodeExceptionNonOK()
[testng] org.testng.TestException:
[testng] Expected an exception in test method base.TestNGApprenti.methodeExceptionNonOK()
[testng] at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:556)
[testng] at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:89)
[testng] at org.testng.TestRunner.privateRun(TestRunner.java:622)
[testng] at org.testng.TestRunner.run(TestRunner.java:505)
[testng] at org.testng.SuiteRunner.privateRun(SuiteRunner.java:200)
[testng] at org.testng.SuiteRunner.run(SuiteRunner.java:126)
[testng] at org.testng.TestNG.run(TestNG.java:294)
[testng] at org.testng.TestNG.privateMain(TestNG.java:382)
[testng] at org.testng.TestNG.main(TestNG.java:330)
# Second test devant échouer, avec un message explicite que nous avons spécifié.
[testng] FAILED: base.TestNGApprenti.attributesTestingShouldFail()
[testng] Java.lang.AssertionError: La filiere ne devrait pas etre null expected:<true> but was:<false>
[testng] at org.testng.Assert.fail(Assert.java:73)
[testng] at org.testng.Assert.failNotEquals(Assert.java:345)
[testng] at org.testng.Assert.assertTrue(Assert.java:28)
[testng] at org.testng.Assert.assertNotNull(Assert.java:273)
...
# Résumé de l'exécution du Test Apprenti
# Et création des rapports de tests (demandé dans le fichier testng.xml).
[testng] ===============================================
[testng] Apprenti
[testng] Tests run: 5, Failures: 2, Skips: 0
[testng] ===============================================
[testng] Creating E:\workspace\Xpose_TestNG\reports\testng\base\testng-failures.xml
[testng] Creating E:\workspace\Xpose_TestNG\reports\testng\base\toc.html
[testng] Creating E:\workspace\Xpose_TestNG\reports\testng\base\Apprenti.properties
[testng] Creating E:\workspace\Xpose_TestNG\reports\testng\base\index.html
[testng] Creating E:\workspace\Xpose_TestNG\reports\testng\base\main.html
[testng] Creating E:\workspace\Xpose_TestNG\reports\testng\base\groups.html
[testng] Creating E:\workspace\Xpose_TestNG\reports\testng\base\methods.html
[testng] Creating E:\workspace\Xpose_TestNG\reports\testng\base\classes.html
# Résumé de l'exécution de la suite IR.
[testng] ===============================================
[testng] IR
[testng] Total tests run: 5, Failures: 2, Skips: 0
[testng] ===============================================
Nous avons pu voir le fonctionnement d'un test de bout en bout. Nous voyons que les tests apportent de bonnes informations sur le cœur de nos applications en permettant un debug efficace.
V-B-4. Les sources de ce test▲
Les sources du test sont disponibles ici.
VI. Exemple de tests avec passage de paramètres▲
Dans ce test nous allons pouvoir voir comment fonctionne le passage de paramètres de manière statique, mais aussi de manière dynamique grâce au @DataProvider
VI-A. Principes généraux du test▲
Dans cet exemple nous utilisons les deux mêmes objets métier que précédemmment, à savoir l'Apprentice et la Stream.
Cette fois les différents objets auront leurs propriétés stockées dans une base de données. J'ai utilisé une base de type MySQL, que j'ai rempli de quatre apprentis et plusieurs filières, dont un apprenti sans filière.
Vous pouvez trouver le fichier concernant de la base Xpose. Cette base sera réutilisée par la suite dans l'exemple de généralisation des tests par une @Factory.
Dans ce test, nous allons utiliser le passage de paramètres statique pour spécifier les paramètres concernant la base de données, à savoir :
- L'URL de la base de données ;
- Le login d'accès à la base ;
- Le password du compte.
Par ailleurs, nous utiliserons un passage de paramètres dynamique (avec un DataProvider) pour effectuer un test sur chaque apprenti de la base. Ce test utilise les données que lui fournit le DataProvider. Le test vérifie que tous les attributs de l'objet Apprentice sont non nuls.
VI-B. Mise en œuvre des tests : TestNGParameters.java▲
VI-B-1. Les méthodes de tests▲
Dans cette classe, nous avons 4 méthodes utilisées par TestNG :
- void setUpData(String url, String user, String password) qui permet d'ouvrir la connexion à la base de données via JDBC ;
- void tearDownData() qui clôt la connexion à la base de données ;
- void verifyAllApprentice(Apprentice apprentice) qui effectuera la vérification des attributs de l'apprenti passé en paramètres.
C'est cette méthode qui spécifiera qu'elle attend des paramètres provenant d'un DataProvider nommé listAll ; - Iterator<Object[]> giveData() qui sera la méthode exerçant la fonction de DataProvider.
Elle permet de récupérer tous les apprentis de la base ainsi que leur filière.
Passage de paramètres de manière statique
Pour passer des paramètres de manière statique, nous allons utiliser l'annotation @Parameters pour spécifier une attente de paramètres venant du fichier testng.xml.
Voici le code de la méthode setUpData() utilisant cette technique :
/**
* Passage de paramètres via le fichier XML pour des infos générales (Base de données)
*
@param
url
url de la base
*
@param
user
le login de la base
*
@param
password
le password de la base
*/
@Configuration
(
beforeTestClass =
true
)
/* On spécifie que l'on attend trois paramètres */
/* et les noms donnés sont les noms des paramètres écrits dans le fichier testng.xml */
@Parameters
({
"url"
, "user"
, "password"
}
)
public
void
setUpData
(
String url, String user, String password) {
try
{
Class.forName
(
"com.mysql.jdbc.Driver"
);
jdbcConnection =
DriverManager.getConnection
(
url, user, password);
}
catch
(
SQLException e) {
e.printStackTrace
(
);
}
catch
(
ClassNotFoundException e) {
e.printStackTrace
(
);
}
}
Le fichier testng.xml associé
<!DOCTYPE suite SYSTEM
"http://testng.org/testng-1.0.dtd"
>
<suite
name
=
"Parameters"
verbose
=
"5"
>
<test
name
=
"Parameters"
>
<!-- Définition des paramètres et de leur valeurs -->
<parameter
name
=
"url"
value
=
"jdbc:mysql://localhost:3306/Xpose"
/>
<parameter
name
=
"user"
value
=
"root"
/>
<parameter
name
=
"password"
value
=
""
/>
<classes>
<class
name
=
"parameters.TestNGParameters"
/>
</classes>
</test>
</suite>
Nous venons de spécifier 3 paramètres statiques pour notre méthode citée ci-dessus. Ils sont reconnus par leur nom dans l'annotation @Parameters. Bien entendu, l'ordre des paramètres est important.
Passage de paramètres de manière dynamique
Nous allons maintenant voir comment utiliser les DataProvider afin de récupérer dynamiquement des données, qui seront ensuite utilisées par la méthode de test utilisant ce DataProvider.
Code de la méthode utilisant le DataProvider :
/**
* Test les paramètres du DataProvider
*
@param
apprentice
l'apprenti testé
*/
/* Spécification du DataProvider qui nous donnera les Apprentice attendus en paramètres */
@Test
(
dataProvider =
"listAll"
, groups =
"parameters"
)
public
void
verifyAllApprentice
(
Apprentice apprentice){
/* Vérification des données de l'apprenti reçu en paramètre. */
assertNotNull
(
apprentice.getName
(
), "Name should not be null"
);
assertNotNull
(
apprentice.getFirstName
(
), "First name of <"
+
apprentice.getName
(
)+
"> should not be null"
);
assertNotNull
(
apprentice.getStream
(
), "Stream of <"
+
apprentice.getName
(
)+
"> should not be null"
);
}
Ici nous attendrons que le DataProvider listAll nous fournisse des Apprentice.
Le code de la méthode du DataProvider ne fait que récupérer les informations de la base de données en les plaçant dans une liste. Cette méthode renverra un Iterator sur cette même liste d'Object.
/**
* Permet de servir de source de données
*
@return
l iterateur des objets créés
*/
/* Définition du DataProvider de nom listAll. */
@DataProvider
(
name =
"listAll"
)
public
Iterator<
Object[]>
giveData
(
){
...
return
list.iterator
(
);
}
VI-B-2. Exécution des tests▲
Grâce à Ant, nous allons exécuter le test et analyser le résultat.
# Une seule méthode de tests a été trouvée.
# La méthode fournissant les données n'est pas comptée.
[testng] TESTCLASS: parameters.TestNGParameters
[testng] [TestClass] BeforeClass : parameters.TestNGParameters.setUpData(java.lang.String, java.lang.String, java.lang.String)
[testng] [TestClass] Test : parameters.TestNGParameters.verifyAllApprentice(parameters.Apprentice)
[testng] [TestClass] AfterClass : parameters.TestNGParameters.tearDownData()
[testng] [TestClass]
...
# Lancement des tests.
# Nous voyons bien que la méthode est appelée 4 fois, ce qui correspond bien au nombre d'apprentis dans la base.
[testng] [TestRunner] Found 1 applicable methods
[testng] [Invoker 5383406] Invoking parameters.TestNGParameters.setUpData(java.lang.String, java.lang.String, java.lang.String)
[testng] [Invoker 5383406] Invoking parameters.TestNGParameters.verifyAllApprentice
[testng] [Invoker 5383406] Invoking parameters.TestNGParameters.verifyAllApprentice
[testng] [Invoker 5383406] Invoking parameters.TestNGParameters.verifyAllApprentice
[testng] [Invoker 5383406] Invoking parameters.TestNGParameters.verifyAllApprentice
[testng] [Invoker 5383406] Invoking parameters.TestNGParameters.tearDownData()
[testng] *********** INVOKED METHODS
# Ici nous voyons les paramètres passés aux méthodes, que ce soit en statique ou dynamique.
# Le password pour la méthode setUpData() est vide d'où le fait que l'on ne le voit pas.
[testng] parameters.TestNGParameters.setUpData(java.lang.String, java.lang.String, java.lang.String)jdbc:mysql://localhost:3306/Xpose root 30971263
[testng] parameters.TestNGParameters.verifyAllApprentice(parameters.Apprentice)parameters.Apprentice@3ee284 30971263
[testng] parameters.TestNGParameters.verifyAllApprentice(parameters.Apprentice)parameters.Apprentice@8965fb 30971263
[testng] parameters.TestNGParameters.verifyAllApprentice(parameters.Apprentice)parameters.Apprentice@867e89 30971263
[testng] parameters.TestNGParameters.verifyAllApprentice(parameters.Apprentice)parameters.Apprentice@1dd7056 30971263
[testng] parameters.TestNGParameters.tearDownData() 30971263
# Résultats des tests
[testng] ***********
[testng] PASSED: verifyAllApprentice
[testng] PASSED: verifyAllApprentice
[testng] PASSED: verifyAllApprentice
# Un des apprentis ne passait pas le test, car il n'a pas de filière.
[testng] FAILED: parameters.TestNGParameters.verifyAllApprentice(parameters.Apprentice)
[testng] java.lang.AssertionError: Stream of <Apprenti2> should not be null expected:<true> but was:<false>
[testng] at org.testng.Assert.fail(Assert.java:73)
...
# Résumé, création des rapports
[testng] ===============================================
[testng] Parameters
[testng] Tests run: 4, Failures: 1, Skips: 0
[testng] ===============================================
[testng] Creating E:\workspace\Xpose_TestNG\reports\testng\parameters\testng-failures.xml
[testng] Creating E:\workspace\Xpose_TestNG\reports\testng\parameters\toc.html
[testng] Creating E:\workspace\Xpose_TestNG\reports\testng\parameters\Parameters.properties
[testng] Creating E:\workspace\Xpose_TestNG\reports\testng\parameters\index.html
[testng] Creating E:\workspace\Xpose_TestNG\reports\testng\parameters\main.html
[testng] Creating E:\workspace\Xpose_TestNG\reports\testng\parameters\groups.html
[testng] Creating E:\workspace\Xpose_TestNG\reports\testng\parameters\methods.html
[testng] Creating E:\workspace\Xpose_TestNG\reports\testng\parameters\classes.html
[testng] ===============================================
[testng] Parameters
[testng] Total tests run: 4, Failures: 1, Skips: 0
[testng] ===============================================
Ce test a permis de montrer comment utiliser les paramètres. C'est très utile, car les tests sont plus dynamiques dès lors que l'on utilise des DataProvider et donc d'automatiser les tests. Cela permet aussi d'utiliser des paramètres de type complexe.
Il faut bien comprendre l'esprit du DataProvider : c'est une méthode particulière de test qui sera exécutée autant de fois que de données fournies.
Il faut bien distinguer ce cas de celui de la Factory où c'est toute une classe qui est appelée autant de fois que de données fournies
VI-B-3. Les sources de ce test▲
Les sources du test sont disponibles ici.
VII. Exemple de généralisation des tests, @Factory▲
Dans cet exemple aussi il faut bien comprendre le fonctionnement de cette Factory. Elle crée des instances d'une classe de tests et les sauvegardes. Lors du lancement, elle les exécutera. La classe exécutée peut, bien entendu, comporter autant de tests que voulu.
VII-A. Principes généraux du test▲
Cet exemple utilise les mêmes éléments que celui concernant le passage de paramètres. On utilise la même base de données avec les mêmes informations stockées. Nous effectuerons le même test que pour les paramètres, mais en utilisant une classe de test externe au lieu d'une méthode et d'un DataProvider.
Nous allons maintenant voir comment mettre cette technique en pratique.
VII-B. Mise en œuvre des tests : TestNGFactory.java et TestApprentice.java▲
VII-B-1. Les méthodes de tests▲
La Factory : TestNGFactory
Dans la classe correspondant à la Factory, nous avons deux méthodes :
- Object[] createInstances(String url, String user, String password)
Méthode qui sera annotée par @Factory. Elle permettra de récupérer les informations de la base de données et de créer des instances de la classe TestApprentice. Ces instances seront celles exécutées ensuite ; - tearDownData() qui ferme juste la connexion JDBC à la base de données.
Voici les éléments principaux du code de la méthode annotée par @Factory :
/**
* Définition de la Factory
* On crée un tableau d'instances de la classe de test avec l'objet à tester
* Les invocations de cette classe seront faites automatiquement grâce à la FACTORY
*
* Passage de paramètres de la base
*
@param
url
url de la base
*
@param
user
le login de la base
*
@param
password
le password de la base
*
@return
un tableau d'instances des tests à lancer
*/
@Factory
@Parameters
({
"url"
, "user"
, "password"
}
)
public
Object[] createInstances
(
String url, String user, String password){
...
/* Tableau contenant nos instances. */
Object[] results =
null
;
...
/* Récupération des apprentis */
...
/* Instanciation et ajout dans notre tableau stockant les instances. */
/* Ici on stocke un apprenti avec filière. */
results[i] =
new
TestApprentice
(
new
Apprentice
(
tab[0
], tab[1
], new
Stream
(
tab[2
], streamSchool)));
}
else
{
/* Instanciation et ajout dans notre tableau stockant les instances. */
/* Ici on stocke un apprenti sans filière. */
results[i] =
new
TestApprentice
(
new
Apprentice
(
tab[0
], tab[1
], null
));
}
...
return
results;
}
La classe de tests : TestApprentice
Pour la classe que la Factory instancie et qui effectuera réellement les tests, nous avons :
- TestApprentice(Apprentice apprentice)() qui est le constructeur et permet l'instanciation par la Factory de la classe ;
- verifyApprentice() qui effectuera les tests sur l'apprenti récupéré à la construction de la classe.
Code des deux méthodes :
/**
* Constructeur de la classe de test
*
@param
apprentice
*/
public
TestApprentice
(
Apprentice apprentice){
this
.apprentice =
apprentice;
}
/**
* Test les paramètres de l'apprenti qui est passé à la création de la classe de test via la Factory
*/
@Test
(
groups =
"factory"
)
public
void
verifyApprentice
(
){
/* Vérification des attributs de l'apprenti en variable de classe. */
assertNotNull
(
apprentice.getName
(
), "Name should not be null"
);
assertNotNull
(
apprentice.getFirstName
(
), "First name of <"
+
apprentice.getName
(
)+
"> should not be null"
);
assertNotNull
(
apprentice.getStream
(
), "Stream of <"
+
apprentice.getName
(
)+
"> should not be null"
);
}
VII-B-2. Le fichier testng.xml associé (similaire à celui du passage de paramètres) :▲
<!DOCTYPE suite SYSTEM
"http://testng.org/testng-1.0.dtd"
>
<suite
name
=
"Factory"
verbose
=
"5"
>
<test
name
=
"Factory"
>
<!-- Définition des paramètres et de leur valeur -->
<parameter
name
=
"url"
value
=
"jdbc:mysql://localhost:3306/Xpose"
/>
<parameter
name
=
"user"
value
=
"root"
/>
<parameter
name
=
"password"
value
=
""
/>
<classes>
<class
name
=
"parameters.TestNGFactory"
/>
</classes>
</test>
</suite>
VII-B-3. Exécution des tests▲
Le lancement s'effectue toujours de la même manière et va nous permettre de voir des éléments particuliers à la Factory. En effet, même si on exécute la Factory, c'est la classe instanciée que l'on retrouvera dans les logs. C'est logique, la Factory n'est présente que pour instancier la classe de test de manière dynamique et de les lancer par la suite.
# On voit ici que l'on exécute la classe de la Factory.
[testng] TESTCLASS: factory.TestNGFactory
[testng] [TestClass]
[testng] ======
[testng] [TestClass]
# Découverte de la classe que la Factory instancie et de sa méthode de test.
[testng] ======
[testng] TESTCLASS: factory.TestApprentice
[testng] [TestClass] Test : factory.TestApprentice.verifyApprentice()
[testng] [TestClass]
[testng] ======
# Exécution de cette méthode de test autant de fois qu'instanciée par la Factory (4).
[testng] [Invoker 41771] Invoking factory.TestApprentice.verifyApprentice
[testng] [Invoker 41771] Invoking factory.TestApprentice.verifyApprentice
[testng] [Invoker 41771] Invoking factory.TestApprentice.verifyApprentice
[testng] [Invoker 41771] Invoking factory.TestApprentice.verifyApprentice
[testng] *********** INVOKED METHODS
[testng] factory.TestApprentice.verifyApprentice() 9004539
[testng] factory.TestApprentice.verifyApprentice() 8814217
[testng] factory.TestApprentice.verifyApprentice() 31289430
[testng] factory.TestApprentice.verifyApprentice() 16399041
# Résumé, on voit les 4 lancements de la méthode de la classe de tests.
[testng] ***********
[testng] PASSED: verifyApprentice
[testng] PASSED: verifyApprentice
[testng] PASSED: verifyApprentice
# Erreur, car l'apprenti n'a pas de filière.
[testng] FAILED: factory.TestApprentice.verifyApprentice()
[testng] java.lang.AssertionError: Stream of <Apprenti2> should not be null expected:<true> but was:<false>
...
# Résumé de l'exécution et création des rapports.
[testng] ===============================================
[testng] Factory
[testng] Tests run: 16, Failures: 4, Skips: 0
[testng] ===============================================
[testng] [Invoker 41771] Invoking factory.TestNGFactory.tearDownData()
[testng] Creating E:\workspace\Xpose_TestNG\reports\testng\factory\testng-failures.xml
[testng] Creating E:\workspace\Xpose_TestNG\reports\testng\factory\toc.html
[testng] Creating E:\workspace\Xpose_TestNG\reports\testng\factory\Factory.properties
[testng] Creating E:\workspace\Xpose_TestNG\reports\testng\factory\index.html
[testng] Creating E:\workspace\Xpose_TestNG\reports\testng\factory\main.html
[testng] Creating E:\workspace\Xpose_TestNG\reports\testng\factory\groups.html
[testng] Creating E:\workspace\Xpose_TestNG\reports\testng\factory\methods.html
[testng] Creating E:\workspace\Xpose_TestNG\reports\testng\factory\classes.html
[testng] ===============================================
[testng] Factory
[testng] Total tests run: 4, Failures: 1, Skips: 0
[testng] ===============================================
Nous avons ainsi vu que la Factory permettait d'instancier dynamiquement une classe de tests. Les méthodes réellement exécutées n'ont finalement aucun lien avec la Factory qui ne fait que son travail de création d'instances.
VII-B-4. Les sources de ce test▲
Les sources du test sont disponibles ici.