Aller au contenu principal

Tests unitaires


Télécharger la démonstration à compléter

Disponible ici 👉 Demonstration13_2


🎯 Objectifs

À la fin de cette séance, vous serez en mesure de :

  • comprendre l’utilité des tests unitaires ;
  • générer un projet de tests unitaires ;
  • définir des classes et des méthodes de tests ;
  • utiliser les principales méthodes d’assertion ;
  • exécuter des tests unitaires ;
  • interpréter les résultats des tests ;
  • détecter des erreurs dans un programme ;
  • corriger un programme à l’aide des tests unitaires.

💥 Exemples réels de catastrophes causées par des bugs informatiques

Catastrophe réelleBug ou erreur informatiqueDégâts causés
Boeing 737 MAX CrashesProblème logiciel du système MCASDeux accidents d’avion et 346 décès. Immobilisation mondiale des avions pendant plusieurs mois.
Accident du Therac-25Bug logiciel dans une machine de radiothérapiePlusieurs patients ont reçu des doses massives de radiation. Blessures graves et décès.
Explosion du vol Ariane 501Conversion d’un nombre invalide dans le logiciel de guidageExplosion de la fusée environ 40 secondes après le décollage. Perte d’environ 370 millions $.
Mars Climate OrbiterConfusion entre unités métriques et impérialesDestruction de la sonde spatiale lors de son approche de Mars. Perte d’environ 125 millions $.
danger

Leçons à retenir

“The earlier a defect is found, the cheaper it is to fix.” — Barry Boehm

« Plus une erreur est détectée tôt, moins elle coûte cher à corriger. »

“Test early, test often, test everything.“ — Bjarne Stroustrup

« Testez tôt, testez souvent, testez tout. »


Mise en situation

Dans ce cours, nous allons utiliser la classe Thermostat afin d’apprendre à écrire des tests unitaires.

La classe fournie n’est volontairement pas complète. Certaines parties du code devront être complétées ou corrigées.

L’objectif est donc de :

  • écrire des tests unitaires ;
  • exécuter les tests ;
  • identifier les erreurs ;
  • corriger la classe Thermostat ;
  • vérifier que les tests réussissent correctement.

Cette approche s’inspire du développement dirigé par les tests (Test-Driven Development ou TDD), dans lequel les tests servent à détecter les problèmes et à guider le développement du programme.

info

Nous allons voir ensemble :

  • comment générer un projet de tests ;
  • comment définir des classes et des méthodes de tests ;
  • comment exécuter les tests ;
  • comment détecter et corriger des erreurs dans le programme.

Test unitaire

Définition

  • Un test unitaire est un procédé permettant de s'assurer du bon fonctionnement d'une unité de programme.
  • Un test est donc un bout de code qui permet de tester un autre code.

Utilité

  • Vérifier que le programme fonctionne comme prévu

(Exemple : la température augmente correctement lorsqu’on appelle la méthode AugmenterTemperature.)

  • Détecter rapidement les erreurs dans le programme

(Exemple : découvrir qu’une méthode retourne une mauvaise valeur ou provoque une erreur inattendue.)


Qu’est-ce qu’on teste exactement dans un programme ?

Quand on fait un test unitaire, on essaie simplement de vérifier que notre programme fonctionne correctement dans plusieurs situations.

  • L’objectif est de répondre à des questions comme :
    • Est-ce que la méthode retourne le bon résultat ?
    • Est-ce que les données sont bien modifiées ?
    • Est-ce que les validations fonctionnent ?
    • Est-ce que les erreurs sont bien détectées ?
    • Est-ce que les exceptions sont bien levées ?

Exemples simples de choses à tester

Tester une valeur retournée

On veut vérifier que le résultat produit par une méthode est correct.

Exemple

int resultat = Additionner(2, 3);

On veut vérifier que le résultat vaut bien 5.


Tester une modification d’état

Certaines méthodes modifient les données d’un objet.

Exemple

thermostat.AugmenterTemperature();

Après l’appel de la méthode, on veut vérifier que la température a augmenté de 1.


Tester les limites

On doit vérifier ce qui arrive avec des valeurs minimales ou maximales.

Exemple

Thermostat thermostat = new Thermostat(35);
thermostat.AugmenterTemperature();

Si 35 est la température maximale permise, on peut tester ce qui arrive si on essaie encore d’augmenter la température.


Tester des valeurs invalides

On vérifie que le programme refuse les mauvaises données.

Exemple

CompteBancaire compte = new CompteBancaire(-100);

Un solde négatif pourrait être interdit.

On teste donc si le programme déclenche une erreur correctement.


Tester des objets null

On vérifie ce qui arrive lorsqu’un objet n’existe pas.

Exemple

Client client = null;

Certaines méthodes doivent gérer cette situation sans faire planter le programme.


Tester des listes vides

On veut vérifier le comportement du programme lorsqu’il n’y a aucune donnée.

Exemple

List<string> noms = new List<string>();

Par exemple :

  • afficher un message ;
  • retourner 0 ;
  • ou empêcher une opération.

Tester des chaînes de caractères vides

Exemple

string nom = "";

ou

string nom = null;

On vérifie si le programme refuse un nom vide.


Tester des fichiers inexistants

Exemple

string chemin =
"C:\\Documents\\test.txt";

On vérifie ce qui arrive si le fichier n’existe pas.


En résumé

attention

Quand on prépare des tests, on doit penser à plusieurs types de situations :

  • Cas normaux
    Tester une utilisation normale du programme.

    (Exemple : augmenter la température lorsque celle-ci est à 20.)

  • Cas limites
    Tester les valeurs minimales et maximales permises.

    (Exemple : augmenter la température lorsqu’elle est déjà à la valeur maximale de 35.)

  • Erreurs possibles
    Tester des situations pouvant provoquer une erreur ou une exception.

    (Exemple : essayer d’ouvrir un fichier qui n’existe pas.)

  • Données vides
    Tester le comportement du programme avec des données vides.

    (Exemple : une chaîne vide "" ou une liste sans éléments.)

  • Valeurs invalides
    Tester des valeurs qui ne respectent pas les règles du programme.

    (Exemple : créer un compte bancaire avec un solde négatif.)

Un bon test permet de vérifier que le programme fonctionne correctement dans toutes les situations importantes.

Étapes d'un test unitaire

En général, un test unitaire se décompose en trois parties suivant le modèle AAA :

  • Arrange (Arranger) Préparer les objets et les données nécessaires au test.

  • Act (Agir) Exécuter la méthode ou l’action à tester.

  • Assert (Auditer) Vérifier que le résultat obtenu correspond au résultat attendu.


Exemple : Test de la méthode AugmenterTemperature

public void TestsAugmenterTemperature()
{
// Arranger (Arrange)
Thermostat unThermostat = new Thermostat(15);

// Agir (Act)
unThermostat.AugmenterTemperature();
int valeurRetournee = unThermostat.Temperature;

// Auditer (Assert)
int valeurAttendue = 16;

Assert.AreEqual(valeurAttendue, valeurRetournee);
}

Arrange (Arranger)

Thermostat unThermostat = new Thermostat(15);

Dans cette étape, on prépare les objets et les données nécessaires au test.

Ici, on crée un thermostat dont la température initiale est 15.


Act (Agir)

unThermostat.AugmenterTemperature();

int valeurRetournee = unThermostat.Temperature;

Dans cette étape, on exécute la méthode que l’on veut tester.

Ici, on augmente la température du thermostat puis on récupère la nouvelle valeur de la température.


Assert (Auditer)

int valeurAttendue = 16;

Assert.AreEqual(valeurAttendue, valeurRetournee);

Dans cette étape, on vérifie que le résultat obtenu correspond au résultat attendu.

Ici, on s’attend à ce que la température soit passée de 15 à 16. Le test vérifie donc que la valeur retournée est bien 16.


Principales méthodes d’assertion

AreEqual

Le test échoue si les deux valeurs sont différentes.

Assert.AreEqual(valeurAttendue, valeurRetournee);

AreNotEqual

Le test échoue si les deux valeurs sont identiques.

Assert.AreNotEqual(valeurAttendue, valeurRetournee);

IsTrue

Le test échoue si l’expression est fausse.

Assert.IsTrue(expression);

IsFalse

Le test échoue si l’expression est vraie.

Assert.IsFalse(expression);

IsNull

Le test échoue si l’objet n’est pas null.

Assert.IsNull(objet);

IsNotNull

Le test échoue si l’objet est null.

Assert.IsNotNull(objet);

Fail

Le test échoue systématiquement.

Assert.Fail();

Tester une méthode qui lève une exception

[TestMethod()]
public void AugmenterTemperatureTest()
{
// Arranger (Arrange)
// On crée un thermostat avec la température maximale permise
Thermostat unThermostat = new Thermostat(Thermostat.MAX_TEMPERATURE);

// Agir/Auditer (Act/Assert)
// On essaie d’augmenter la température.
// Le test vérifie qu’une exception de type
// InvalidOperationException est bien déclenchée.
Assert.ThrowsException<InvalidOperationException>(
() => unThermostat.AugmenterTemperature()
);


}
Assert.ThrowsException<InvalidOperationException>(
() => thermostat.AugmenterTemperature()
);

👉 Cette instruction vérifie que la méthode AugmenterTemperature() provoque bien une exception de type InvalidOperationException.


Jeu de tests unitaires

Tester uniquement quelques exemples ne garantit pas l’absence d’erreurs.

Un jeu de tests est un ensemble de cas de tests permettant de couvrir différents scénarios possibles.


Bonne pratique : définir des constantes pour les données de test

Dans les tests unitaires, il est recommandé de définir des constantes pour les valeurs utilisées dans les tests.

Cela permet :

  • d’éviter de répéter les mêmes valeurs ;
  • de rendre les tests plus faciles à lire ;
  • de modifier les données plus facilement.

Exemple

// Valeurs limites
const int TEMPERATURE_MIN_TEST = 5;
const int TEMPERATURE_MAX_TEST = 35;

// Valeurs nominales (normales)
const int TEMPERATURE_NORMALE_TEST = 20;

// Valeurs hors limites
const int TEMPERATURE_HORS_LIMITE_BASSE_TEST = -1;
const int TEMPERATURE_HORS_LIMITE_HAUTE_TEST = 36;


// Objet null
const Question QUESTION_NULL_TEST = null;

// chaine vide ou null
const string NOM_QUESTION_VIDE_TEST = "";
const string NOM_QUESTION_NULL_TEST = null

Ces constantes peuvent ensuite être réutilisées dans plusieurs tests.

Thermostat thermostat = new Thermostat(TEMPERATURE_NORMALE_TEST);

ou

Assert.ThrowsException<ArgumentException>(
() => new Thermostat(
TEMPERATURE_HORS_LIMITE_HAUTE_TEST
)
);

ou

QUiz unQuiz = new Quiz();

Assert.ThrowsException<ArgumentNullException>(
() => nQuiz.AjouterQuestion(QUESTION_NULL_TEST)
);

Structure générale d’une classe de tests

[TestClass]
public class ClasseDeTest
{
[TestMethod]
public void MethodeDeTest()
{
// Arrange
// Préparer les objets et les données

// Act
// Exécuter la méthode à tester

// Assert
// Vérifier le résultat obtenu
}
}

Explication

  • [TestClass]
    Indique que la classe contient des tests unitaires.

  • [TestMethod]
    Indique qu’une méthode représente un test unitaire.

  • Arrange
    Préparer les objets et les données nécessaires au test.

  • Act
    Exécuter l’action ou la méthode à tester.

  • Assert
    Vérifier que le résultat obtenu correspond au résultat attendu.