IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

TestNG, un autre framework de tests unitaires Java


précédentsommairesuivant

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 :

 
Sélectionnez
<!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

Image non disponible

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.
 
Sélectionnez
<!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.

 
Sélectionnez
<!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.

 
Sélectionnez
<!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

 
Sélectionnez
<!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.

 
Sélectionnez
<!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

Image non disponible

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 :

Image non disponible

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 :

Image non disponible

Et la vue de reporting d'un test échoué :

Image non disponible

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 :

 
Sélectionnez
<!-- 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 :

 
Sélectionnez
   <!-- 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.

Image non disponible

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

 
Sélectionnez
/**
 * 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é

 
Sélectionnez
<!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.

 
Sélectionnez
# 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 :

 
Sélectionnez
/**
 * 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é

 
Sélectionnez
<!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 :

 
Sélectionnez
/**
 * 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.

 
Sélectionnez
/**
 * 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.

 
Sélectionnez
# 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 :

 
Sélectionnez
/**
 * 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 :

 
Sélectionnez
/**
 * 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) :

 
Sélectionnez
<!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.

 
Sélectionnez
# 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.


précédentsommairesuivant

Copyright © 2007 Sébastien Palud. Aucune reproduction, même partielle, ne peut être faite de ce site ni 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.