III. Les outils à notre disposition▲
Afin de décrire les tests, nous utilisons des annotations Java que nous plaçons sur les classes, méthodes de test. Elles permettent de spécifier l'organisation des tests, les configurations à adopter…
Par ailleurs, nous disposons des assertions pour l'écriture des tests à proprement dit.
Les annotations utilisées par TestNG se trouvent dans le package org.testng.annotations du framework.
Un listing exhaustif des attributs de chaque annotation est disponible à l'adresse suivante : http://testng.org/doc/documentation-main.html#annotations
III-A. Les annotations de base▲
Nous allons dans un premier temps décrire les annotations primordiales permettant de décrire nos premiers tests de manière simple, sans utiliser de fonctionnalités avancées. Ces mêmes annotations pourront par ailleurs permettre d'utiliser certaines fonctionnalités telles que les dépendances entre tests, le nombre d'exécutions d'un test…
Les annotations étudiées sont :
- @Configuration ;
- @Test.
III-A-1. L'annotation @Configuration▲
Cette annotation est une des deux annotations primordiales du framework TestNG. Elle permet de définir tout ce qui se passe autour des méthodes de tests à proprement parler. Elle permet, entre autres, de spécifier qu'une méthode sera exécutée avant ou après chaque suite de tests, classe de tests ou chaque méthode de test.
Elle permet donc de définir les méthodes correspondantes aux méthodes obligatoires setUp() et tearDown() des tests JUnit. Elle offre toutefois plus de flexibilité quant aux configurations.
Son utilisation est simple et se fait grâce à la demande des attributs de l'annotation.
Nous verrons, par ailleurs, qu'aucune norme de nommage n'est présente et que les noms des méthodes sont spécifiés par les développeurs. Cela permet entre autres d'avoir plusieurs méthodes setUp() et tearDown() qui seront activées selon les groupes de tests par exemple.
Nous allons maintenant passer à quelques exemples de code représentatifs de son utilisation.
- Méthode appelée au lancement d'une classe de tests
/**
* Méthode appelée dès le début de l'exécution de la classe de tests
*/
@Configuration
(
beforeTestClass =
true
)
public
void
setUpData
(
) {
/* Code à effectuer au début de l'exécution */
/* Exemple : ouverture d'une connexion à une base de données */
}
Nous voyons ici que grâce à l'attribut beforeTestClass activé (mis à true), nous pouvons spécifier au framework TestNG que cette méthode devra être exécutée dès le lancement de cette classe de tests.
Nous allons voir par la suite qu'une granularité supérieure permet de spécifier des méthodes appelées avant et après chaque test effectué.
- Méthode appelée à la fin d'une classe de tests
/**
* Méthode appelée à la fin d'une exécution de la classe de tests
*/
@Configuration
(
afterTestClass =
true
)
public
void
tearDownData
(
){
/* Code à effectuer à la fin de la classe de test */
/* Exemple : fermeture d'une connexion à une base de données */
}
Cette fois-ci, c'est l'attribut afterTestClass qui est actif. Cela permet de configurer une méthode devant être appelée à la fin de l'exécution de la classe de tests.
- Équivalent de la méthode setUp()
/**
* Méthode appelée avant chaque exécution d'un test
*/
@Configuration
(
beforeTestMethod =
true
)
public
void
beforeMethode
(
){
/* Code à effectuer avant chaque méthode de test */
/* Exemple : préparation des données nécessaires pour le test */
}
Ici c'est l'attribut beforeTestMethod qui est actif. Cette méthode sera appelée avant chaque test. On peut ainsi mettre en place certaines données qui sont nécessaires à l'exécution d'un test.
- Équivalent de la méthode tearDown()
/**
* Méthode appelée après chaque exécution d'un test
*/
@Configuration
(
afterTestMethod =
true
)
public
void
afterMethode
(
){
/* Code à effectuer après chaque méthode de test */
/* Exemple : remettre le système dans l'état initial */
}
Nous spécifions que l'attribut afterTestMethod est activé. Cette méthode sera appelée après chaque méthode de test exécutée. Elle permet de remettre le système en l'état initial.
Nous pouvons donc bien voir que cette annotation est nécessaire pour définir tout ce qui est périphérique aux tests unitaires.
III-A-2. L'annotation @Test▲
Cette annotation permet de spécifier qu'une classe ou une méthode correspond à un test (pour une méthode) ou un regroupement de tests (pour une classe).
Nous pouvons par ailleurs avoir plusieurs attributs pour cette annotation notamment les groupes auxquels la méthode de test appartient et sa description.
/**
* Test faisant partie du groupe "ir"
*/
@Test
(
groups =
"ir"
, description =
"test of group IR"
)
public
void
descriptedTestMemberOfGroupIR
(
){
/* Code du test */
}
D'autres attributs permettent de spécifier de nombreux comportements tels que :
- La dépendance de ce test par rapport à d'autres exécutions de tests ou groupes de tests. Attributs dependsOnMethods et dependsOnGroups.
/**
* Test qui sera appelé une fois que la méthode de test firstTest aura été exécutée
* ainsi que toutes les méthodes de test du groupe init.
*/
@Test
(
groups =
"ir"
, dependsOnMethods =
{
"firstTest"
}
, dependsOnGroups =
{
"init.*"
}
)
public
void
dependentTest
(
){
/* Code du test */
}
Nous spécifions ici les dépendances d'un test à un groupe de tests et une méthode de test particulière. Cela permet d'être sûr d'un ordre d'exécution.
- L'activation d'un test
Le test sera ou non exécuté en fonction de la valeur de cet attribut. Si le test n'est pas activé, il n'apparaîtra pas dans les rapports de tests. C'est une fonctionnalité gênante, car des tests peuvent être perdus.
L'utilisation d'un groupe particulier pour les tests inactifs est préférable.
Attribut enabled.
/**
* Test qui ne sera pas exécuté, car l'attribut enabled est à false
*/
@Test
(
groups =
"ir"
, enabled =
false
)
public
void
testWillNotBeExecuted
(
){
/* Code du test */
}
- L'obligation de lancer un test
Le test sera lancé malgré qu'il dépende de certains tests qui auront échoué précédemment.
Attribut alwaysRun mis à « true ». - Le nombre de fois que sera exécuté un test
Attribut invocationCount. - Le nombre de threads utilisés pour une exécution multiple
Cet attribut est en relation directe avec le nombre d'exécutions du test. Si l'attribut invocationCount n'est pas spécifié, le nombre de thread sera ignoré (car le test ne sera exécuté qu'une fois).
Attribut threadPoolSize.
/**
* Test qui ne sera exécuté 30 fois et dans 3 threads différents.
*/
@Test
(
groups =
"ir"
, invocationCount =
30
, threadPoolSize =
3
)
public
void
testMultiThreadMultipleInvocation
(
){
/* Code du test */
}
- La spécification d'un timeout pour un test
Le test échouera si jamais le timeout spécifié est dépassé. Cela permet de faire des tests de performances et de montée en charge.
Attribut timeOut. - Spécification d'une méthode de récupération des paramètres
Cet attribut est très important dans le cadre de la généralisation des tests. Ainsi un test peut être exécuté plusieurs fois avec des paramètres différents, récupérés via une autre méthode particulière.
La valeur de cet attribut représente la méthode où seront récupérés les paramètres nécessaires au test.
Attribut dataProvider. - Le pourcentage de réussite attendu pour le test
Cet attribut peut-être utile dans le cadre d'exécution multiple d'un test. Cela permet aussi de ne pas attendre d'un test qu'il réussisse tout le temps (par exemple lors d'une montée en charge).
Attribut successPercentage.
Nous avons vu les différentes possibilités offertes par les annotations de base du framework TestNG. Nous verrons leurs utilisations dans les exemples disponibles dans une autre rubrique.
Nous pouvons désormais passer à la description des annotations avancées proposées par le framework TestNG.
III-B. Les annotations avancées▲
Les autres annotations disponibles permettent de définir les fonctionnalités citées précédemment telles que les Exceptions, les paramètres…
Nous allons définir chacune d'elles afin d'avoir une vision d'ensemble pour les exemples à venir.
III-B-1. L'annotation @Parameters▲
Cette annotation permet de préciser qu'une méthode de test prendra des paramètres. Cela est valable pour des paramètres spécifiés dans le fichier testng.xml. Les paramètres récupérés dynamiquement grâce à un DataProvider ne sont pas spécifiés grâce à cette annotation.
L'annotation prend comme attribut value la liste des variables utilisées pour remplir les paramètres de la méthode de test.
/**
* Passage de paramètres via le fichier XML pour des infos générales (Base de données)
*
@param
test
la chaine à tester
*/
@Parameters
({
"testP"
}
)
public
void
paramtest
(
String test) {
/* Test utilisant le paramètre */
}
Ici le paramètre sera récupéré dans le fichier testng.xml.
<suite
name
=
"Test"
verbose
=
"5"
>
<test
name
=
"ParametersTest"
>
<parameter
name
=
"testP"
value
=
"une String à tester"
/>
</test>
<!-- Les classes utilisant les paramètres -->
</suite>
III-B-2. L'annotation @DataProvider▲
Cette annotation est particulière. Elle annote une méthode qui fournira des données (via un passage en paramètres) aux méthodes de tests qui en ont besoin.
Elle est reconnue dans la suite de tests par son nom. C'est le seul attribut de cette annotation.
La méthode annotée par le @DataProvider doit obligatoirement retourner Object[][] ou Iterator<Object[]>.
Cette annotation est utile dans le cas de paramètres très nombreux, dynamiques et de types complexes (objets métiers).
/**
* Permet de servir de source de données
*
@return
l iterateur des objets créés
*/
@DataProvider
(
name =
"listAllApprentices"
)
public
Iterator<
Object[]>
giveData
(
){
/* Récupération d'une liste d'Object[] avec un Iterator */
/* Les objets récupérés sont des Apprentice */
}
/**
* Teste les paramètres du DataProvider
*
@param
apprentice
l'apprenti testé
*/
@Test
(
dataProvider =
"listAllApprentices"
)
public
void
verifyAllApprentice
(
Apprentice apprentice){
/* Code du test */
/* Utilisation des objets récupérés en paramètres à travers le DataProvider spécifié (ici listAllApprentices) */
}
III-B-3. L'annotation @ExpectedExceptions▲
Cette annotation permet de spécifier qu'une méthode de test doit retourner une Exception d'un type donné (ou plusieurs Exceptions). Cela peut-être utile lorsque l'on teste les levées d'erreurs d'une classe, d'un module. Ainsi on peut vérifier que le comportement attendu en termes d'exception est bien celui présent.
/**
* Méthode qui attend une Exception.
*/
@Test
(
)
@ExpectedExceptions
(
NullPointerException.class
)
public
void
methodeException
(
){
/* Test devant lever une Exception de type NullPointerException */
}
III-B-4. L'annotation @Factory▲
Cette annotation permet de spécifier qu'une méthode sera une Factory de tests. Elle permettra de créer et exécuter des classes de tests à la volée. Cette méthode retournera des Object que TestNG utilisera comme une classe de tests.
Cette méthode doit obligatoirement retourner Object[].
La factory créera des objets d'une autre classe contenant des tests. Souvent le constructeur de cette classe de tests prendra des paramètres dynamiquement créés dans la méthode annotée par @Factory.
/**
* Méthode qui crée les instances d'une classe de tests et qui les exécutera.
*/
@Factory
public
Object[] testFactory
(
){
/* Test renvoyant un tableau d'instance d'une classe de tests */
/* Exemple : tableau rempli de cette manière : tab[i] = new SimpleTestClass(param1, ...); */
/* et retourné : return tab; */
}
/**
* Classe effectuant des tests. Elle sera instanciée et exécutée par notre Factory.
*/
public
class
SimpleTestClass {
/**
* Constructeur de notre classe de tests, prenant des paramètres.
*/
public
SimpleTestClass
(
String param1, ...){
/* Sauvegarde des paramètres reçus... */
}
/**
* Méthode de test qui sera exécutée lorsque l'instance de la classe sera exécutée par la Factory
*/
@Test
(
)
public
simpleTest
(
){
/* Effectue un test */
}
}
Le fonctionnement se passe en plusieurs étapes :
Mécanisme de génération dynamique des classes de tests grâce à la méthode annotée par @Factory
- Récupération ou génération des paramètres dynamiques si nécessaire (base de données…).
- La méthode @Factory crée des instances d'une classe contenant des tests TestNG avec des paramètres éventuellement.
- Sauvegarde des instances dans un tableau d'Object.
Mécanisme de lancement des instances
- Récupération du tableau d'Object contenant les instances de la classe de tests.
- Exécution de l'instance comme une classe TestNG.
Récupération des résultats de toutes les exécutions
III-C. Les assertions▲
Les assertions sont utilisées par les tests unitaires pour vérifier nos certitudes dans le code. C'est très utile pour les tests afin de vérifier les propriétés de nos objets testés par rapport au comportement attendu. Si nos certitudes ne sont pas bonnes alors le test échouera.
Les assertions permettent d'exprimer de façon programmée nos certitudes humaines.
Nous pouvons par exemple, être sûr que certaines méthodes ne renvoient pas de valeur null, et donc nous les utilisons sans plus d'attention aux valeurs de retour.
Cette certitude peut-être vérifiée par le programme grâce aux assertions.
Ainsi, si jamais des modifications sont apportées aux méthodes utilisées par un autre programmeur, les assertions permettront éventuellement de lever une erreur, montrant que notre certitude n'est plus vérifiée. Nous pourrons alors prendre des mesures de changement de nos certitudes ou bien de retour arrière sur le code des méthodes.
Cela permet d'éviter, entre autres, des régressions.
Pour plus d'informations sur les assertions en java, vous pouvez consulter le lien suivant : https://smeric.developpez.com/java/astuces/assertions/
III-C-1. Les assertions dans le framework TestNG▲
Le framework offre deux jeux d'assertions. Il permet d'utiliser les assertions provenant de JUnit, mais aussi un jeu d'assertions qui lui est propre.
Ce dernier permet de garder un ordre identique dans les paramètres des assertions, à savoir : assertXXX( actualValue, expectedValue [, message] ).