Qu’est-ce que le développement piloté par les tests ?
Le développement piloté par les tests, ou TDD (Test-Driven Development), consiste à écrire d’abord un test qui échoue, puis le code qui le fait passer. On avance par petits pas, et chaque pas est protégé par un test.
Une boutique veut appliquer une remise à un prix. Plutôt que d’écrire toute la fonction d’un coup, on la fait grandir, un test après l’autre.
À la fin de cette leçon
Remarque
Je peux écrire un test qui échoue d’abord, le faire passer, puis ranger le code.
Mots-clés
| Mot | Ce que ça veut dire |
|---|---|
| TDD | développement piloté par les tests : le test est écrit avant le code |
| Rouge | le moment où le nouveau test échoue |
| Vert | le moment où tous les tests passent |
| Refactorisation | ranger le code sans changer ce qu’il fait |
| Petit pas | le plus petit changement qui fait passer le test |
| Régression | un ancien test qui échoue à cause d’un changement récent |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Le cycle : rouge, vert, refactorisation
Le cycle a trois temps. Rouge : vous écrivez un test pour une fonction qui n’existe pas encore, et vous le voyez échouer.
Vert : vous écrivez juste assez de code pour que tous les tests passent. Refactorisation : vous rangez le code, les tests restent verts. Puis vous recommencez avec un nouveau test.
Pourquoi voir le test échouer d’abord ?
Un test que l’on n’a jamais vu échouer peut ne rien vérifier. En le voyant rouge, vous savez qu’il peut détecter une faute.
Les petits pas rendent aussi la recherche d’une faute facile. Si le test devient rouge, la cause est dans les quelques lignes que vous venez d’écrire.
Rouge : écrire le premier test
printf 'import unittest\nfrom remise import prix_remise\n\n\nclass TestsRemise(unittest.TestCase):\n def test_dix_pour_cent(self):\n self.assertEqual(prix_remise(1000, 10), 900)\n' > essais/test_remise.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.
Rouge : une ébauche qui rend 0
printf 'def prix_remise(prix, remise):\n return 0\n' > essais/remise.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.
Rouge : le test échoue
python3 -m unittest discover -s essais -p "test_remise.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.
Vert : le plus petit code qui passe
printf 'def prix_remise(prix, remise):\n return 900\n' > essais/remise.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.
Vert : le test passe
python3 -m unittest discover -s essais -p "test_remise.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.
Rendre 900 est un petit pas
La fonction rend toujours 900. C’est faux en général, mais c’est tout ce que le seul test sait demander.
Ce n’est pas une triche : c’est le plus petit pas. Un second test va obliger la fonction à calculer vraiment. Ajoutons-le : 2000 avec 50 % de remise doit donner 1000.
Rouge : ajouter un second test
printf '\n def test_moitie_prix(self):\n self.assertEqual(prix_remise(2000, 50), 1000)\n' >> essais/test_remise.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.
Rouge : le second test échoue
python3 -m unittest discover -s essais -p "test_remise.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.
Vert : calculer la remise
printf 'def prix_remise(prix, remise):\n return prix - prix * remise // 100\n' > essais/remise.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.
Vert : les deux tests passent
python3 -m unittest discover -s essais -p "test_remise.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.
Ranger le code sans changer ce qu’il fait
Une fois vert, on range. Ici on peut donner aux variables des noms plus clairs. Les deux tests diront si on a cassé quelque chose.
La refactorisation ne change pas ce que fait le code. Elle ne se fait donc que lorsque tous les tests sont verts.
La fonction après rangement
def prix_remise(prix_en_centimes, remise_en_pourcent):
# La remise est calculée en centimes entiers, pour éviter les nombres décimaux.
montant_remise = prix_en_centimes * remise_en_pourcent // 100
return prix_en_centimes - montant_remise
assert prix_remise(1000, 10) == 900
assert prix_remise(2000, 50) == 1000
print("Les deux tests passent encore")
Erreur fréquente
Attention
Beaucoup pensent que le TDD veut dire écrire tous les tests d’abord. Au contraire : un seul test à la fois, puis le code, puis le suivant. Avec trop de tests rouges d’un coup, on ne sait plus par où commencer.
Faire grandir une fonction test par test
Dans le devoir, vous écrivez en_centimes(texte), qui lit un prix tel qu’on le tape en France, avec une virgule : "12,50" donne 1250.
Les règles arrivent par étapes, comme des tests. Pas de virgule : des euros entiers. Un seul chiffre après la virgule : des dizaines de centimes. Un texte qui n’est pas un prix : une erreur ValueError. Faites grandir votre fonction règle après règle.