pytest
Le framework de tests Python le plus utilisé. Syntaxe simple (un assert
suffit), découverte automatique des tests, fixtures puissantes — c'est l'outil de référence
pour vérifier que ton code fait ce qu'il dit.
À quoi ça sert
Tester son code, c'est écrire d'autres morceaux de code qui vérifient que ton code se comporte comme prévu. Pourquoi s'embêter ?
- Confiance — quand tu modifies une fonction, tu sais immédiatement si tu as cassé autre chose.
- Documentation vivante — un test montre comment utiliser ta fonction.
- Détection précoce — un bug attrapé en local coûte 10× moins cher qu'un bug en prod.
Python a un module unittest intégré, mais il est verbeux. pytest
propose la même chose en plus simple, avec en bonus des fixtures, du paramétrage et un
écosystème énorme de plugins.
pytest = écrire des tests Python en quelques lignes, et savoir en un coup d'œil si ton code marche encore.
Un exemple d'usage
Tu as une fonction qui calcule le prix TTC à partir d'un prix HT. Tu veux t'assurer
qu'elle est correcte. Crée un fichier test_prix.py à côté de ton code :
from prix import calcul_ttc
def test_ttc_simple():
assert calcul_ttc(100) == 120.0
def test_ttc_zero():
assert calcul_ttc(0) == 0
def test_ttc_refuse_negatif():
import pytest
with pytest.raises(ValueError):
calcul_ttc(-10)
Tu lances pytest dans le terminal, et tu obtiens :
collected 3 items
test_prix.py ... [100%]
============== 3 passed in 0.02s ===============
Trois tests passent. Si tu casses la fonction, pytest pointe la ligne assert
qui échoue, avec la valeur attendue et la valeur obtenue.
How-to : installer et utiliser pytest
-
Installer pytest comme dépendance de dev
Avec UV (voir la fiche UV) :
bashuv add --dev pytestOu avec pip :
bashpip install pytestAstuceLe flag
--devindique que pytest n'est pas une dépendance de production : on n'en a besoin que pour développer et tester. -
Structurer ton projet
pytest suit une convention simple :
arbomon-projet/ ├── src/ │ └── calculs.py ├── tests/ │ └── test_calculs.py └── pyproject.tomlpytest découvre automatiquement les fichiers
test_*.pyou*_test.py, et dans chacun, les fonctions commençant partest_. -
Écrire un premier test
Fichier
src/calculs.py:pythondef addition(a, b): return a + b def division(a, b): if b == 0: raise ValueError("division par zéro") return a / bFichier
tests/test_calculs.py:pythonimport pytest from calculs import addition, division def test_addition(): assert addition(2, 3) == 5 def test_division_normale(): assert division(10, 2) == 5 def test_division_par_zero(): with pytest.raises(ValueError, match="division par zéro"): division(10, 0) -
Lancer les tests
bash# Tous les tests uv run pytest # Mode verbeux (un nom de test par ligne) uv run pytest -v # Un seul fichier uv run pytest tests/test_calculs.py # Un seul test uv run pytest tests/test_calculs.py::test_addition # Filtre par nom (mots-clés) uv run pytest -k "division and not zero" # S'arrêter au premier échec uv run pytest -x -
Paramétrer un test (data-driven)
Plutôt que d'écrire 5 fois le même test avec des valeurs différentes, utilise
parametrize:pythonimport pytest from calculs import addition @pytest.mark.parametrize("a, b, attendu", [ (2, 3, 5), (0, 0, 0), (-1, 1, 0), (1.5, 2.5, 4.0), ]) def test_addition(a, b, attendu): assert addition(a, b) == attendupytest crée automatiquement 4 tests à partir de cette définition.
-
Utiliser des fixtures (données partagées)
Une fixture prépare un objet réutilisable par plusieurs tests (un fichier temporaire, une connexion DB, un client HTTP…).
pythonimport pytest @pytest.fixture def utilisateur(): return {"nom": "Alice", "role": "admin"} def test_nom(utilisateur): assert utilisateur["nom"] == "Alice" def test_role(utilisateur): assert utilisateur["role"] == "admin"pytest voit le paramètre
utilisateurdans la signature et injecte automatiquement la valeur retournée par la fixture du même nom.Fixtures partagéesPlace tes fixtures communes dans
tests/conftest.py: pytest les rendra disponibles dans tous les fichiers de test sans import. -
Mesurer la couverture (coverage)
Pour savoir quelle proportion de ton code est testée, ajoute le plugin
pytest-cov:bashuv add --dev pytest-cov uv run pytest --cov=src --cov-report=term-missingTu vois ligne par ligne ce qui est couvert et ce qui ne l'est pas.
--cov-report=htmlgénère un rapport HTML navigable. -
Configurer VS Code
- Installe l'extension Python de Microsoft.
- Ouvre la palette (Ctrl+Shift+P) →
Python: Configure Tests. - Choisis pytest puis le dossier
tests. - Une icône « bécher » apparaît dans la barre latérale : tu y vois tous tes tests, tu peux les lancer ou les déboguer en un clic.
Pour figer la config dans le projet, ajoute à
pyproject.toml:toml[tool.pytest.ini_options] testpaths = ["tests"] pythonpath = ["src"] addopts = "-v --tb=short"pythonpathévite les imports cassés quand tes tests sont dans un dossier séparé du code.
Aide-mémoire
pytest # tous les tests
pytest -v # détaillé
pytest -x # stop au 1er échec
pytest -k "motif" # filtre par nom
pytest --lf # rejouer uniquement les derniers échoués
pytest -s # affiche les print()
pytest --pdb # debugger sur échec
pytest --cov=src # couverture (avec pytest-cov)
Marqueurs utiles
@pytest.mark.skip(reason="non implémenté")
@pytest.mark.skipif(sys.platform == "win32", reason="Linux only")
@pytest.mark.xfail # attendu en échec
@pytest.mark.parametrize(...) # multi-valeurs
Pour aller plus loin
- Documentation officielle : docs.pytest.org
- Cookbook de fixtures : docs.pytest.org/.../fixtures
- Plugins populaires : liste officielle