Accueil > Programmer avec Java >
đ ïž Tests unitaires
- Cas de test
- Approche naĂŻve pour les tests unitaires
- JUnit - framework Java pour les tests unitaires
- Exemple de test unitaire avec JUnit
Survol et attentes
Dans un programme dĂ©composĂ©, câest important de tester chaque mĂ©thode avant de lâintĂ©grer dans une mĂ©thode appelante. Ainsi, si une erreur se produit, on limite la recherche de lâerreur Ă une seule mĂ©thode.
Définitions
- Test unitaire
- vĂ©rifier si une mĂ©thode produit le rĂ©sultat attendu pour un ensemble dâentrĂ©es spĂ©cifiques. Un test unitaire Ă©value une seule mĂ©thode, dâoĂč le terme âunitaireâ.
- Cas de test
- un scĂ©nario (une entrĂ©e spĂ©cifique) qui doit ĂȘtre testĂ© pour une mĂ©thode. Chaque cas de test fait le lien entre les entrĂ©es et les sorties attendus. Un test unitaire inclut gĂ©nĂ©ralement tous les cas de test identifiĂ©es pour une mĂ©thode.
- JUnit
- un framework de test unitaire pour Java. Nous utiliserons la suite dâoutils Java dans VS Code pour installer et exĂ©cuter les tests JUnit. De plus, ces outils crĂ©ent automatiquement les fichiers de test et les signatures des mĂ©thodes pour chaque test unitaire.
- Framework
- un ensemble dâoutils et de conventions qui facilitent le dĂ©veloppement de logiciels. Un framework est plus vaste quâun package ou une module de code, et la plupart des applications logicielles sont bĂątis Ă lâaide de frameworks. JUnit est un framework pour les tests unitaires.
- Test dâentrĂ©e/sortie
- test unitaire qui doit tenir compte des valeurs normalement saisies via lâentrĂ©e standard et affichĂ©es Ă la console. Ces tests sont plus difficiles Ă automatiser parce quâil faut temporairement rediriger lâentrĂ©e et/ou la sortie standard vers des sources de texte Ă©crites Ă lâavance dans le test.
Objectifs dâapprentissage
Ă la fin de cette leçon vous devrez ĂȘtre en mesure de :
- dĂ©crire câest quoi un test unitaire et pourquoi on les utilise.
- savoir comment choisir des cas de test pour une méthode donnée.
- savoir comment utiliser les outils dans VS Code pour créer et exécuter des tests unitaires pour des programmes Java.
CritĂšres de succĂšs
ĂvaluĂ©s sommativement dans ce cours :
- Je suis capable de rĂ©diger une liste de cas de test pour une mĂ©thode donnĂ©e et dâintĂ©grer ces cas dans le test unitaire.
- Je suis capable dâadapter le gabarit de test dâĂ©galitĂ© pour mes mĂ©thodes.
Aspirationnels - on nâa pas assez de temps dans ce cours pour devenir vraiment bons Ă ces compĂ©tences, mais on peut les explorer :
- Je suis capable de créer un fichier de test et les méthodes de test pour mes programmes en utilisant les outils de JUnit dans VS Code.
- Je suis capable dâadapter le gabarit de test dâentrĂ©e et de test de sortie pour mes mĂ©thodes.
Pourquoi faire des tests unitaires?
Un programme devient trÚs rapidement large et complexe. On utilise déjà la décomposition pour gérer la complexité et la taille du programme : elle nous permet de résoudre une série de petits problÚmes.
Par contre, lâavantage de la dĂ©composition est largement gaspillĂ© si on ne vĂ©rifie pas le bon fonctionnement de chaque mĂ©thode - chaque morceau du problĂšme dĂ©composĂ© - avant de lâintĂ©grer dans la mĂ©thode plus haut dans la chaĂźne dâappels. La raison est la suivante : sâil y a une erreur dans une mĂ©thode quâon nâa pas testĂ© mais on lâintĂšgre sans connaissance dans une autre mĂ©thode, lorsque on exĂ©cute Ă©ventuellement le programme, on ne sait pas si lâerreur vient de la mĂ©thode appelante ou de la mĂ©thode appelĂ©e. Ce problĂšme explose avec le nombre dâintĂ©grations de mĂ©thodes quâon fait avant de tester.
Avec les tests unitaires, on peut isoler les erreurs Ă une seule mĂ©thode, limitant la quantitĂ© de code Ă vĂ©rifier pour corriger lâerreur. Le but est de tester chaque mĂ©thode et de sâassurer de son fonctionnement avant de lâintĂ©grer dans une autre mĂ©thode. Ainsi, si une erreur se produit, on sait quâelle vient du plus rĂ©cent code quâon a Ă©crit et non des autres mĂ©thodes qui existent dĂ©jĂ .
Un autre avantage des tests unitaires formelles, comme ceux avec le framework JUnit, est que si on modifie le programme, on peut relancer tous les tests que nous avons dĂ©jĂ Ă©crits pour sâassurer que les modifications nâont pas cassĂ© quelque chose qui fonctionnait dĂ©jĂ .
Cas de tests
En faisant un test unitaire, on doit savoir quel comportement est attendu de notre mĂ©thode, soit quâest-ce qui constitue un rĂ©sultat acceptable. Les cas de tests sont simplement la description de ces comportements attendus pour une mĂ©thode donnĂ©e. Câest, en fait, la partie la plus importante de lâĂ©criture des tests unitaires. Si on ne sait pas ce quâon attend de notre mĂ©thode, on ne peut pas Ă©crire de tests pour vĂ©rifier si la mĂ©thode fonctionne correctement.
Déterminer quels sont les cas de test importants est un art. Il faut trouver un équilibre entre tester tous les cas possibles et tester seulement les cas les plus importants. Voici quelques pistes pour déterminer les cas de test pour une méthode :
- Cas de test limites : les cas de test qui couvrent les valeurs aux limites de ce qui serait normalement attendu. Par exemple, si on doit donner une note entre 0 et 100, on devrait tester avec 0 et 100. De mĂȘme, si dans la zone des valeurs acceptĂ©s, il y a des limites pour diffĂ©rentes catĂ©gories, on devrait tester ces limites aussi. Par exemple, si la note de passage est de 50, on devrait tester avec 49 et 50.
- Cas de test invalides : si notre programme doit gĂ©rer la qualitĂ© des entrĂ©es, il faut aussi prĂ©voir les cas de test qui couvrent les valeurs qui ne devraient pas ĂȘtre acceptĂ©es. Par exemple, si on doit donner une note entre 0 et 100, on devrait tester avec -1, 101, et des lettres.
Tableau de cas tests
Pour lâexemple prĂ©cĂ©dent, soit dâune mĂ©thode qui calcule la moyenne de deux notes et retourne le rĂ©sultat comme valeur de retour, on peut prĂ©parer le tableau de cas de test suivant :
| Intention | Entrée | Sortie attendue |
|---|---|---|
| deux notes int valides | 80, 90 | 85.0 |
| deux notes double valides | 80.0, 90.0 | 85.0 |
| notes limites | 0, 100 | 50.0 |
| notes limites | 0, 0 | 0.0 |
| notes limites | 100, 100 | 100.0 |
| note négative | -1, 90 | -1.0 // une valeur drapeau pour signaler un résultat invalide |
| note supérieur à 100 | 80, 101 | -1.0 // une valeur drapeau pour signaler un résultat invalide |
On peut reprĂ©senter ce mĂȘme tableau comme commentaire de bloc ou comme javadoc dans la mĂ©thode de test unitaire, comme ceci :
/*
* Test la méthode `average` pour les cas suivants :
* - deux notes int valides : 80, 90 => 85.0
* - deux notes double valides : 80.0, 90.0 => 85.0
* - notes limites : 0, 100 => 50.0
* - notes limites : 0, 0 => 0.0
* - notes limites : 100, 100 => 100.0
* - note négative : -1, 90 => -1.0 // une valeur drapeau pour signaler un résultat invalide
* - note supérieur à 100 : 80, 101 => -1.0 // une valeur drapeau pour signaler un résultat invalide
*/
Approche naĂŻve pour les tests unitaires
Sans utiliser des outils spĂ©cialisĂ©s, nous pouvons simplement lancer manuellement le programme aprĂšs avoir ajoutĂ© une nouvelle mĂ©thode. Naturellement, on aura probablement appelĂ© la mĂ©thode dans la logique globale du programme pour voir si elle fonctionne. Ăa marche pour des programmes trĂšs simples mais câest limitĂ© mĂȘme dans ces cas :
- Sâil y a plusieur cas de test, on doit les exĂ©cuter un par un et comparer les rĂ©sultats manuellement, possiblement en insĂ©rant des
System.out.println()pour afficher les rĂ©sultats quâon aura Ă retirer plus tard. Cela est une tĂąche lourde et sujette Ă des erreurs. - Si plusieurs parties du programme doivent sâexĂ©cuter avant dâarriver Ă lâappel de la nouvelle mĂ©thode Ă tester, on doit passer Ă travers tout le programme pour chaque test. Cela est une perte de temps et dâĂ©nergie qui fait en sorte quâon Ă©vite souvent de tester rigoureusement les mĂ©thodes.
Mieux mais sans les outils de test unitaire
Pour faire mieux que lâapproche dĂ©crite ci-dessus, on peut tenter dâĂ©crire des tests unitaires sans lâappui des outils, notamment parce que lâinstallation des outils peut ĂȘtre complexe et parce que ça nous Ă©vite dâapprendre comment utiliser les outils. En Ă©crivant nos propres tests, on peut dĂ©finitivement pallier aux deux problĂšmes soulevĂ©s ci-dessus :
- on peut inclure les cas de test dans notre code de test
- on peut inclure les comparaisons automatiques avec les résultats attendus dans notre code de test
- on peut exécuter les tests unitaires sans passer par la logique globale du programme
Par contre, tout ça exige la rĂ©daction de beaucoup de code additionnel (avec lâintroduction presque garantie de nouvelles erreurs). Et tout ce nouveau code est probablement mieux Ă©crit et dĂ©finitivement dĂ©jĂ validĂ© dans les outils de test unitaire.
De plus, le code de test âmaisonâ est le plus facile Ă Ă©crire et Ă lancer dans la mĂȘme classe que les mĂ©thodes Ă tester. Cela rend le code dans cette classe plus difficile Ă lire, sans parler de la mĂ©thode main qui peut commencer Ă ressembler Ă quelque chose comme ceci :
Fichier:
MyGreatProgram.java
void main() {
// tests unitaires
// testMethod1(); // masqué derriÚre un commentaire pour ne pas l'exécuter
// testMethod2();
testMethod3();
// logique globale du programme
// ...
}
void method1() {
// ... code qu'on veut utiliser dans le programme
}
void testMethod1() {
// ... code pour tester la méthode qu'on veut utiliser dans le programme
}
// ... reste des méthodes de la classe
Approche standard pour les tests unitaires avec JUnit 4
En se servant des outils existants, comme JUnit :
- notre classe peut ĂȘtre Ă©crite exactement comme on lâa dĂ©crit avec la dĂ©composition du problĂšme sans code additionnel,
- les tests peuvent ĂȘtre Ă©crits plus succinctement avec le code fourni par JUnit,
- le lancement des tests est entiÚrement indépendant du lancement de notre programme et
- nous nâavons pas besoin de programmer comment afficher les rĂ©sultats des tests parce que JUnit le fait pour nous.
Lâutilisation de JUnit nous impose pour la premiĂšre fois une structure de projet Java qui dĂ©passe un seul fichier :
- le fichier pour notre code et
- un autre fichier pour les tests unitaires.
La section suivante dĂ©crit la modification quâil faut apporter Ă nos programmes pour les tester avec JUnit.
Structure de projet pour les tests unitaires
On a vu dans une leçon sur les bases de Java que ce langage est utilisĂ© pour dâĂ©normes projets logiciels et que le code peut ĂȘtre divisĂ© en plusieurs types dâunitĂ©s autonomes du plus grand (les logiciels et les frameworks) aux unitĂ©s intermĂ©diaires (les modules et les packages) et finalement aux unitĂ©s atomiques (les classes).
Parce que tous nos programmes jusquâĂ prĂ©sent tenaient dans une seule classe, le compilateur Java nous permettait dâomettre une dĂ©claration de classe et de simplement Ă©crire des dĂ©clarations pour nos mĂ©thodes et nos variables globales.1 De plus, on nâavait pas Ă se prĂ©occuper des mots-clĂ©s qui sont utilisĂ©s pour gĂ©rer la visibilitĂ© des Ă©lĂ©ments de notre classe (public, private) ni de comment les mĂ©thodes sont appelĂ©es (static ou non). On ne travaillera pas avec ces concepts dans ce cours (ils sont couverts dans le cours de 12e annĂ©e), mais on doit quand mĂȘme ajouter une chose Ă notre code pour le tester avec JUnit : une dĂ©claration de classe.
La dĂ©claration de classe donne un nom Ă notre code et permet au code dans la classe test dây rĂ©fĂ©rer pour utiliser nos mĂ©thodes.
Ainsi, si on a un fichier Calculator.java avec le contenu suivant :
int add(int a, int b) {
return a + b;
}
void main() {
System.out.println(add(1, 2));
}
et on veut tester la méthode add, on doit ajouter une déclaration de classe autour de notre code pour le tester avec JUnit :
class Calculator {
int add(int a, int b) {
return a + b;
}
void main() {
System.out.println(add(1, 2));
}
}
- Utiliser lâoutil âMettre le document en formeâ dans VS Code pour rĂ©tablir une bonne indentation aprĂšs avoir ajoutĂ© la dĂ©claration de classe (nâoubliez pas son accolade fermante Ă la fin du code).
- Le nom de la classe doit ĂȘtre le mĂȘme que le nom du fichier et on devrait respecter les conventions Java pour les noms de classe/fichier : commencer par une majuscule et utiliser la casse chameau pour les noms composĂ©s.
Pour renommer le fichier, au besoin, utiliser lâoutil âRenommer le fichierâ dans VS Code parce quâil renommera automatiquement le nom de la classe et toutes les rĂ©fĂ©rences Ă cette classe Ă travers le projet.
Maintenant, le code dans la classe de test pourra créer un objet de type Calculator pour tester la méthode add(), comme on ajoute des objets de type Scanner pour utiliser ses méthodes next*().
Travailler avec JUnit dans VS Code et l'Extension Pack for Java
On va utiliser pour la premiĂšre fois lâoption âSource Actionsâ dans VS Code pour crĂ©er des tests unitaires. Cette section vous montre comment le faire Ă©tape par Ă©tape.
ĂTAPE 1 : CrĂ©ez votre fichier dans un projet Java et Ă©crire au moins une de ses mĂ©thodes. Pour cet exemple, on peut utiliser le fichier Calculator.java avec le contenu suivant :
class Calculator {
int add(int a, int b) {
return a + b;
}
void main() {
System.out.println(add(1, 2));
}
}
- ĂTAPE 2
-
Ouvrir le fichier dans VS Code et attendre une minute afin de laisser les outils Java sâactiver.
- ĂTAPE 3
-
Faites un clic droit nâimporte oĂč dans le fichier et choisissez âSource ActionâŠâ. Vous devriez voir une option pour
Generate Tests. Cliquez dessus.
| 
- ĂTAPE 4
-
Si câest la premiĂšre fois quâon fait ces Ă©tapes dans un projet, VS Code vous donnera une erreur et un bouton
Enable tests. Cliquez dessus et choisir le frameworkJUnit. VS Code installera automatiquement les dépendances nécessaires pour JUnit dans votre projet, dans le sous-dossierlib.
|
| 
Note : vous aurez à faire cette étape une seule fois par projet, mais vous aurez à la refaire si vous créez un nouveau projet.
- ĂTAPE 5
-
Tapez
Enterpour acceptez le nom proposĂ© pour la classe de test, gĂ©nĂ©ralement[nom de ma classe]Test. Dans notre exemple, ce seraitCalculatorTest. - ĂTAPE 6
-
Sélectionnez la méthode que vous voulez tester. Dans notre exemple, on veut tester
add. Cliquez surEnterpour accepter. La classe sera créée avec la signature de la mĂ©thode de test pouradd. Cochez seulement la plus rĂ©cente mĂ©thode, celle qui nâa pas encore de test unitaire.
| 
Notez quâon ne doit pas tester
mainparce quâil contient la logique globale du programme.mainnâest pas une âunitĂ©â mais plutĂŽt âlâintĂ©grationâ ultime de toutes les unitĂ©s de notre programme. Ăa ne fait pas de sens de prĂ©parer des tests unitaires pour cette mĂ©thode. - ĂTAPE 7
-
Ăcrivez les tests unitaires pour les mĂ©thodes choisies. Câest Ă cet Ă©tape quâon doit considĂ©rer les cas de test et les implĂ©menter.
Les sections suivantes donnent des gabarits de tests unitaires que vous pouvez utiliser pour écrire vos tests.
- ĂTAPE 8
-
Exécutez les tests unitaires en cliquant sur le bouton
Run Testà cÎté de la méthode de test ou en cliquant sur le boutonRun All Testsen haut de la classe de test.
Notez que sâil y a une seule classe dans votre projet qui contient des erreurs de syntaxe, vous recevrez un message dâerreur avant le lancement des tests parce que les outils Java compilent tout votre projet. Si lâerreur nâest pas dans la classe Ă tester ni dans la classe avec les tests, vous pouvez simplement cliquer sur le bouton
Continuepour ignorer ces erreurs et lancer les tests.
- ĂTAPE 9
-
Analysez les rĂ©sultats des tests dans la fenĂȘtre de sortie de JUnit. Si les tests sont tous rĂ©ussis, rien ne sâaffichera et vous devrez vous rendre Ă lâonglet âTest Resultsâ pour voir la sortie. Si un test Ă©choue, vous verrez un message dâerreur dans la fenĂȘtre de sortie.

Notez que la sortie texte dans la partie gauche ne dit pas grand-chose dâutile. Câest plutĂŽt la partie droite qui donne lâĂ©tat de chacun des tests. Le crochet vert est pour un test rĂ©ussi, le point rouge est pour un test Ă©chouĂ©. Vous pouvez cliquer sur le rĂ©sultat de chaque test pour plus de dĂ©tails ou pour les lancer de nouveau.
ĂTAPE 10 : Corrigez les erreurs dans votre code et rĂ©exĂ©cutez les tests jusquâĂ ce quâils passent tous. Les erreurs peuvent se trouver dans le test unitaire ou dans la mĂ©thode que vous testez⊠mais pas ailleurs! Câest lâavantage de faire des tests unitaires pour chaque mĂ©thode quâon finit dâĂ©crire.
ĂTAPE 11 : RĂ©pĂ©tez les Ă©tapes 3 Ă 10 pour chaque nouvelle mĂ©thode que vous Ă©crivez dans la classe principale du projet.
Gabarits - code de démarrage pour vos tests unitaires avec JUnit
Votre responsabilité principale en lien avec les tests unitaires est la définition des cas de tests pour chaque méthode que vous écrivez.
Par contre, vous pouvez aussi apprendre comment implémenter les tests unitaires qui appliquent ces cas de test à vos méthodes. Pour vous aider, les exemples de tests unitaires ci-dessous vous donnent du bon code de démarrage pour quatre cas communs :
- un test dâĂ©galitĂ©,
- un test dâentrĂ©e (avec un Scanner global),
- un test dâentrĂ©e (avec un Scanner local) et
- un test de sortie.
Selon la structure de vos mĂ©thodes, vous aurez probablement Ă combiner le code de tests diffĂ©rents, p. ex. dâĂ©galitĂ© et dâentrĂ©e ou dâĂ©ntrĂ©e et de sortie pour avoir la bonne structure de test pour vos mĂ©thodes.
Structure de la classe de test
Voici un gabarit de base pour une classe de test unitaire oĂč vous remplacerez âMyClassnameâ par le nom de la classe que vous testez. Vous pouvez ajouter autant de mĂ©thodes de test que nĂ©cessaire dans cette classe.
import static org.junit.Assert.*; // pour les méthodes de comparaison
import org.junit.Test; // pour l'annotation @Test et les outils associés
public class MyClassnameTest {
// déclaration d'un objet de la classe à tester
// p. ex. Calculator calc = new Calculator();
@Test
// déclaration d'une' méthode de test pour une méthode dans votre code
@Test
// déclaration d'une méthode de test pour une autre méthode dans votre code
}
Test dâĂ©galitĂ©
Les tests dâĂ©galitĂ© sont pour des mĂ©thodes qui retournent une valeur. On compare la valeur retournĂ©e par la mĂ©thode avec la valeur attendue.
Exemple de base
Considérant notre méthode add dans la classe Calculator :
class Calculator {
// ... reste du code de la classe
int add(int a, int b) {
return a + b;
}
}
La classe de test produit par les outils de JUnit dans VS Code serait le suivant, avec quelques lignes additionnelles :
import static org.junit.Assert.*; // ajoutez cette ligne pour les méthodes de comparaison
import org.junit.Test;
public class CalculatorTest {
Calculator calc = new Calculator(); // ajoutez cette ligne pour faire référence à votre code
@Test
public void testAdd() {
}
}
On peut Ă©crire le test unitaire testAdd dans la classe CalculatorTestcomme suit Ă lâintĂ©rieur de la classe de test :
@Test
public void testAdd() {
/*
* Cas de tests pour int add(int, int)
*
* Descr. Entrées Sortie attendue
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* base 1, 2 3
*/
assertEquals(3, calc.add(1,2));
}
Deux points qui peuvent faire planter ce code :
- La méthode
assertEquals()ne sera pas reconnue si vous nâavez pas ajoutĂ© lâimporation static dâAssertau dĂ©but du fichier de test. Voir le gabarit pour la classe de test ci-dessus. - La mĂ©thode
add()ne sera pas reconnue si vous nâavez pas créé une variablecalcde typeCalculatordans la classe de test. Voir le gabarit pour la classe de test ci-dessus.
Notez que assertEquals est une mĂ©thode de JUnit qui compare le premier argument avec le deuxiĂšme argument et lĂšve une exception si les deux arguments ne sont pas Ă©gaux. Câest la mĂ©thode la plus courante pour tester des valeurs de retour.
Il y a plusieurs autres détails à noter dans ce code :
- Lâannotation
@Testet la signature de la mĂ©thode ont peut-ĂȘtre Ă©tĂ© créées automatiquement si vous avez suivi les Ă©tapes ci-dessus. Lâannotation@Testdevient un point de lancement du code de test pour JUnit. - On Ă©crit les cas de test dans un commentaire de bloc au dĂ©but de la mĂ©thode de test. Dans lâexemple, on a simplement inclut un cas de test, mais vous devrez considĂ©rer lâensemble des cas Ă tester dans la mĂ©thode.
- On utilise la méthode
assertEqualspour implĂ©menter le cas de test, soit comparer le rĂ©sultat attendu avec la valeur de retour de la mĂ©thode testĂ©e. Il devrait y avoir un appel ĂassertEqualspour chaque cas de test.
Exemple pour comparer des 'double'
Lâexemple de base fonctionne pour tous les types sauf les valeurs Ă virgule flottante (comme les double).
Avec les double, dĂ» Ă la conversion inexacte entre le binaire (dans la machine) et le dĂ©cimal (dans la reprĂ©sentation du nombre), il y a toujours - ou presque - des erreurs dâarrondissement. Ainsi on ne peut jamais Ă©valuer lâĂ©galitĂ© entre deux double directement comme on le fait avec les autres types de donnĂ©es.
Lâalgorithme Ă utiliser se rĂ©sume Ă :
valeur 1 = valeur 2 ± une marge d'erreur acceptableouvaleur absolue de (valeur 1 - valeur 2) <= une marge d'erreur acceptable(la valeur absolue ignore le signe du résultat)
Par exemple, pour des notes de cours à une décimale prÚs on pourrait note1 = note2 ± 0.1 ou note1 = note2 ± 0.05, selon votre préférence.
Pour les double, JUnit fournit une mĂ©thode assertEquals qui prend un troisiĂšme argument, la marge dâerreur acceptable. Par exemple, pour une mĂ©thode average qui retourne la moyenne de deux nombres Ă une dĂ©cimale prĂšs, on pourrait Ă©crire le test unitaire comme suit :
@Test
public void testAverage() {
/*
* Cas de tests pour double average(double, double) :
*
* Descr. Entrées Sortie attendue
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* base 80.0, 90.0 85.0
*/
assertEquals(85.0, calc.average(80.0, 90.0), 0.1);
}
Notez que nous fournissons la valeur attendue et la valeur de retour de la mĂ©thode testĂ©e comme avec lâexemple. Cependant, on ajoute aussi un troisiĂšme argument, la marge dâerreur acceptable. Dans cet exemple, on accepte une diffĂ©rence de 0.1 entre la valeur attendue et la valeur de retour.
Le choix de la marge acceptable dĂ©pend de la prĂ©cision requise par votre programme. Comme rĂšgle de base, on devrait choisir une marge dâerreur qui est plus petite que la prĂ©cision finale que nous voulons parce que les erreurs dâarrondissment sâaccumulent Ă chaque opĂ©ration.
Donc la marge de 0.1 si on veut des rĂ©sultats Ă une place dĂ©cimale est trop gĂ©nĂ©reuse et on perd beaucoup de prĂ©cision. RĂšgle de base : utiliser une marge dâerreur au moins 3 chiffres de plus que la prĂ©cision finale dĂ©sirĂ©e. P. ex., si on veut une prĂ©cision finale de 0.1 on devrait choisir une marge dâerreur dâau maximum 0.0001. le test ci-dessus serait modifiĂ© Ă :
assertEquals(85.0, calc.average(80.0, 90.0), 0.0001);
Test dâentrĂ©e
Pour tester une mĂ©thode qui sollicite des entrĂ©es de lâutilisateur via la console, on a deux options selon la façon dont le Scanner de la console est gĂ©rĂ© dans la classe du programme :
Scanner static et global | Scanner passé comme paramÚtre (Scanner local) |
|---|---|
![]() | ![]() |
Dans le cas dâun Scanner global, parce que la vie de la variable est plus longue que la vie de la mĂ©thode, on doit sâassurer de rĂ©tablir sa valeur originale avant de quitter le test. Pour le faire, on doit sâassurer de copier sa valeur originale au dĂ©but du test.
Pour le Scanner local, ces Ă©tapes sont Ă©liminĂ©es, mais on doit sâassurer de passer le Scanner comme argument Ă la mĂ©thode Ă tester.
Exemple avec un Scanner global
Si on a la méthode getNameUsingGlobalScanner dans la classe Calculator :
import java.util.Scanner;
class Calculator {
/** Scanner global pour toutes les méthodes de la classe */
Scanner console = new Scanner(System.in);
// ... reste du code de la classe
String getNameUsingGlobalScanner() {
System.out.print("Entrez votre prénom > ");
String name = console.next(); // utilise le Scanner global
console.nextLine(); // jeter tout aprĂšs le premier mot
return name;
}
}
On peut écrire le test unitaire testGetNameUsingGlobalScanner dans la classe CalculatorTest comme suit :
@Test
public void testGetNameUsingGlobalScanner() {
/*
* Cas de test pour String getName() qui utilise un
* Scanner déclaré globalement
*
* Descr. Entrée Sortie attendue
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* normal "david" "david"
* un caractĂšre "A" "A"
* plusieurs mots "David Crowley" "David"
*/
/* PRĂPARATION */
// garder une référence au Scanner original de calc
Scanner original = calc.console;
// définir les cas de test
String testInput = "david\n" +
"A\n" +
"David Crowley\n"; // les \n sont les `Entrée` de l'utilisateur
// créer une source d'entrées qui contient nos cas de tests
InputStream testStream = new ByteArrayInputStream(testInput.getBytes());
// passer la nouvelle source d'entrées au Scanner de calc
calc.console = new Scanner(testStream);
/* TESTS */
assertEquals("david", calc.getNameUsingGlobalScanner());
assertEquals("A", calc.getNameUsingGlobalScanner());
assertEquals("David", calc.getNameUsingGlobalScanner());
/* NETTOYAGE */
// rediriger le Scanner global de calc Ă lire sa source originale
calc.console = original;
}
Note : avec lâajout des InputStream et du Scanner, on doit ajouter import java.io.*; et import java.util.*; au dĂ©but du fichier de test, p. ex. :
import static org.junit.Assert.*;
import java.io.*; // ici
import java.util.*; // et ici
import org.junit.Test;
public class CalculatorTest {
// ... reste du code de la classe
}
Si on oublie, parfois les outils dâautocomplĂ©tion dans VS Code peuvent ajouter ces dĂ©clarations automatiquement, mais câest quelque chose Ă vĂ©rifier si vous avez des messages dâerreurs sur ces variables.
Exemple avec un Scanner local (passé en argument)
Une autre façon dâutiliser un Scanner dans un programme est de le dĂ©clarer localement, par exemple avec la mĂ©thode getName dans la classe Calculator :
import java.util.Scanner;
class Calculator {
String getName(Scanner in) {
System.out.print("Entrez votre prénom > ");
String name = in.next(); // fait référence au Scanner passé en paramÚtre
in.nextLine(); // jeter tout aprĂšs le premier mot
return name;
}
void main() {
Scanner console = new Scanner(System.in); // déclarer un Scanner local
//... autre code
String name = getName(console); // passer le Scanner local comme argument
//... autre code
}
}
Le test unitaire testGetName dans CalculatorTest serait alors :
@Test
public void testGetName() {
/*
* Cas de test pour String getName(Scanner)
*
* Descr. Entrée Sortie attendue
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* normal "david" "david"
* un caractĂšre "A" "A"
* plusieurs mots "David Crowley" "David"
*/
/* PRĂPARATION */
// définir les cas de test
String testInput = "david\n" +
"A\n" +
"David Crowley\n"; // les \n sont les `Entrée` de l'utilisateur
// créer un Scanner qui lit nos entrées de test au lieu de l'entrée standard
InputStream testStream = new ByteArrayInputStream(testInput.getBytes());
Scanner testScanner = new Scanner(testStream);
/* TESTS */
assertEquals("david", calc.getName(testScanner));
assertEquals("A", calc.getName(testScanner));
assertEquals("David", calc.getName(testScanner));
/* NETTOYAGE */
// Le nouveau Scanner est détruit automatiquement à la fin de cette méthode
}
Note : avec lâajout des InputStream et du Scanner, on doit ajouter import java.io.*; et import java.util.*; au dĂ©but du fichier de test, p. ex. :
import static org.junit.Assert.*;
import java.io.*; // ici
import java.util.*; // et ici
import org.junit.Test;
public class CalculatorTest {
// ... reste du code de la classe
}
Si on oublie, parfois les outils dâautocomplĂ©tion dans VS Code peuvent ajouter ces dĂ©clarations automatiquement, mais câest quelque chose Ă vĂ©rifier si vous avez des messages dâerreurs sur ces variables.
Test de sortie
Les tests de sortie sont utiles pour les méthodes void qui passent leurs résultats à la console. On peut utiliser la redirection de la sortie standard pour capturer les messages affichés par la méthode et les comparer avec les messages attendus.
Exemple de redirection de System.out pour les tests
Considérant la méthode sayName dans la classe Calculator :
void sayName(String name) {
System.out.println("Bonjour " + name);
}
On peut écrire le test unitaire testSayName dans la classe CalculatorTest comme suit :
@Test
public void testSayName() {
/*
* Cas de test pour void sayName(String)
*
* Descr. Entrée Sortie attendue
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* normal "David" "Bonjour David"
* un caractĂšre "A" "Bonjour A"
* plusieurs mots "David Crowley" "Bonjour David Crowley"
*
*/
/* PRĂPARATION */
// garder une référence à la sortie standard originale
PrintStream original = System.out;
// rediriger la sortie standard vers un flux de sortie temporaire
OutputStream outContent = new ByteArrayOutputStream();
System.setOut(new PrintStream(outContent));
/* TESTS */
calc.sayName("David");
calc.sayName("A");
calc.sayName("David Crowley");
String expectedOutput = "Bonjour David\nBonjour A\nBonjour David Crowley\n";
assertEquals(expectedOutput, outContent.toString().replace("\r\n", "\n")); // le replace() est nécessaire sur Windows (encodage de fin de ligne différent)
/* NETTOYAGE */
// rétablir la sortie standard originale
System.setOut(original);
}
Note : on applique la méthode de traitement de texte
.replace("\r\n", "\n")sur la sortie pour sâassurer que les diffĂ©rents types de retour de ligne ne causent pas un Ă©chec de la comparaison. Windows utilise\r\npour le retour de ligne, alors que Linux, MacOS et nos propres instructions Java utilisent seulement\n.
Note : avec lâajout du PrintStream et des OutputStream, on doit ajouter import java.io.*; au dĂ©but du fichier de test, p. ex. :
import static org.junit.Assert.*;
import java.io.*; // ici
import org.junit.Test;
public class CalculatorTest {
// ... reste du code de la classe
}
Si on oublie, parfois les outils dâautocomplĂ©tion dans VS Code peuvent ajouter ces dĂ©clarations automatiquement, mais câest quelque chose Ă vĂ©rifier si vous avez des messages dâerreurs sur ces variables.
Les exemples complets
Vous pouvez voir le code complet pour les classes Calculator et CalculatorTest dans les fichiers Calculator.java et CalculatorTest.java.
Exercices
đ ïž Pratique
Travaillez dans le répertoire GitHub partagé par votre enseignant pour la pratique et les exercices
-
Créez un fichier texte nommé
Cas-de-tests.txtdans votre rĂ©prtoire de travail.- Ăcrivez le tableau de cas de test pour une mĂ©thode qui retourne
truesi un nombre est pair etfalsesinon. La signature de cette mĂ©thode estboolean isEven(int number). Quels sont les cas normaux? Est-ce quâil y a des cas limites ou invalides pour cette mĂ©thode? - Ăcrivez le tableau de cas de test pour une mĂ©thode qui retourne une note en lettres pour une note numĂ©rique. La signature de cette mĂ©thode est
char letterGrade(double average). Les lettres sontF(moins de 50),D(50 à 59),C(60 à 69),B(70 à 79) etA(80 à 100). Quels sont les cas normaux? Quels sont les cas limites pour cette méthode, se rappelant que la moyenne est une valeur décimale (il y en a beaucoup!)? Quels sont les cas invalides?
- Ăcrivez le tableau de cas de test pour une mĂ©thode qui retourne
-
Voici une implémentation de la logique de la méthode
isEvendécrit dans le premier exemple.class NumberUtils { boolean isEven(int number) { return number % 2 == 0; } }- Créez une copie de ce fichier et nommez-le
NumberUtils.java. - Utilisez les outils de VS Code pour créer la classe de test et la squelette de test unitaire pour cette méthode.
- Ajoutez une déclaration globale (dans la classe
NumberUtilsTest) pour un objet NumberUtils :NumberUtils utils = new NumberUtils(); - Vous devrez traduire votre tableau de cas de test (Ă©crit plus haut) en commentaire de bloc Ă lâintĂ©rieur de votre mĂ©thode de test.
- ImplĂ©menter des tests dâĂ©galitĂ© pour cette mĂ©thode, un test par cas de test identifiĂ©. Servez vous du gabarit comme point de dĂ©part.
- Exécutez les tests.
- Prenez une capture dâĂ©cran de lâonglet âTest Resultsâ pour montrer que vos tests ont passĂ©. Lâenregistrez comme
4-unit-test.pngdans le dossier âcapturesâ.
- Créez une copie de ce fichier et nommez-le
-
Depuis JEP 463 (pleinement intĂ©grĂ© aux outils Java22+) â©

