Vous êtes sur la page 1sur 12

CM3 : Tests

M1 informatique
1- Développement dirigé par les tests (TDD)
Module Génie Logiciel – Définitions et constats

CM3 : Tests – Tests fonctionnels


– Tests structurels
– Rédiger les tests avant le code
– Hiérarchisation des tests
Céline ROUDET
Celine.Roudet@u-bourgogne.fr 2- Tests unitaires en Java avec JUnit
http://ufrsciencestech.u-bourgogne.fr/~roudet/teaching.html
http://ufrsciencestech.u-bourgogne.fr/master1/GenieLogiciel/

Qu’est-ce qu’un test et quel est son coût ? Problématique et TDD (Test Driven Development)

• Expérience d’exécution pour mettre en évidence un défaut / erreur


• But : détecter les défauts le plus tôt possible dans le cycle
− diagnostic : quel est le problème, l’erreur − tester une nouvelle méthode dès qu’on l’écrit
− localisation : où est la cause du problème ? − répéter les tests à chaque modification du code
− vérifier que le résultat de l’expérience est conforme aux intentions
• Mais prouver l’absence d’erreur = problème indécidable
• Les tests à faire et résultats dépendent des spécifications − on ne peut pas tester tout le temps ni tous les cas possibles
− il faut des heuristiques réalistes
Implém. − critères pour choisir les cas intéressants = Cas de Test (CT)
Spécif.
Test
Anal. • Test Driven Development (TDD)
− écrire les Cas de Test (CT) avant le programme
− développer la partie du programme qui fait passer les CT
Le coût du test dans le développement
(+maintenance = 80% du coût global de développement)
3 4

Exemple : comment tester la classe StringList ? Exemple : comment tester la classe StringList ? (2)

Spécifications : • Vocabulaire du test :


Gérer une liste de noms de restaurants par ordre de proximité qu'on - Données de Test (DT) : jeux de données d’entrée d’un programme
puisse faire évoluer, mettre à jour, ... - Cas de Test (CT) : scenario à exécuter
- Suite de Tests : ensemble de CTs

Cas de Test (CT) :


• Cas limites et valeurs spéciales Comment écrire ces tests ?
− DT : ajout dans une liste vide Comment les exécuter ?
Les tests sont-ils bons ?
• Test des cas normaux Est-ce que c’est assez testé ?
− DT : ajout dans une liste à 1 élément
− DT : retrait dans une liste à 2 éléments

• Test des cas anormaux


− DT : entrée invalide (non respect des pré-conditions)
Objectifs de test : quels sont les comportements attendus par cette spécification ?
− DT : retrait dans une liste vide
5 6
Tests statiques vs dynamiques Tests dynamiques : avantages et inconvénients


Statiques : inspections, revues, analyse d’anomalies, … • très simple, toujours faisable, bon rapport performance / coût
• peu rigoureux, non-exhaustif, mesure difficile de sa qualité,

Dynamiques = scripts de test : subjectivité du testeur
CT = exécution du programme sur des entrées choisies (DT) :
• Problème n°1 : Comment sélectionner une suite de tests qui
− sélection puis soumission des DT permet de détecter le plus d’erreurs ?  Stratégies de test
− dépouillement des résultats obtenus
• Problème n°2 : Comment mesurer l’efficacité du test ?
− comparaison avec les résultats attendus  Critères de couverture du code
− évaluation de la qualité, de la couverture des tests
• Problème n°3 : Comment formaliser le test ?  Modèles
− décision d’arrêt du test formels de spécification

7 8

3 grandes familles de sélection de tests CM3 : Tests


I1 O1
I2
• Test fonctionnel (test boîte noire : BN) O2
− Aucun accès au code ni à l’architecture
I3 1- Développement dirigé par les tests (TDD)
− Utilise la description des fonctionnalités du programme – Définitions et constats
– Tests fonctionnels
• Test structurel (test boîte blanche : BB) – Tests structurels
Structure interne du programme doit être accessible (le code)
– Rédiger les tests avant le code
I1 O1 – Hiérarchisation des tests
I2
I3 O2
2- Tests unitaires en Java avec JUnit
• Tests aléatoires et statistiques  données choisies selon :
– Une loi uniforme (test aléatoire)
– Une loi statistique (difficile à obtenir)
9 10

Principe des tests fonctionnels Principe des tests fonctionnels : l’oracle

décrit
• Contexte :
Spécification Programme − Soit D le domaine d’entrée d’un programme P, spécifié par SP, on
implante veut : xD, P(x) = SP(x)
− Test exhaustif impossible : D trop grand voire infini
Test − On cherche alors un ensemble de données de test T tel que :
− TD
Résultat
attendu − Il faut que : si x T, P(x) = SP(x) alors  xD, P(x) = SP(x)
Résultat

Comparer : • Oracle : fonction qui évalue le résultat d’un cas de test


Oracle
− Soit xT / O(x, P(x)) = vrai ssi P(x) = SP(x)
différent égal
• Problème : comment comparer P(X) et SP(X) ?
Test échoue Test passe – vérifier qu’on a construit le résultat attendu
– plus SP est formalisé, plus on peut automatiser

11 12
Méthode de sélection des tests fonctionnels Classes d’équivalence

On ne peut pas tout explorer : il faut délimiter But : diviser le domaine d’entrée en un nb fini de classes
• Partition du domaine d’entrée : - même comportement pour toutes les valeurs d’une classe
Ex : pour l’intervalle [1, 100]
Tests : 1, 100, 2, 99, 0, 101 - conséquence : on ne teste qu’une valeur dans chaque classe
− Tests aux limites
- évite des tests redondants (si classes bien identifiées)
− Test combinatoire : tester un Longueur
des côtés
Classe
fragment des combinaisons de valeurs
0,0,0 1 seul point
1. Identifier les classes d’équivalence des entrées :
− Classes d’équivalence pour les entrées :
1,1,0 non triangle - sur la base des conditions sur les entrées / sorties
choisir une donnée de test (DT)
6,1,6 isocèle - en prenant des classes d’entrées valides et invalides
dans chaque classe
3,4,5 rectangle
4,4,4 équilatéral 2. Définir des DT couvrant chaque classe
• Couverture fonctionnelle à base de modèle

• Graphe causes – effets 3. Tester les valeurs aux limites des classes d’équivalence
• Tests ad hoc
13 14

Approche aux limites Classes d’équivalence : exemple


Calcul de la valeur absolue d’un entier n
Exemples de choix des valeurs de test :
Condition Classe valide Classe invalide
- variable dans [a,b] : tester a, b, a ± Δ, b ± Δ (Δ = plus petite
nb. entrées 1 0, > 1
variation possible)
type d’entrée byte, short, int, long String, float, boolean, …
- variable dans {a1, a2, a3, …, an} : tester a1, a2, an-1, an (premier, valeur d’entrée < 0, > 0, 0
second, avant dernier, dernier)

- variable entière (avec N le plus petit / grand entier admissible) : On en déduit un ensemble de DT (valeurs de n) :
tester N − 1, N, N + 1 - DT valides : -10, 100, 0
- DT invalides : ”XYZ”, rien, (10,20)  détecté à la compilation (langages tels que Java)
- variable dans une liste, pile, file, … : tester ensemble vide, ens. à 1
élément, ensemble de taille maximale, ensemble trop gros Approche aux limites (N = -128/127 pour un byte) :
- … - DT valides : 126, 127, -127, -126 (pour un byte)
- DT invalides : -128, -129, 128 (pour un byte)

15 16

CM3 : Tests Tests structurels : principes

1- Développement dirigé par les tests (TDD) • Analyse du code pour produire les Données de Test (DT)
– Définitions et constats
– Tests fonctionnels • Chaque fonction : représentée par un graphe de contrôle
– Tests structurels
– Rédiger les tests avant le code • Comportement du programme = chemin dans le graphe
– Hiérarchisation des tests • Basé sur différents critères de couverture
2- Tests unitaires en Java avec JUnit • Certaines erreurs ne sont détectées que par le test
structurel

17 18
Exemple de graphe de flot de contrôle Exemple de graphe de flot de contrôle (2)
PGCD de 2 nombres :
E Entrée
Précondition : p et q entiers naturels (positifs) Tests : E

pgcd : integer B1 Tous les noeuds et tous les arcs


local p,q : integer B1
do Tous les chemins élémentaires (0 ou 1 passage
read(p, q) // B1 P1 dans la boucle) :
while p ≠ q do // P1 (E, B1, P1, P2, B2, P1, B4, S) P1
p=q p≠q
if p > q // P2 (E, B1, P1, P2, B3, P1, B4, S) p=q p≠q
then P2 (E, B1, P1, B4, S)
p := p-q // B2 P2
p>q p<q
else p>q p<q
Tous les 2-chemins (2 passages dans la boucle) :
q:= q-p // B3 B4 B2 B3
end -- if (E, B1, P1, P2, B2, P1, P2, B2, P1, B4, S) B4 B2 B3
end -- while (E, B1, P1, P2, B2, P1, P2, B3, P1, B4, S)
result:=p // B4 (E, B1, P1, P2, B3, P1, P2, B2, P1, B4, S)
end-- pgcd S (E, B1, P1, P2, B3, P1, P2, B3, P1, B4, S) S
Sortie Graphe de flot
de contrôle
19 20

Forme algébrique associée à un graphe Complexité cyclomatique dans un graphe

• Définitions :
Composants :
− 1 fonction = 1 graphe
ab : séquence a puis b : − 1 exécution = 1 parcours de ce graphe
− Complexité cyclomatique = nb de chemins indépendants

• Permet d’évaluer :
a(b+c) d : choix entre b et c :
− difficulté des tests (couverture)
− cohésion (unité sémantique)

• Ordres de grandeur :
− 1 - 10 : fonction simple, peu de risque d’erreurs
ab (cb)⋆ d : itération :
− 11 - 20 : plus complexe, risques d’erreurs modérés
− 21 - 50 : fonction complexe, risque d’erreurs élevés
− > 50 : fonction non testable
21 22

Chemin de contrôle et forme algébrique Chemins exécutables / non-exécutables


Sensibiliser un chemin de contrôle :
Exécutions possible du code :
une DT sensibilise un chemin de contrôle, si pour ses valeurs, le code suit ce
if (x<=0)
x = -x; ch1 = abdeg chemin de contrôle en visitant consécutivement ses nœuds
else ch2 = acdeg
Chemin exécutable :
x = 1-x; ch3 = abdfg un chemin de contrôle est exécutable s’il existe une DT qui le sensibilise
if (x == -1) ch4 = acdfg
x = 1;
else ch1 = abdeg
x++; le chemin bdfg n’est pas
un chemin de contrôle ch2 = acdeg
ch3 = abdfg
ch4 = acdfg

Sous forme algébrique : DT = {x = −2} sensibilise ch3


DT = {x = 3} sensibilise ch4
abdfg + abdeg + acdfg + acdeg (“+” désigne le “ou” logique)
DT = {x = 2} sensibilise ch2
a(bdf + bde + cdf + cde)g Aucune valeur de x ne sensibilise
a(b + c)d(f + e)g ← expression factorisée des chemins de contrôle ch1 ⇒ chemin non-exécutable

23 24
Critère de couverture d'un graphe Hiérarchie des critères

Critère C : méthode de sélection des DT pour couvrir un ensemble • Nombreux critères :


d’objets (ex. : tous les nœuds / arcs du graphe) − tous les nœuds, les arcs
− tous les chemins (un ou plusieurs passages dans les boucles)
• A tout critère C est associé :
− un taux de couverture, exprimant le degré T de satisfaction de C : • Ces critères ne sont pas équivalents :
− certains demandent plus de DT
nombre d’objets effectivement couverts − certains détectent plus d’erreurs
T=
nombre d’objets dénotés par C

• Le critère C1 est plus fort que le critère C2 si :


• Problème du testeur : − toute faute détectée par C2 l’est par C1
− trouver un ensemble de DT (le plus petit possible) − toute suite de test qui passe pour C2 passe pour C1
− qui satisfont le critère C − un taux de couverture de X pour C1 implique un taux au moins
égal à X pour C2

25 26

Critère « tous les nœuds » Critère « tous les nœuds » : exemple

Critère le plus faible, aussi appelé TER1 (Test


Effectiveness Ratio 1)
− consiste à couvrir l’ensemble des nœuds du graphe
− but du testeur : proposer un ensemble de DT qui couvrent
l’ensemble des nœuds
− signifie que toutes les instructions sont été exécutées au moins
une fois

nombre de nœuds couverts {abce, abde} donne un taux de couverture de 100%


T=
nombre total de nœuds (= satisfait le critère) + détecte l’erreur, avec les DT :
{ {x=2, y=5}, {x=0, y=4} }

27 28

Critère « tous les nœuds » : exemple (2) Critère « tous les arcs »

int div2(x) {
int res = 0; ou TER2 :
if (x != 0)
x = 2*x; − tous les arcs du graphe de contrôle sont couverts
res = 1 / x;
return res; − toutes les branches conditionnelles ont été couvertes
} nombre des arcs couverts
T=
nombre total des arcs

DT : {x=2} satisfait le critère “tous les noeuds” : {iabcd} − chaque prédicat a pris au moins une fois la valeur “vrai” et la
valeur “faux”
− attention aux prédicats composés
Erreur non détectée, chemin révélateur : {iacd}
Il aurait fallu couvrir les deux branches du if : − “Tous les arcs” ⇒ “tous les nœuds”. L’inverse n’est pas vrai

“critère tous les arcs”

29 30
Critère « tous les arcs » : exemple Critère « tous les chemins »
int div2(x) {
int res = 0; • “Tous les chemins” ⇒ “tous les arcs” ⇒ “tous les nœuds”
if (x != 0)
x = 2*x;
res = 1 / x; • Critère impossible à obtenir en général
return res;
} test exhaustif = “tous les chemins avec toutes les valeurs possibles”

DT : { {x=0}, {x=2} } satisfait le critère “tous les arcs”


• Cas des boucles :
if ((x < 2) && (y == x)) − aucun passage dans la boucle
res = 2 – y;
else − chemin intérieur simple : 1 passage dans la boucle
res = y – 2; − chemin intérieur général : au moins 2 passages dans la boucle
(test d'au moins 1 itération de la boucle)
Attention (prédicat composé), DT : { {x=1, y=1}-> VV,
{x=1, y=2}-> VF, {x=3, y=3}-> FV, {x=3, y=2}-> FF}
31 32

Autres exemples CM3 : Tests


i = 1;
found = 0; 1- Développement dirigé par les tests (TDD)
while (!found)
{ – Définitions et constats
if (a[i] == E)
{ – Tests fonctionnels
found = 1; – Tests structurels
s = i;
} – Rédiger les tests avant le code
i++;
} – Hiérarchisation des tests
if (n <= 0)
Donnez leur graphe et leur forme algébrique n = 1 - n;
2- Tests unitaires en Java avec JUnit
if ( n % 2 == 0)
Donnez des DT pour atteindre les critères : n = n / 2;
- tous les nœuds else
- tous les arcs n = 3 * n + 1;
- tous les chemins
33 34

Rédiger les tests avant le code Rédiger les tests avant le code (exemple StringList)

Les cas de test décrivent ce que le programme doit faire (ex : ajout
Avant de réfléchir à « comment faire » : dans une liste chaînée vide) :
− donner l’ensemble des cas d’utilisation, public void testAdd1() {
StringList list = new StringList();
− en déduire la liste des tests à faire et les coder. list.add("first");
assertTrue(list.size() == 1);
}

Tant qu’il reste un test i que la fonction ne valide pas : Puis développer la partie du programme qui fait passer le cas de test :
public void add (String it) {
- rédiger le test i Node node = new Node();
node.setItem(it);
- ajouter le code nécessaire pour que la fonction passe le test i node.setNext(null);
if (firstNode == null) {
node.setPrevious(null);
- revoir le code rédigé et le réorganiser (refactoring)
this.setFirstNode(node);
}
- revalider les tests 1 à i (tests de non-régression) lastNode = node;
this.setCurrentNode(node);
}
35 36
Difficultés et limites du test Hiérarchisation des tests dans le cycle en V

Maintenance
• Difficulté pour le développeur
Problème Plan de tests d’acceptation
− but du développeur : éviter que le système se casse Programme livrable
− buts du test : casser le système Analyse des besoins Tests d’acceptation
Plan de tests système
Spécif. fonctionnelles (fonctionnel) Système
• Le test n’est jamais complet Cahier des charges Tests système

− 1000 erreurs trouvées : bonne ou mauvaise nouvelle ? Plan de tests


Conception globale d’intégration Intégration
− aucune erreur trouvée : test insuffisant ou logiciel robuste ? Tests d’intégration

Plan de tests
Conception détaillée unitaires Composants unitaires

• Le test n’augmente pas la qualité du système Tests unitaires


ou par modules
Implémentation
mais mesure sa qualité
programme

37 38

Tests unitaires Tests d’intégration


Avant de tester le mur, tester ses briques !

• Objectifs :
• Tester une unité logicielle isolée du reste du système :
− vérifier l’interaction entre unités (classes, modules, sous-systèmes)
− plus petit composant compilable (classe en Java)
− test de l’interface (tests fonctionnels : BN) − choisir un ordre pour intégrer (assembler)

− test structurels (BB) des parties les plus importantes − modéliser la structure de dépendances entre le composant et son
environnement (graphe de dépendance des tests)
− doivent être positifs à 100% sur le code passé et courant

• Stratégies possibles : sous -


• Test par mutation : systèmes
− Big-bang : tout est testé ensemble (peu recommandé)
− évaluer l’efficacité d’un ensemble de cas de tests
− Top-down (peu courant)
− injection d’erreurs (mutant) dans le programme sous test
− Bottom-up (la plus classique) modules
− calcul de la proportion d’erreurs détectées (score de mutation)
classes
39 40

Autres tests S’organiser pour tester

• Tests système (de conformité) : adéquation aux spécif. ●


Module contenant les tests indépendant du code « réel »
− test d’un composant depuis ses exigences ( cas d’utilisation)
• Faire des tests indépendants les uns des autres
− possibilité de traduire des diagrammes de séquences en test − pas de tests imbriqués
− fonctions + environnement matériel + performances − un test qui échoue ne fait pas échouer les tests suivants

• Utiliser des frameworks (création des tests rapide et fiable)


• Tests de performance (appli. intégrée dans son environnement)
− JUnit pour les tests unitaires (en Java)
− load testing : résistance à la montée en charge

ne doit tester qu’un seul comportement à la fois,
− stress testing : résistance aux demandes de ressources anormales ●
doit faire le moins d’appel possible aux dépendances, ...
− EasyMock, Mockito, … pour les doublures (en Java)
• Tests d’acceptation (de recette) : avec l’utilisateur final ●
permet de simuler un objet réel (base de données, web service, fichier, …),

recommandé pour fiabiliser les tests unitaires.
− s’assurer de la conformité aux besoins du client et des utilisateurs
− valider le système produit (approbation avant livraison) • Réutiliser les tests et leurs résultats comme documentation
41 42
CM3 : Tests Exemple : comment tester la classe StringList ?
Créer une classe de test :
− qui manipule des instances
1- Développement dirigé par les tests (TDD) de StringList
− avec au moins 8 cas de test
(un par méthode publique)
2- Tests unitaires en Java avec JUnit
− sans accès aux attributs
privés (count, LastNode,
– Framework JUnit ≤ 3.8 CurrentNode, FirstNode)

– Framework JUnit 4
– Notions de doublure, bouchon et mock spécification du cas de test
// test for add: call add and see if current
// element is the one that's been inserted

– Outil de couverture de code public void testAdd2(){


StringList list = new StringList();
initialisation list.add("first");
list.add("second");
appel avec donnée de test list.add("third");
assertTrue(list.size() == 3);
oracle assertTrue(list.item() == "third");
}
43 44

JUnit (www.junit.org) Architecture de JUnit3 : TestCase



Un objet pour concrétiser un cas de test (CT) :
• Framework de test (junit.framework) écrit en Java

unifier instructions System.out.println, debuggage et scripts de tests,
− avantages : open source, simple, intégré à plusieurs IDE

écriture plus aisée, plus facile à manipuler, conserve sa valeur au cours du temps.
− différences entre JUnit3 (≤ 3.8) et JUnit 4

DP Command pour la classe abstraite TestCase :
créer un objet pour faire une opération et lui fournir une méthode « run() »,
• Organisation du code des tests


chaque TestCase possède un nom pour identifier lesquels ont échoué.
− créer un cas de Test : hériter de la classe TestCase
− créer une suite de Tests : hériter de la classe TestSuite
public abstract class TestCase implements Test {
    private final String fName;

• Lancement des tests à tout moment     public TestCase(String name) {


        fname = name;
− grâce au Test Runner (rapport des erreurs produites)     }

− pour s’assurer de la non régression     public abstract void run(); MonCasDeTest


    …

45 Tiré de : http://junit.sourceforge.net/doc/cookstour/cookstour.htm 46

Exemple sur la classe Money Premier test JUnit3 avant d’implémenter


import junit.framework.*;
class Money {
private int fAmount; //somme d’argent public class MoneyTest extends TestCase {
private String fCurrency; //devise //…
public void testSimpleAdd() {
public Money(int amount, String currency) { Money m12CHF = new Money(12, "CHF"); // (1)
fAmount = amount; Money m14CHF = new Money(14, "CHF");
fCurrency = currency;
} Money expected = new Money(26, "CHF");
public int amount() { Money result = m12CHF.add(m14CHF); // (2)
return fAmount; assertTrue(expected.equals(result)); // (3)
} }
public String currency() { }
return fCurrency;
}
1. Code de mise en place du contexte de test (fixture)
public Money add(Money m) {
… 2. Expérimentation sur les objets dans le contexte Structure
} 3. Vérification du résultat = oracle commune
}
4. Eventuellement libération des ressources externes
47 48
Classe TestCase Exemple sur Money
• Rassembler les tests « reliés » dans une même classe
public void testEquals() { MoneyTest
− hérite de JUnit.framework.TestCase Money m12CHF= new Money(12, "CHF");
− indépendance des tests : tous exécutés même si l’un d’eux échoue Money m14CHF= new Money(14, "CHF");
assertFalse(m12CHF.equals(null));
• Définir une méthode publique pour chaque test assertFalse(m12CHF.equals(m14CHF));
assertEquals(m12CHF, m12CHF);
− son nom commence obligatoirement par test
assertEquals(m12CHF, new Money(12, "CHF"));
− aucun paramètre / ne retourne rien / peut lever une exception }

• Pour vérifier les résultats attendus (écrire des oracles !) :


public boolean equals(Object anObject) { Money
− assertTrue(String message, boolean test), assertFalse(…) if (anObject instanceof Money && anObject != null) {
− assertEquals(expected, actual) : test d’égalité avec equals Money aMoney = (Money)anObject;
return aMoney.currency().equals(currency())
− assertSame(…), assertNotSame(…) : tests d’égalité de référence
&& amount() == aMoney.amount();
− assertNull(Object objet), assertNotNull(…) }
− fail() : pour lever directement une AssertionFailedError return false;
}
− assertThat([actualValue], [matcher statement]) : plus général (version 4.4 de JUnit)

49 50

Fixture : contexte commun Test Fixture : exemple


public class MoneyTest extends TestCase {
• Dans les 2 méthodes de test précédentes : code d’initialisation private Money f12CHF;
du contexte de test dupliqué private Money f14CHF;

Money m12CHF = new Money(12, "CHF"); protected void setUp() { // appelée avant chaque méthode de test
f12CHF = new Money(12, "CHF");
Money m14CHF = new Money(14, "CHF"); f14CHF = new Money(14, "CHF");
}
• Pour exécuter les tests dans un état donné, une classe héritant protected void tearDown() { // appelée après chaque méthode de test
f12CHF = null;
de TestCase peut définir deux méthodes spéciales : ...
– méthode setUp() : lancée avant l’exécution de chaque test, }
– méthode tearDown() : lancée après l’exécution de chaque test public void testSimpleAdd() {
(restauration du contexte initial : libération des ressources mémoires). Money expected = new Money(26, "CHF");
Money result = f12CHF.add(f14CHF);
• But principal : résultat d’un test n’influence pas celui d’un autre Assert.assertTrue(expected.equals(result));
}

}
51 52

Exécution des tests Récupération des résultats


• Statiquement • Test simple
− Redéfinir la méthode runTest() :
TestResult result = (new MoneyTest("testSimpleAdd")).run();
TestCase test = new MoneyTest("simple add") { − success : pas d'erreur ni d'exception levées
public void runTest() {
testSimpleAdd(); − failure (echec) : lancement d'une AssertionFailedError
} − error (erreur) : autre exception ou erreur levée
};

• il reste encore à organiser les tests : TestSuite


• Dynamiquement
• puis à lancer et récupérer les résultats de manière
TestCase test = new MoneyTest("testSimpleAdd");
globale : TestRunner
− recherche par réflexivité de la méthode de test de même nom − cherche toutes les classes héritant de TestCase dans le module,
− moins sûr, sauf si tout ceci est généré automatiquement − exécute les méthodes dans un ordre arbitraire,
− compte et transmet les succès, échecs, erreurs.

53 54
Architecture de JUnit3 : TestCase (2) Architecture de JUnit3 : tous les Patterns

DP Template Method pour la classe abstraite TestCase : Package junit.framework

structure commune à tous les tests
public void run() {
(squelette de l’algorithme),     setUp();
    runTest();

implantation par défaut des 3     tearDown();
}
méthodes protégées : vide.


Pattern Collecting Parameter : collecter les résulats des tests

objet TestResult (en paramètre de run) qui collecte les résultats pour nous, fTests
addTest(Test)

mais garder une méthode run() qui créé et renvoie son propre résultat.
public void run(TestResult result) {
     result.startTest(this);
     setUp();
     try {
         runTest(); }
     catch (AssertionFailedError e) {   //déclenchée par les
         result.addFailure(this, e);}  //méthodes assert...
     catch (Throwable e) {               
         result.addError(this, e); }
     finally {
         tearDown();
     }
}
55 Tiré de : http://junit.sourceforge.net/doc/cookstour/cookstour.htm 56

JUnit : TestRunner Nouveautés JUnit 4

• Classe de test
• exécute les test et − n’hérite plus de TestCase mais importe le package org.junit
affiche les résultats
− annotations @Before et @After pour setUp() et TearDown()
• intégration dans les IDE − annotations @BeforeClass et @AfterClass exécutées avant (resp.
après) la première (resp. dernière) méthode de test
• 2 versions (textuelle,
graphique) • Méthode de test
− son nom ne commence plus obligatoirement par test
− annotation @Test : obligatoire sinon le test est ignoré !
Mode textuel : − annotation @Test(expected = Class) : indique l'exception attendue
junit.textui.TestRunner.run (<ma suite de test>); − annotation @Ignore pour ignorer un test
// depuis un main
• Le runner par défaut exécute tous les tests (non ignorés)
junit.textui.TestRunner <ma suite de Test> de la classe
// depuis la ligne de commande
57 58

JUnit 4 : exemple Test de l’envoi d’une exception Java


import org.junit.Test; import org.junit.After; Comment vérifier que des exceptions sont lancées comme prévu ?
import static org.junit.Assert.*; import org.junit.Before;

public class MoneyTest { public void testEmpty(){ en JUnit3


private Money f12CHF; try{
private Money f14CHF; new ArrayList<Object>().get(0); // méthode qui doit
// lancer une exception
@Before fail(« IndexOutOfBoundsException non lancée »);
protected void setUp() { //exécuté avant chaque méthode de test }
f12CHF = new Money(12, "CHF"); catch(IndexOutOfBoundsException e){
f14CHF = new Money(14, "CHF"); } //Ok exception lancée
@After }
protected void tearDown() { ... } //exécuté après chaque méthode } fail : provoque l’échec du test et lance une AssertionFailedError
@Test
public void testSimpleAdd() {
Money expected = new Money(26, "CHF"); @Test (expected = IndexOutOfBoundsException.class) en JUnit4
Money result = f12CHF.add(f14CHF); public void testEmpty(){
assertTrue(expected.equals(result)); new ArrayList<Object>().get(0); paramètre expected
} } de l’annotation @Test
} 59
60
CM3 : Tests Architecture logicielle testable

• Injection des dépendances → couplage faible entre les objets du


1- Développement dirigé par les tests (TDD) système (pas de dépendances à des implantations)

2- Tests unitaires en Java avec JUnit • Avantage du « test d’abord » : meilleure conception en imposant une
architecture testable, sans couplage fort, évolutive
– Framework JUnit ≤ 3.8 – tests a posteriori : on risque de devoir tout casser pour tester

– Framework JUnit 4 – si on réfléchit aux tests a priori : la conception sera testable d’office
– méthodes de conception agiles : laisser émerger la conception au fil du
– Notions de doublure, bouchon et mock développement

– Outil de couverture de code • Ce qui rend le code non testable :


– toute forme d’état global : variables et méthodes statiques
– l’utilisation du DP Singleton

61 62

Tester une classe en isolation : problème Tester une classe non isolée : solution
• Notion de doublure :
• Principe : la tester indépendamment de toutes les classes dont elle dépend
– objets substitués aux objets attendus pour obtenir un comportement qui rend les tests
plus faciles à écrire,
• Test unitaire (pris au pied de la lettre) = test en isolation
– code manipulateur ne s’en rend pas compte et agit comme s’il manipulait un objet
• Si on veut tester une classe A, qui dépend de B : normal (il est trompé),
– en utilisant B dans les tests, c’est du test d’intégration. Si le test échoue, où chercher – analogie : mannequins utilisés lors des essais de choc automobile
l’erreur ? Dans A ou dans B ?
– et que B n’a pas encore été développé, comment tester A ? • Doublure BDouble de l’implantation BImpl, pour le test :
– simule le comportement de BImpl pour la durée du test,
– ou n’est pas encore disponible de façon fiable (application en couches dont les
couches sont développées en parallèle), – c’est un objet du test, pas du domaine,

– ou coûteux à mettre en place (ex. : base de données), – on ne le teste pas, il sert à tester.

– ou difficilement contrôlable (ex. : le réseau, comment créer une panne de réseau ?), • Plusieurs types de doublures :
– ou une situation exceptionnelle (ex. : comment tester un réseau indisponible, lent,
– bouchon ou « stub » : classe écrite à la main. Connaissant le système (boîte blanche),
une déconnexion ?), ...
on en tient compte pour écrire le code le plus simple possible
– simulacre ou « mock » : généré (par l’usage d’un outil dédié) à partir de la classe
originale → il peut donc la remplacer
63 64

Générateurs automatiques de doublures Outil de couverture de code

• Doublures développées à la main : • Définition et outils OpenSource :


– pénibles à écrire, source potentielles d’erreurs, − vérifier dynamiquement (à l’exécution) quelles parties du code à tester ont
– difficiles à maintenir. effectivement été exécutées (couvertes par le test)

• Nombreux frameworks (EasyMock, Mockito) génèrent automatiquement − Cobertura, JCoverage, Jester, CodeCover, Emma, JaCoCoverage, ...
le code des doublures, à partir d’une spécification de leur comportement
• Principaux critères de couverture :
• S’utilisent dans les cas de tests JUnit :
– doublures créées par la méthode « mock », − Function coverage : quelles fonctions/méthodes ont été exécutées
− Statement coverage : quelles lignes de code source
– on décrit le comportement attendu de la doublure,
− Branchcoverage : quelles parties d’un bloc conditionnel (if/else)
– on créé l’objet de la classe à tester en utilisant les doublures,
− Entry/Exit coverage : toutes les possibilités d’invocation et de sortie d’une
– et on vérifie que l’intéraction avec les doublures est correcte.
méthode/fonction ont-elles été utilisées ?
• Ne dispensent pas d’effectuer des tests d’intégration (comment les − Loop coverage : 0, 1, plusieurs exécutions successives d’une boucle
classes interagissent entre elles) − Path coverage : tous les chemins d’exécution ont-ils été empruntés ?

65 66
Conclusion tests : en pratique Livres sur les tests
– Test logiciel en pratique. J. Watkins. Vuibert – 2002
• Coder un peu / tester un peu, coder / tester ... – Test-Driven Development: By Example . K. Beck. Pearson Education – 2003
lancer les tests aussi souvent que possible (autant que le compilateur) – xUnit Test Patterns. Refactoring Test Code. G.Meszaros. Addison Wesley – 2007

• Faut-il tout tester ? – Pragmatic Software Testing. R. Black. Wiley - 2007

il est inutile de tester les méthodes triviales (ex. : méthodes get / set) – Pratique des tests logiciels. Concevoir et mettre en œuvre une stratégie de
tests. J-F. Pradat-Peyre, J. Printz. Dunod – 2009

• Comment tester une méthode sans valeur de retour ? – Tester une application Web. Des tests unitaires à l'homologation (Exemples en
PHP). E. Itié. Editions ENI – 2010
tester le(s) effet(s) de bord
– Industrialiser le test fonctionnel. Pour maîtriser les risques métier et accroître
• Comment tester une interface graphique / web ? l'efficacité du test – 2 e éd. B. Legeard, F. Bouquet, N. Pickaert. Dunod – 2011
– Les tests logiciels : Fondamentaux . B. Homès. Hermès - Lavoisier – 2011
Tests d’acceptation (avec l’utilisateur final) :
– JUnit : Mise en œuvre pour automatiser les tests en Java . B. Gantaume. Eni – 2011
– tests du singe (appui au hasard sur boutons, entrée de données aléatoires)
– Pratique des tests logiciels. Concevoir et mettre en oeuvre une stratégie de tests-
– utiliser un « robot » (simulateur programmable d’actions : http://abbot.sourceforge.net/) Préparer la certification ISTQB, J. Printz, J.-F. Pradat-Peyre. Dunod - 2014
– utiliser des outils spécialisés pour les interfaces web comme Selenium – Gestion des tests logiciels. Bonnes pratiques à mettre en oeuvre pour
(http://docs.seleniumhq.org/) l'industrialisation des tests : tests unitaires, recettes techniques et fonctionnelles, E. Itié.
Editions Eni - 2016
67 68

Vous aimerez peut-être aussi