Tests 1 Lionel Seinturier
Tests
Lionel Seinturier
Université des Sciences et Technologies de Lille
28/11/06
Tests 2 Lionel Seinturier
Plan
1. Introduction 2. Tests unitaires 3. Éléments à tester 4. Anti-patterns de test 5. Test et modularité du code 6. Organisation de projet
1. Introduction
Objectifs du test de programmes
Détecter des erreurs dans un programme
"Testing is the process of executing a program with the intent of finding errors"
[Myers: The Art of Software Testing. 1979]
Autres techniques pour valider un programme
• preuve : modélisation formelle + établissement de théorèmes
• vérification : ex model-checking
Tests : + facile à appréhender, + proche du code, - coûteux à mettre en oeuvre Mais
• ne garantît pas que le programme est exempt d'erreur
• on ne teste que ce à quoi on a pensé
"Program testing can be used to show the presence of bugs, but never to show their absence!" [Edsger Dijkstra]
1. Introduction
Objectifs du test de programmes
Les tests sont importants (∀monde d'accord)
mais les tests tendent à être répoussés à la fin d'un projet
¾plus une erreur est découverte tard, plus elle coûte cher
L'écriture de test n'est une tâche dévolue
¾ni aux utilisateurs, ni aux managers, ni aux chefs de projet
¾c'est vraiment une activité de programmeur
Tests 5 Lionel Seinturier
1. Introduction
Différentes catégories de tests
• boîte blanche
• boîte noire Tests boîte blanche
• on a accès à la structure du code
• ex. : calcul de tous les chemins d'exécution possibles (branches, instructions, …)
• tests sur chacun de ces chemins
• avantage : on peut détecter des situations extrèmes qui ne se manifestent que dans certaines branches
• inconvénients
• calcul des chemins souvent long
• il faut avoir accès au code source
Tests 6 Lionel Seinturier
1. Introduction
Différentes catégories de tests
• boîte blanche
• boîte noire Tests boîte noire
• les + courants
• aucune connaissance des détails d'implémentation n'est nécessaire
• on s'en remet à la spécification
on vérifie que l'implémentation fournit bien le résultat attendu par la spéc
1. Introduction
Différentes granularités de tests
unitaire, intégration, système, recettes Tests unitaires
• test d'une "unité logicielle"
procédural Öprocédure
OO Öméthode
• test indépendant des autres unités
• les + faciles à mettre en oeuvre
• les + fréquents
• nombreux frameworks disponibles
• Java : JUnit, Cactus, TestNG, JTiger, …
• wikipedia : +90 frameworks !! dans 35 langages ≠
1. Introduction
Différentes granularités de tests
Tests d'intégration
• test du comportement d'un ensemble d'unités
• principales techniques : bouchon de tests, mock objects
•≠frameworks : Easy Mock, jMock, Mocquer
s'utilisent en complément/avec les frameworks de tests unitaires Tests système
• tests de l'intégration d'une application complète avec son env. d'exécution (OS, VM, librairies, serveurs, …)
Tests de recettes
• tests effectués en présence du client afin de valider l'application complète
Tests 9 Lionel Seinturier
1. Introduction
Éventail de tests possibles
Tests 10 Lionel Seinturier
1. Introduction
Différentes techniques d'écriture de tests
Écriture manuelle
• par le programmeur, ou celui qui connaît la spécification
• cas le plus fréquent Génération automatique
• "force brute"
• ex : JCrasher
• outil d'évaluation de la robustesse d'un prog.
• génère des séquences aléatoires d'appels de méthodes
• dans le but de détecter celles qui font planter le programme
• par mutation de tests existants
• on fait varier les paramètres des tests existants
• afin de détecter les conditions non couvertes par les tests
• à partir de spécifications formelles du programme
1. Introduction
Écriture de programmes testables
Pour qu'un programme soit "facilement" testable
• il faut (souvent) prévoir l'accès à l'état Öajout de setter/getter
• il faut éviter au maximum les effets de bord
ex. : lecture au clavier, écriture à l'écran, dans un fichier Öséparer en 2 parties
• une méthode avec la fonctionnalité à tester
• une méthode avec les effets de bord qui appelle la précédente Ösouvent refactorisation de l'application pour accéder éléments à tester Ömieux : penser à la testabilité en écrivant le programme
1. Introduction
Sélection du code à tester
• tester les conditions aux limites
• paramètre null, tableau vide, valeurs max, min, …
• tester les "grandes catégories" (classes d'équivalence) de comportements
• dans tels cas, la méthode lève une exception
• dans tels cas, la méthode retourne null
• dans tels cas, la méthode retourne une valeur particulière
• tester les invariants du programme
• propriété à conserver après chaque opération de modification
•≠catégories
• structurels : ex. : index d'une liste ∈[0..taille_liste]
• sémantiques : ex. : une liste (auto)-triée doit rester triée
• cohérence de données
• ex. : ∑dépôts + ∑retraits = solde
• ex. : prix TTC = prix HT * TVA
Tests 13 Lionel Seinturier
1. Introduction
Sélection du code à tester
• tester les invariants du programme
• cohérence de données
• ex. : même donnée stockée à≠endroits du programme dans la même unité ou des unités (cm, inch) ≠
• intégrité référentielle
• ex. : vérifier que la bidirectionalité dans une association est conservée
• tester que les préconditions des méthodes sont respectées
• tester que les postconditions des méthodes sont respectées
Tests 14 Lionel Seinturier
1. Introduction
Sélection du code à tester
Ölien avec la notion d'assertion / contrat
• notion introduite dans le langage Eiffel [Meyer 85]
¾programming / design by contract
•≠mises en oeuvre en Java
• instruction assert(à partir JDK 1.4)
• frameworks Contract4J, Jass, jContractor, JML, … Contrats vs tests
• contrats
• intrusifs (certes débraillables) : vérifier sur une application qui s'exécute
• utiles si les conditions d'exécution sont difficilement reproductibles
• tests
•off-line
• ne modifient pas l'application
• prise en compte + facile de nombreux scenarii d'exécution
1. Introduction
Sélection du code à tester
• on n'écrit pas forcément 1 test / méthode du programme
• pas de test pour
• setter/getter
• méthode de 2 lignes, …
• ce que l'on cherche à tester
• les "services" rendus par une classe
1. Introduction
Mesure de la qualité des tests
Notion de couverture de code (coverage)
Ö% de code couvert par des tests (unitaires, intégration, …)
Öon cherche à vérifier que chaque ligne d'un programme est couvert par au moins un test
Öne garantît pas la correction
Öindicateur sur le "sérieux" d'un ensemble de tests
≠formes
• % de méthodes, branches, lignes, instructions
≠outils
• Cobertura, EMMA, Patchwork, Quilt, …
Tests 17 Lionel Seinturier
2. Tests unitaires
Test unitaire
Code qui évalue le comportement d'une fonctionnalité
¾la fonctionnalité testée est souvent de granularité fine
¾accumulation de tests de "petites portions" d'un programme Exemples
fonctionnalité : ajouter une valeur à une liste (auto)-triée test : vérifier qu'une "grande" valeur est bien placée à la fin fonctionnalité : supprimer un pattern dans une chaîne de caractères test : vérifier que le pattern n'y est plus
¾"prouve" que la fonctionnalité fait ce qu'elle est censée faire
¾augmente la confiance dans la correction de la fonctionnalité
¾on est plus enclin à la réutiliser
¾facilite l'intégration
Tests 18 Lionel Seinturier
2. Tests unitaires
Test unitaire
comportement : code
test : code
Code du test
• plus petit que le code du comportement à tester
• ne contient pas de complexité algorithmique (source d'erreurs)
• souvent de la forme
• appel méthode + paramètres d'entrées
• vérification résultat attendu est bien le bon
2. Tests unitaires
Questions auxquelles permettent de répondre des tests unitaires
• est-ce que mon code fait ce que je veux qu'il fasse ?
• est-ce qu'il le fait dans toutes les conditions ?
• pas seulement dans le cas nominal standard
• mais aussi en présence d'exception, de buffer overflow, …
• aux conditions limites(valeurs, charge, …)
• avantage collatéral des tests
¾ils traduisent l'intention du programmeur
≈spécification light
Plan
1. Introduction
2. Tests unitaires
3. Éléments à tester
4. Anti-patterns de test
5. Test et modularité du code
6. Organisation de projet
Tests 21 Lionel Seinturier
3. Éléments à tester
Que tester ?
• "tout ce qui est susceptible de provoquer un plantage" …
• principe Right-BICEP
• Right : est-ce que les résultats sont corrects ?
• B (Boundary) : est-ce que les conditions aux limites sont correctes ?
• I (Inverse) : est-ce que l'on peut vérifier la relation inverse ?
• C (Cross-check) : est-ce que l'on peut vérifier le résultat autrement ?
• E (Error condition) : est-ce que l'on peut forcer l'occurrence d'erreurs ?
• P (Performance) : est-ce que les performances sont prévisibles ?
Tests 22 Lionel Seinturier
3. Éléments à tester
Right
• validation des résultats en fonction de ce que définit la spécification
• on doit pouvoir répondre à la question
• comment sait-on que le programme s'est exécuté correctemment ?
• si pas de réponse Öspécifications certainement vagues, incomplètes
• la notion de correction peut évoluer au cours du temps
• en fonction de l'évolution des besoins, des spécifications, …
• tests = traduction des spécifications
3. Éléments à tester
Boundary conditions
• identifier les conditions aux limites de la spécification
• que se passe-t-il lorsque les données sont
• anachroniques ex. : !*W@\/"
• non correctement formattées ex. : fred@foobar.
• vides ou nulles ex. : 0, 0.0, "", null
• extraordinaires ex. : 10000 pour l'age d'une personne
• dupliquées ex. : doublon dans un Set
• non conformes ex. : listes ordonnées qui ne le sont pas
• désordonnées ex. : imprimer avant de se connecter
• principe "CORRECT"
• conformance, ordering, range, reference, existence, cardinality, time
3. Éléments à tester
Boundary conditions
Conformance
• données doivent souvent respecter un format bien particulier
• format de fichier, email (voir RFC 822), URL, …
• tester les cas où les données ne respectent pas le format Ordering
• l'ordre des données dans une structure peut avoir de l'importance
• tester les cas où l'ordre n'est pas respecté Range
• situation où une donnée a moins de valeurs légales que son type
• ex. int angle Ö0..359
• invariants : le # de données dans un buffer fini ∈tjrs à [0..max]
• tester les valeurs qui sont en dehors de l'intervalle
Tests 25 Lionel Seinturier
3. Éléments à tester
Boundary conditions
Reference
• les dépendances de la méthode à tester avec le reste de l'application
• pré et post-conditions
• ex. : ne pouvoir faire pop() sur une pile vide
• cf. les diagrammes états/transitions
• tester ce qui n'est pas dans le diagramme Existence
• que se passe-t-il lorsque la valeur attendue n'existe pas ?
• pas de Client associé à une Facture Cardinality
• que se passe-t-il avec les valeurs "remarquables" ?
• 0, 1, -1, Integer.MAX_VALUE, Integer.MIN_VALUE, …
Tests 26 Lionel Seinturier
3. Éléments à tester
Boundary conditions
Time
• les cas où l'ordre dans le temps à de l'importance
• ex. : pas de logout() avant login()
3. Éléments à tester
Inverse – Cross-check
Identifier
• les relations inverses
• les algorithmes équivalents (cross-check) qui permettent de vérifier le comportement
public void testSquareRootUsingInverse() { double x = mySquareRoot(4.0);
assertEquals( 4.0, x*x, 0.0001 );
}
public void testSquareRootUsingStd() { double x1 = mySquareRoot(4.0);
double x2 = Math.sqrt(4.0);
assertEquals( x2, x1, 0.0001 );
}
3. Éléments à tester
Error condition – Performance
Que se passe-t-il en cas de
• disque, mémoire, … plein
• perte de connexion réseau
Est-ce que le système réagit de façon non exponentielle à une montée en charge ? ex. : vérifier qu'un élément n'est pas dans une liste
• vérifier que le temps est linéaire avec la taille de la liste
• application continue de répondre en présence d'une surcharge du système ?
¾quasiment un domaine à part entière
¾nombreux frameworks d'injection de charge et d'identification des parties d'application coûteuses (Eclipse TPTP, ObjectWeb, CLIF, …)
Tests 29 Lionel Seinturier
Plan
1. Introduction 2. Tests unitaires 3. Éléments à tester 4. Anti-patterns de test 5. Test et modularité du code 6. Organisation de projet
Tests 30 Lionel Seinturier
4. Anti-patterns de test
Anti-patterns de test
Anti-pattern
• le contraire d'un pattern
• les mauvaises pratiques à éviter
• assert manquant
• asserts multiples
• utilisation du mauvais assert
• test trop compliqué
• dépendances externes
• récupération d'exceptions non prévues
4. Anti-patterns de test
Anti-patterns de test
Assert manquant
• un test qui ne teste rien Öinutile dans 99% des cas
• 1% : pas de résultat attendu, mais vérification qu'il n'y a pas d'exception
• utilité : non regression par rapport à un bug précédent
Asserts multiples
• pas une bonne idée
• les tests doivent rester courts
• + facile à comprendre
• + facile d'identifier les causes des erreurs détectées par les tests
• - de risque d'erreur dans les tests
• si besoin de partage de code entre parties à tester ÖsetUp
4. Anti-patterns de test
Anti-patterns de test
Asserts multiples
public void testSomething() { // ...
assertTrue(condition1);
assertTrue(condition2);
assertTrue(condition3);
}
public void setUp() { // ...
}
public void testSomething1() { assertTrue(condition1);
}
public void testSomething2() { assertTrue(condition2);
}
public void testSomething3() { assertTrue(condition3);
}
Tests 33 Lionel Seinturier
4. Anti-patterns de test
Anti-patterns de test
Utilisation du mauvais assert
• appel à assertTrue avec un true codé en dur : non
assertTrue( "Object must be the same", expected == actual );
assertTrue( "Objects must be equals", expected.equals(actual) );
assertTrue( "Object must be null", actual == null );
assertTrue( "Object must be non null", actual != null );
• utiliser l'assert correspondant
assertSame( "Object must be the same", expected, actual );
assertEquals( "Objects must be equals", expected, actual );
assertNull( "Object must be null", actual );
assertNotNull( "Object must be non null", actual );
¾plus clair
¾précise l'intention du testeur
Tests 34 Lionel Seinturier
4. Anti-patterns de test
Anti-patterns de test
Test trop compliqué
• il ne faut pas avoir à tester le test pour se convaincre qu'il est bon
Dépendances externes
• éviter les dépendances vers l'environnement
• SGBD, fichiers, librairies, socket, …
• il ne faut qu'il y ait des éléments trop compliqués à mettre en place Ösinon les tests ne seront pas exécutés
¾utiliser des mocks pour remplacer les librairies
¾faire en sorte d'inclure toutes les données nécessaires avec les tests
¾s'il faut vraiment un SGBD, envisager l'emploi de SGBD en mémoire (par ex. HSQLDB)
4. Anti-patterns de test
Anti-patterns de test
Récupération d'exceptions
• code "normal" Örécupérer les exceptions
• tests Önon
public void testCalculation() { try {
deepThought.calculate();
assertEquals("Calculation wrong", 42, deepThought.getResult());
}
catch(CalculationException ex) {
Log.error("Calculation caused exception", ex);
} }
¾raté ! JUnit ne détectera pas que le test a échoué
4. Anti-patterns de test
Anti-patterns de test
Récupération d'exceptions
• 2ème essai
public void testCalculation() { try {
deepThought.calculate();
assertEquals("Calculation wrong", 42, deepThought.getResult());
}
catch(CalculationException ex) {
Log.error("Calculation caused exception", ex);
fail("Calculation caused exception");
} }
¾encore raté !
¾test échoue effectivement mais on perd la source de l'erreur (StackTrace)
Tests 37 Lionel Seinturier
4. Anti-patterns de test
Anti-patterns de test
Récupération d'exceptions
• la bonne solution
public void testCalculation() throws CalculationException { deepThought.calculate();
assertEquals("Calculation wrong", 42, deepThought.getResult());
}
¾les exceptions sont ce qui est recherché par les tests
Tests 38 Lionel Seinturier
Plan
1. Introduction 2. Tests unitaires 3. Éléments à tester 4. Anti-patterns de test 5. Test et modularité du code 6. Organisation de projet
5. Test et modularité du code
Concevoir/refactoriser en vue du test
Séparer les préoccupations Une préoccupation
• un ensemble de fonctionnalités ayant un lien logique entre elles
• notion
• floue : plus un principe, une "idée" qu'une définition formelle
• variable : en fonction des développeurs, des besoins, de l'évolution Exemple
• application de gestion de clientèle
• ajouter/supprimer clients
• gérer données client
• affichage graphique GUI
• sauver/restaurer les données dans un fichier
5. Test et modularité du code
Concevoir/refactoriser en vue du test
Séparer les préoccupations
Tests 41 Lionel Seinturier
5. Test et modularité du code
Concevoir/refactoriser en vue du test
Séparer les préoccupations
• pouvoir tester les différentes parties sans avoir à se préoccuper du reste
• plus le programme est modulaire
• plus il est facile à appréhender, concevoir, tester
• plus les erreurs sont facilement identifiables
• écriture des tests
¾occasion de refactoriser
¾bénéficie aussi en terme de conception
Tests 42 Lionel Seinturier
6. Organisation de projet
S'organiser pour faciliter la testabilité
• 1 classe de test par classe à tester
• nom des classes de test
• le même que la classe à tester préfixé par Test
• les classes de test
• dans le même package
• mais dans des hiérarchies de répertoire //
src/org/foo/MaClasse.java test/org/foo/TestMaClasse.java
• fréquence d'exécution des tests
• chaque fois qu'une nouvelle classe / méthode significative est ajoutée
• à chaque résolution de bug
• en cas de travail collabortif (CVS/SVN, …)
• à chaque récupération des mises à jour à partir du référentiel
• éventuellement périodiquement (gros projets)
6. Organisation de projet
Tests dits de non régression
• chaque découverte de bug doit donner lieu à l'écriture d'un test caractérisant le bug
¾objectif : faire en sorte que le bug ne réapparaisse pas ultérieurement (non régression)
Tests et logiciels patrimoniaux (legacy)
• a priori non conçus pour les tests
• écrire des tests
• pour les fonctionnalités critiques
• pour les fonctionnalités dont on sait qu'elles sont mal écrites (donc les + susceptibles de provoquer des erreurs)
• en fonction des bugs découverts (non regression)
Bibliographie
A. Hunt, D. Thomas. Pragmatic Unit Testing. 2004.
G. Myers, C. Sandler, T. Badgett, T. Thoma. The Art of Software Testing.
2nd Edition.
http://en.wikipedia.org/wiki/Software_testing http://www.junit.org
http://www.easymock.org http://jakarta.apache.org/cactus