Qu’est-ce qu’une doublure de test ?
Une doublure de test, appelée aussi faux ou mock, est un composant simple qui remplace une dépendance pendant un test. Cette dépendance est une horloge, un tirage au hasard ou un service lent. Elle rend le résultat prévisible, donc le test donne toujours la même réponse.
Une application de métro affiche le prix du billet selon l’heure. Un test qui lit l’heure réelle réussit à midi et échoue à huit heures.
À la fin de cette leçon
Remarque
Je peux tester un code qui dépend d’une horloge ou d’un tirage au hasard en les remplaçant par un faux.
Mots-clés
| Mot | Ce que ça veut dire | Exemple |
|---|---|---|
| Dépendance | ce dont une fonction a besoin et qu’elle ne contrôle pas | l’heure, le hasard, un réseau |
| Doublure de test | un faux composant qui remplace la vraie dépendance pendant un test | |
| Bouchon | une doublure qui rend toujours la même réponse | une horloge qui dit toujours 8 |
| Espion | une doublure qui retient comment on l’a appelée | |
| Simulacre | une doublure qui se vérifie elle-même, appelée mock en anglais | |
| Test instable | un test qui réussit parfois et échoue parfois sans que le code change |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Un test qui dépend de l’heure est instable
Voici la règle du métro : de 7 h à 9 h et de 17 h à 19 h, le billet coûte 250 centimes. Le reste du temps, il coûte 190.
Si la fonction lit elle-même l’heure de la machine, son résultat change au fil de la journée. Un test qui réussit à midi et échoue à huit heures est un test instable. Il apprend à tout le monde à ne plus croire les tests.
Lire l’heure de la machine
date +%HAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Passer l’horloge en paramètre
La commande affiche l’heure sur deux chiffres. Elle sera autre dans une heure, et c’est bien le problème.
La solution est de ne pas lire l’heure dans la fonction. On lui passe une horloge, c’est-à-dire une fonction qui rend l’heure. En production, on donne la vraie. Dans un test, on donne un faux.
Une fonction qui reçoit son horloge
def tarif_metro(horloge):
heure = horloge()
if 7 <= heure < 9 or 17 <= heure < 19:
return 250
return 190
# Un bouchon : une horloge qui dit toujours 8.
def horloge_8h():
return 8
# Un autre bouchon : toujours midi.
def horloge_midi():
return 12
assert tarif_metro(horloge_8h) == 250
assert tarif_metro(horloge_midi) == 190
print("Les deux tarifs sont justes, à toute heure")
Écrire la fonction du métro
printf 'def tarif_metro(horloge):\n heure = horloge()\n if 7 <= heure < 9 or 17 <= heure < 19:\n return 250\n return 190\n' > essais/metro.pyAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Écrire deux tests avec des bouchons
printf 'import unittest\nfrom metro import tarif_metro\n\n\ndef horloge_8h():\n return 8\n\n\ndef horloge_midi():\n return 12\n\n\nclass TestsMetro(unittest.TestCase):\n def test_heure_de_pointe(self):\n self.assertEqual(tarif_metro(horloge_8h), 250)\n\n def test_heure_creuse(self):\n self.assertEqual(tarif_metro(horloge_midi), 190)\n' > essais/test_metro.pyAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Lancer les tests : OK à toute heure
python3 -m unittest discover -s essais -p "test_metro.py" 2>&1 | tail -1Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Bouchon, espion, simulacre : trois sortes de faux
Un bouchon rend une réponse fixe : l’horloge qui dit 8. Un espion retient comment on l’a appelée : combien de fois, avec quoi. Un simulacre (mock) fait les deux et vérifie lui-même ses appels.
Python en fournit un dans unittest.mock. Les deux commandes qui suivent montrent un faux qui rend toujours 8, puis le nombre de fois qu’on l’a appelé.
Un faux qui rend toujours 8
python3 -c "from unittest.mock import Mock; m = Mock(return_value=8); print(m())"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Compter les appels d’un faux
python3 -c "from unittest.mock import Mock; m = Mock(return_value=8); m(); m(); print(m.call_count)"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Le hasard se remplace comme l’horloge
Un tirage au sort pose le même problème : le résultat change à chaque essai. On passe donc en paramètre la fonction qui tire.
Un faux tirage rend toujours la même place, par exemple la première. Le test sait alors qui doit gagner.
Un tirage au sort remplaçable
def choisir_gagnant(participants, tirage):
place = tirage(len(participants))
return participants[place]
# Un espion : il retient la taille qu'on lui a donnée et rend toujours la place 1.
appels = []
def tirage_espion(taille):
appels.append(taille)
return 1
gagnant = choisir_gagnant(["Aya", "Bao", "Chen"], tirage_espion)
print(gagnant, appels)
Le vrai hasard reste imprévisible
python3 -c "import random; print(random.randint(1, 6) in [1, 2, 3, 4, 5, 6])"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Erreur fréquente
Attention
Ne remplacez jamais ce que vous testez : ce serait tester le faux. Remplacez seulement ce qui échappe à votre contrôle, comme l’heure ou le réseau. Gardez aussi un test avec la vraie dépendance, plus haut dans la pyramide.
Écrire du code testable avec un faux
Dans le devoir, vous écrivez deux fonctions qui reçoivent leur dépendance. message_accueil(horloge) dit Bonjour, Bon après-midi ou Bonsoir selon l’heure. tirer_gagnant(participants, tirage) rend le participant que le tirage désigne.
Les tests cachés leur donnent des horloges et des tirages truqués. Votre code ne doit jamais lire la vraie heure.