Installer pytest et votre premier test
Pourquoi les vérifications manuelles finissent par échouer, pourquoi un simple assert ne vous apprend presque rien, et comment installer pytest, puis écrire et exécuter un premier fichier de test. Y compris les points, la ligne de résumé, -q, -v et les codes de sortie.
Comment voulez-vous apprendre ?
Le problème que nous résolvons
Voici une petite fonction, et la manière dont la plupart d'entre nous vérifient une fonction de ce genre — la lancer et regarder ce qui en sort :
def discounted(price, percent):
return price - price * percent / 100
print(discounted(200, 10))
print(discounted(80, 25))
print(discounted(99, 0))180.0
60.0
99.0Vous lisez les trois nombres, faites les calculs de tête et décidez qu'ils sont corrects. C'est un test. C'est un test manuel, et il pose deux problèmes.
Le premier est qu'il ne passe pas à l'échelle. Trois nombres sont faciles à vérifier à l'œil nu. Trente fonctions avec cinq cas chacune, revérifiées après chaque modification, ne le sont pas — et personne ne le fait.
Le second problème est pire. Un mois plus tard, quelqu'un nettoie la fonction :
def discounted(price, percent):
return price * (1 - percent // 100)$ python -c "from prices import discounted; print(discounted(200, 10))"
200Cela a l'air raisonnable, et cela s'exécute sans erreur. Mais // correspond à la division entière, donc 10 // 100 vaut 0, et chaque remise a discrètement disparu. Du code qui fonctionnait et qui cesse de fonctionner s'appelle une régression, et une régression est exactement ce que les vérifications manuelles ne détectent pas — parce que personne ne revient revérifier les anciens nombres.
La solution consiste à consigner les réponses attendues dans le code, pour qu'une machine puisse les vérifier à chaque fois. Python dispose déjà d'une instruction pour cela, assert. Placez les vérifications dans un fichier nommé check_prices.py, aux côtés de prices.py :
from prices import discounted
assert discounted(200, 10) == 180.0
assert discounted(80, 25) == 60.0
assert discounted(99, 0) == 99
print("all good")Avec la fonction d'origine :
$ python check_prices.py
all goodAvec celle « nettoyée » :
$ python check_prices.py
Traceback (most recent call last):
File "/home/you/shop/check_prices.py", line 3, in <module>
assert discounted(200, 10) == 180.0
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionErrorLa régression est détectée, ce qui est un progrès. Mais regardez ce qui vous est indiqué : AssertionError, et rien d'autre. Pas ce que discounted(200, 10) a réellement renvoyé. Et le script s'est arrêté au premier échec, vous ne savez donc pas si les deux autres vérifications réussissent.
Ce chapitre installe l'outil qui résout ces deux problèmes : pytest.
À la fin de ce chapitre, vous saurez
- Expliquer pourquoi les tests automatisés détectent les régressions que les vérifications manuelles laissent passer
- Installer pytest avec
uvoupip, et confirmer l'installation avecpytest --version - Écrire un fichier de test que pytest trouve tout seul, et l'exécuter
- Lire les points et la ligne de résumé, et basculer entre
-qet-v - Expliquer ce que signifient les codes de sortie
0et1, et pourquoi c'est important
Prérequis : avant d'écrire un test. Vous devez également avoir Python 3 et savoir écrire une fonction.
Avant d'écrire le test
Le chapitre précédent portait sur la décision de ce qu'il faut tester. Celui-ci porte sur les éléments concrets qui doivent être en place avant de pouvoir exécuter la première ligne de code de test. Il y en a quatre, et chacun d'eux, lorsqu'il manque, produit sa propre erreur déroutante — il est donc avantageux de les vérifier dans l'ordre.
1. Un dossier de projet, un environnement. Tout ce qui figure dans ce chapitre réside dans un seul et même dossier :
shop/
├── .venv/ the project's own Python environment
├── pyproject.toml created by uv; lists pytest as a dev dependency
├── prices.py the code under test
└── test_prices.py the testsPourquoi un environnement dédié ? Parce que pytest doit être installé dans le même Python que celui qui importera votre code. Si pytest réside dans un Python et les paquets de votre projet dans un autre, vous obtenez des erreurs qui ressemblent à du code manquant alors que le véritable problème est un outil manquant. Un .venv par projet élimine toute incertitude.
2. pytest installé dans cet environnement — c'est ce que fait la section suivante.
3. Le code importable. Un fichier de test est du Python ordinaire : sa première ligne sera from prices import discounted. Donc avant d'écrire le moindre test, vérifiez que cet import exact fonctionne depuis le dossier du projet :
$ python -c "from prices import discounted; print(discounted(200, 10))"
180.0Si cette ligne échoue, pytest échouera de la même manière — mais avec un rapport plus long. La vérifier isolément prend cinq secondes et sépare « mon code est introuvable » de « mon code est faux ». Cela implique également que le nom du module doit être un nom Python valide : prices.py peut être importé, my-prices.py et 2prices.py ne le peuvent pas.
4. La promesse, consignée sous forme de cas. Avant de taper un test, notez ce que discounted promet, une ligne par cas :
| Cas | Entrée | Attendu | | --- | --- | --- | | Remise typique | discounted(200, 10) | 180.0 | | Une seconde remise, différente | discounted(80, 25) | 60.0 | | Cas limite : aucune remise | discounted(99, 0) | 99 |
Et tout aussi délibérément, ce qui n'est pas sur la liste. Un pourcentage négatif, ou supérieur à 100 : la fonction ne fait encore aucune promesse à leur sujet, et un test ne ferait que figer ce qu'elle fait par hasard aujourd'hui. (Rejeter une entrée invalide avec une erreur est une promesse à part entière — chapitre quatre.) L'arithmétique propre à Python également : vous testez votre formule, pas le fait que * multiplie.
Pourquoi deux cas ordinaires et pas un seul ? Regardez la troisième ligne face à la fonction « nettoyée » du début du chapitre : 99 * (1 - 0 // 100) vaut toujours 99. Le cas à zéro pour cent réussit même avec le bug. Un plan composé uniquement de cas limites peut passer à côté de l'erreur même qui compte, et un plan composé d'un seul cas typique passe à côté des limites. Chaque ligne est là pour intercepter quelque chose que les autres ne verraient pas.
La suite du chapitre transforme ce tableau en un fichier de test.
Installer pytest
pytest ne fait pas partie de Python ; c'est un paquet que vous installez dans votre projet. C'est un outil pour développer le code, et non une chose dont le programme a besoin lorsqu'il s'exécute — il s'intègre donc comme une dépendance de développement.
Si votre projet utilise uv, exécutez ceci dans le dossier du projet :
$ uv add --dev pytest
Resolved 7 packages in 59ms
Installed 5 packages in 18ms
+ iniconfig==2.3.1
+ packaging==26.3
+ pluggy==1.6.0
+ pygments==2.21.0
+ pytest==9.1.1uv l'enregistre dans pyproject.toml sous un groupe dev, afin que quiconque configurera le projet plus tard obtienne le même outil :
[dependency-groups]
dev = [
"pytest>=9.1.1",
]Si vous utilisez simplement pip dans un environnement virtuel, activez l'environnement et exécutez :
$ python -m pip install pytestpip affiche sa progression de téléchargement et se termine par :
Successfully installed iniconfig-2.3.1 packaging-26.3 pluggy-1.6.0 pygments-2.21.0 pytest-9.1.1python -m pip plutôt que pip tout court garantit que le paquet atterrit dans le même Python que celui que vous allez exécuter — une source fréquente de « je l'ai installé mais il n'est pas là ».
Vérifiez maintenant que cela a fonctionné :
$ pytest --version
pytest 9.1.1Une remarque avant de poursuivre. Avec uv, les commandes s'exécutent dans l'environnement du projet lorsqu'elles sont précédées de uv run — soit uv run pytest --version. Avec un environnement virtuel activé, pytest seul suffit. La suite de ce cours écrit simplement pytest ; ajoutez uv run devant si c'est ainsi que vous travaillez.
Votre premier fichier de test
Deux règles de nommage, et pytest n'a besoin de rien d'autre de votre part :
- le nom du fichier commence par
test_(ou se termine par_test.py) - chaque fonction que vous voulez exécuter commence par
test
Remettez le fichier prices.py d'origine, correct, et à côté créez test_prices.py :
from prices import discounted
def test_ten_percent_off():
assert discounted(200, 10) == 180.0
def test_quarter_off():
assert discounted(80, 25) == 60.0
def test_no_discount():
assert discounted(99, 0) == 99Chaque vérification de check_prices.py est maintenant sa propre petite fonction, et chaque nom indique ce qui est vérifié. Il n'y a pas de print, pas de liste de tests à enregistrer, pas de if __name__ == "__main__". Exécutez maintenant pytest depuis le dossier du projet :
$ pytest
============================= test session starts ==============================
platform linux -- Python 3.12.3, pytest-9.1.1, pluggy-1.6.0
rootdir: /home/you/shop
collected 3 items
test_prices.py ... [100%]
============================== 3 passed in 0.01s ===============================pytest a parcouru le dossier, a trouvé test_prices.py grâce à son nom, a trouvé les trois fonctions grâce aux leurs, a exécuté chacune d'elles et a affiché son rapport.
Lire la sortie
Prenons les lignes une par une.
platform ...— quel Python et quel pytest ont tourné. Vaut le coup d'œil quand les résultats diffèrent entre deux machines.rootdir: /home/you/shop— le dossier que pytest considère comme la racine du projet.collected 3 items— combien de tests il a trouvés. La découverte des tests s'appelle la collecte (collection). Si ce nombre est inférieur à ce que vous attendiez, un nom est incorrect quelque part.test_prices.py ...— un caractère par test, dans l'ordre où ils ont tourné. Un.signifie qu'il a réussi.[100%]— progression sur l'ensemble de l'exécution.3 passed in 0.01s— la ligne de résumé. C'est la ligne à lire en premier.
Remettez maintenant le discounted « nettoyé » et relancez. Cette fois avec -q, que nous verrons en détail dans un instant :
$ pytest -q
FF. [100%]
=================================== FAILURES ===================================
_____________________________ test_ten_percent_off _____________________________
def test_ten_percent_off():
> assert discounted(200, 10) == 180.0
E assert 200 == 180.0
E + where 200 = discounted(200, 10)
test_prices.py:5: AssertionError
_______________________________ test_quarter_off _______________________________
def test_quarter_off():
> assert discounted(80, 25) == 60.0
E assert 80 == 60.0
E + where 80 = discounted(80, 25)
test_prices.py:9: AssertionError
=========================== short test summary info ============================
FAILED test_prices.py::test_ten_percent_off - assert 200 == 180.0
FAILED test_prices.py::test_quarter_off - assert 80 == 60.0
2 failed, 1 passed in 0.01sLa ligne de progression est désormais FF. : un F pour chaque échec. Et comparez cela avec la simple AssertionError de tout à l'heure :
- chaque test a tourné — un échec dans l'un n'a pas arrêté les autres, vous pouvez donc constater que zéro pour cent fonctionne toujours
- chaque échec nomme le test et la ligne
assert 200 == 180.0montre la valeur que la fonction a réellement renvoyée
Lire ce rapport dans son intégralité fait l'objet de tout le chapitre suivant. Pour l'instant, remarquez seulement que la ligne de résumé est passée à 2 failed, 1 passed — et que le même assert que vous connaissiez déjà s'explique désormais de lui-même.
-q et -v : moins et plus
La sortie par défaut est un compromis. Deux options permettent de la modifier dans un sens ou dans l'autre.
-q (quiet) supprime l'en-tête :
$ pytest -q
... [100%]
3 passed in 0.01s-v (verbose) donne à chaque test sa propre ligne, avec son nom complet :
$ pytest -v
============================= test session starts ==============================
platform linux -- Python 3.12.3, pytest-9.1.1, pluggy-1.6.0
cachedir: .pytest_cache
rootdir: /home/you/shop
collecting ... collected 3 items
test_prices.py::test_ten_percent_off PASSED [ 33%]
test_prices.py::test_quarter_off PASSED [ 66%]
test_prices.py::test_no_discount PASSED [100%]
============================== 3 passed in 0.01s ===============================test_prices.py::test_ten_percent_off est le node ID du test : le fichier, un double deux-points, la fonction. Vous l'utiliserez plus tard pour lancer un test individuel. C'est également là que les noms de tests descriptifs portent leurs fruits — dans une liste avec -v, test_quarter_off vous indique ce qui a réussi ; test_2 ne le ferait pas.
Utilisez -q lorsque vous lancez des tests en permanence et que vous voulez seulement le verdict, et -v lorsque vous souhaitez voir exactement quels tests ont tourné.
Qu'est-ce que .pytest_cache ? Un dossier créé par pytest pour mémoriser des informations entre les exécutions, comme les tests qui ont échoué la dernière fois. Il peut être supprimé sans danger et n'a pas sa place dans le contrôle de version.Codes de sortie : comment une machine lit le résultat
Vous lisez la ligne de résumé. Un script, un éditeur ou un serveur de build ne le peut pas — il lit le code de sortie (exit code), le numéro que chaque programme renvoie au shell lorsqu'il se termine. Dans un shell Unix, $? contient le dernier code renvoyé :
$ pytest -q
... [100%]
3 passed in 0.01s
$ echo $?
0Et avec la fonction cassée, après l'exécution qui s'est terminée par 2 failed, 1 passed :
$ echo $?
10— tout a été collecté, tout a réussi1— des tests ont tourné, et au moins un a échoué
(Dans PowerShell, la variable s'appelle $LASTEXITCODE.) C'est ce qui permet aux tests de s'exécuter automatiquement avant la fusion du code : un code de sortie non nul interrompt le processus. Il existe quelques autres codes, dont l'un que vous rencontrerez dans la section « En cas d'erreur » ci-dessous.
Où placer les fichiers de test
Dans ce chapitre, le fichier de test se trouve juste à côté du code :
shop/
├── prices.py
└── test_prices.pyCela convient pour une poignée de fichiers. La plupart des projets rassemblent plutôt les tests dans leur propre dossier tests/. Dans les deux cas, pytest les trouve grâce à leur nom ; la façon d'organiser un vrai projet, et ce qui change lorsque vous le faites, est le sujet du chapitre trois.
Un exemple complet
Une fonction qui transforme une note chiffrée en une note sous forme de lettre, grades.py :
def letter_grade(score):
"""Turn a score from 0 to 100 into a letter."""
if score >= 80:
return "A"
if score >= 60:
return "B"
if score >= 40:
return "C"
return "F"Et ses tests, test_grades.py :
from grades import letter_grade
def test_top_score_is_an_a():
assert letter_grade(95) == "A"
def test_exactly_eighty_is_still_an_a():
assert letter_grade(80) == "A"
def test_just_below_eighty_is_a_b():
assert letter_grade(79) == "B"
def test_middle_score_is_a_c():
assert letter_grade(50) == "C"
def test_zero_is_an_f():
assert letter_grade(0) == "F"$ pytest -v
============================= test session starts ==============================
platform linux -- Python 3.12.3, pytest-9.1.1, pluggy-1.6.0
cachedir: .pytest_cache
rootdir: /home/you/grades
collecting ... collected 5 items
test_grades.py::test_top_score_is_an_a PASSED [ 20%]
test_grades.py::test_exactly_eighty_is_still_an_a PASSED [ 40%]
test_grades.py::test_just_below_eighty_is_a_b PASSED [ 60%]
test_grades.py::test_middle_score_is_a_c PASSED [ 80%]
test_grades.py::test_zero_is_an_f PASSED [100%]
============================== 5 passed in 0.01s ===============================Deux choses méritent d'être soulignées.
Premièrement, les tests à 80 et 79. Ils se situent de part et d'autre d'une limite (boundary), qui est précisément là où se nichent les erreurs. Remplacez score >= 80 par score > 80 — une étourderie facile — et lancez la forme silencieuse :
$ pytest -q
.F... [100%]
=================================== FAILURES ===================================
______________________ test_exactly_eighty_is_still_an_a _______________________
def test_exactly_eighty_is_still_an_a():
> assert letter_grade(80) == "A"
E AssertionError: assert 'B' == 'A'
E
E - A
E + B
test_grades.py:9: AssertionError
=========================== short test summary info ============================
FAILED test_grades.py::test_exactly_eighty_is_still_an_a - AssertionError: as...
1 failed, 4 passed in 0.01sLe second test, test_exactly_eighty_is_still_an_a, est celui qui échoue : 80 obtient désormais un 'B'. Un test à 95 seul ne l'aurait jamais remarqué.
Deuxièmement, les noms. Dans la liste générée par -v, chaque ligne se lit comme une phrase décrivant le comportement de la fonction. Lorsqu'un test échouera dans plusieurs mois, le nom seul vous indiquera quelle promesse n'a pas été tenue.
En cas d'erreur
zsh: command not found: pytest (ou bash: pytest: command not found) Le shell ne parvient pas à trouver le programme pytest. Soit il n'est pas installé, soit il est installé dans un environnement virtuel qui n'est pas actif. Avec uv, lancez uv run pytest ; avec pip, activez d'abord l'environnement. python -m pytest fonctionne également, et exécute pytest avec le Python auquel correspond la commande python.
/usr/bin/python3: No module named pytest Vous avez exécuté python -m pytest, et ce Python ne dispose pas de pytest. Le chemin au début vous indique de quel Python il s'agissait — ici celui du système, et non celui de votre projet. Installez pytest dans l'environnement que vous prévoyez d'utiliser.
ModuleNotFoundError: No module named 'price' pendant la collecte Le fichier de test n'a pas pu importer votre code — ici à cause d'une faute de frappe, from price import ... au lieu de from prices import ... :
$ pytest -q
==================================== ERRORS ====================================
_______________________ ERROR collecting test_prices.py ________________________
ImportError while importing test module '/home/you/shop/test_prices.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/usr/lib/python3.12/importlib/__init__.py:90: in import_module
return _bootstrap._gcd_import(name[level:], package, level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
test_prices.py:1: in <module>
from price import discounted
E ModuleNotFoundError: No module named 'price'
=========================== short test summary info ============================
ERROR test_prices.py
!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!
1 error in 0.01sRemarquez le mot ERROR, et non FAILED : aucun test n'a tourné du tout, parce que le fichier n'a même pas pu être chargé, et le code de sortie est 2. Lisez la dernière ligne commençant par E, puis lancez la vérification d'import de la section « Avant d'écrire le test » — python -c "from prices import discounted" — jusqu'à ce qu'elle fonctionne d'elle-même.
no tests ran in 0.01s pytest s'est exécuté, mais n'a rien collecté. Le nom du fichier ne commence pas par test_ (tests_prices.py, prices_tests.py et test.py sont tous ignorés), ou les fonctions ne commencent pas par test (check_quarter_off est ignoré silencieusement). Le code de sortie est ici 5, et non 0, pour qu'une exécution automatisée ne confonde pas « rien trouvé » avec « tout a réussi ».
Un test réussit alors qu'il ne devrait manifestement pas Cherchez un assert manquant :
from prices import discounted
def test_quarter_off():
discounted(80, 25) == 999$ pytest -q
. [100%]
1 passed in 0.01sLa comparaison est calculée puis ignorée. Un test n'échoue que si une exception est levée à l'intérieur ; sans assert, aucune exception n'est levée.
PytestReturnNotNoneWarning: Test functions should return None Vous avez écrit return discounted(99, 0) == 99 au lieu de assert .... pytest ignore ce qu'un test renvoie, donc ce test réussit que la comparaison soit vraie ou fausse. L'avertissement demande même : Did you mean to use assert instead of return?
Étape 4 sur 6 — Prédire
Vérifiez votre compréhension
Ce fichier est enregistré sous le nom test_double.py et pytest -q est exécuté. Par quels caractères commence la première ligne de la sortie ?
def double(n):
return n * 2
def test_two():
assert double(2) == 4
def test_zero():
assert double(0) == 1
def test_negative():
assert double(-3) == -6- AF
- B..F
- C.F.
- DF..
999 est manifestement la mauvaise réponse, et pourtant pytest affiche 1 passed. Pourquoi ?
def discounted(price, percent):
return price - price * percent / 100
def test_quarter_off():
discounted(80, 25) == 999- Apytest vérifie uniquement que la fonction s'est exécutée, pas son résultat
- BLa ligne ne comporte aucun `assert` — la comparaison est calculée puis ignorée
- CLes nombres décimaux ne peuvent pas être comparés avec `==`
- DLe nom de la fonction ne commence pas par `test_`
Un fichier contenant des tests corrects est nommé double_checks.py. Que se passe-t-il lorsque vous exécutez pytest dans ce dossier ?
- Apytest s'arrête avec une `ImportError`
- B`no tests ran` — le nom du fichier ne commence pas par `test_`, il n'est donc jamais analysé ; code de sortie `5`
- CTous les tests s'exécutent, car les noms de fonctions commencent par `test_`
- Dpytest renomme le fichier en `test_double_checks.py` à votre place
Répondre nécessite un compte
Connectez-vous pour vérifier vos réponses
Les questions sont ci-dessus, et les résoudre de tête est ce qui compte vraiment. Connectez-vous pour voir les réponses, les explications et les indices à trois niveaux.
À vous de jouer
Créez un dossier contenant un fichier temperature.py avec deux fonctions :
c_to_f(celsius)— renvoiecelsius * 9 / 5 + 32f_to_c(fahrenheit)— renvoie(fahrenheit - 32) * 5 / 9
Installez ensuite pytest dans l'environnement de ce dossier et confirmez l'installation avec pytest --version.
Écrivez test_temperature.py avec au moins cinq tests. Incluez le point de congélation de l'eau (0 et 32), le point d'ébullition de l'eau (100 et 212), et -40, qui est identique dans les deux échelles. Donnez à chaque test un nom qui indique ce qu'il vérifie.
Exécutez-le de trois manières — pytest, pytest -q et pytest -v — et après l'une d'entre elles, vérifiez le code de sortie.
Ensuite, cassez-le délibérément : remplacez 9 / 5 par 5 / 9 dans c_to_f. Avant d'exécuter, prévoyez quels tests échoueront et à quoi ressemblera la ligne de progression. Exécutez pytest -q, comparez, et vérifiez à nouveau le code de sortie.
Enfin, renommez test_temperature.py en temperature_check.py et exécutez pytest. Lisez ce qu'il indique, ainsi que son code de sortie. Renommez-le ensuite avec son nom d'origine. Savoir à quoi ressemble « rien trouvé » vous évitera un après-midi de confusion.
Solution
D'abord le plan, avant le moindre code — le même tableau que précédemment :
| Cas | Entrée | Attendu | | --- | --- | --- | | L'eau gèle | c_to_f(0), f_to_c(32) | 32, 0 | | L'eau bout | c_to_f(100), f_to_c(212) | 212, 100 | | Les deux échelles se rejoignent | c_to_f(-40), f_to_c(-40) | -40, -40 |
temperature.py :
def c_to_f(celsius):
return celsius * 9 / 5 + 32
def f_to_c(fahrenheit):
return (fahrenheit - 32) * 5 / 9Avant d'écrire les tests, la vérification d'import depuis le dossier du projet :
$ python -c "import temperature; print('importable')"
importabletest_temperature.py :
from temperature import c_to_f, f_to_c
def test_water_freezes_at_32_f():
assert c_to_f(0) == 32
def test_water_boils_at_212_f():
assert c_to_f(100) == 212
def test_32_f_is_zero_c():
assert f_to_c(32) == 0
def test_212_f_is_100_c():
assert f_to_c(212) == 100
def test_minus_40_is_the_same_in_both_scales():
assert c_to_f(-40) == -40
assert f_to_c(-40) == -40$ pytest -q
..... [100%]
5 passed in 0.01s
$ echo $?
0
$ pytest -v
============================= test session starts ==============================
platform linux -- Python 3.12.3, pytest-9.1.1, pluggy-1.6.0
cachedir: .pytest_cache
rootdir: /home/you/temperature
collecting ... collected 5 items
test_temperature.py::test_water_freezes_at_32_f PASSED [ 20%]
test_temperature.py::test_water_boils_at_212_f PASSED [ 40%]
test_temperature.py::test_32_f_is_zero_c PASSED [ 60%]
test_temperature.py::test_212_f_is_100_c PASSED [ 80%]
test_temperature.py::test_minus_40_is_the_same_in_both_scales PASSED [100%]
============================== 5 passed in 0.01s ===============================À présent, le bug intentionnel, 9 / 5 remplacé par 5 / 9 dans c_to_f :
$ pytest -q
.F..F [100%]
=================================== FAILURES ===================================
__________________________ test_water_boils_at_212_f ___________________________
def test_water_boils_at_212_f():
> assert c_to_f(100) == 212
E assert 87.55555555555556 == 212
E + where 87.55555555555556 = c_to_f(100)
test_temperature.py:9: AssertionError
___________________ test_minus_40_is_the_same_in_both_scales ___________________
def test_minus_40_is_the_same_in_both_scales():
> assert c_to_f(-40) == -40
E assert 9.777777777777779 == -40
E + where 9.777777777777779 = c_to_f(-40)
test_temperature.py:21: AssertionError
=========================== short test summary info ============================
FAILED test_temperature.py::test_water_boils_at_212_f - assert 87.55555555555...
FAILED test_temperature.py::test_minus_40_is_the_same_in_both_scales - assert...
2 failed, 3 passed in 0.01s
$ echo $?
1Et le fichier renommé :
$ pytest -q
no tests ran in 0.01s
$ echo $?
5Pourquoi cette structure :
- Un comportement par test, formulé comme une phrase. Dans la liste produite avec
-v, chaque ligne énonce ce qui est promis. Lorsquetest_water_boils_at_212_féchoue, vous savez ce qui s'est cassé avant même de lire la suite. - Des valeurs connues et vérifiables.
0/32,100/212et-40sont des faits que vous pouvez vérifier sans calculatrice, de sorte qu'un échec pointe vers le code, et non vers une valeur attendue douteuse. - Les deux sens.
c_to_fetf_to_csont deux morceaux de code distincts, chacun nécessite donc ses propres tests ; le fait que l'un soit correct ne dit rien sur l'autre. - L'exécution avec le bug est la partie la plus révélatrice. Seuls deux tests ont échoué, et
test_water_freezes_at_32_fn'en faisait pas partie :0 * 5 / 9 + 32vaut toujours32, car multiplier par zéro masque n'importe quel coefficient multiplicateur. C'est exactement pour cela que le plan comporte plus d'un cas — une suite ne testant que le point de congélation aurait réussi malgré la présence du bug. Le test pour-40comporte deux lignesassert; la première a échoué, la seconde ne s'est donc jamais exécutée. C'est acceptable ici, car toutes deux décrivent la même promesse. - Les codes de sortie —
0,1,5— sont les trois que vous rencontrerez le plus souvent : tout a réussi, un test au moins a échoué, rien n'a été trouvé.
Étape 6 sur 6
Défi — le quiz du chapitre
Dix questions, de la plus facile à la plus difficile. Les dernières sont difficiles exprès.
Connectez-vous pour faire le quiz