Python : qualité et tests
Structure de projet
Section intitulée « Structure de projet »monprojet/├── pyproject.toml├── src/│ └── monapp/│ ├── __init__.py│ └── calcul.py└── tests/ └── test_calcul.pyInstaller les outils dans l’environnement du projet :
pip install pytest ruff mypyTests avec pytest
Section intitulée « Tests avec pytest »src/monapp/calcul.py :
def additionner(a: int, b: int) -> int: return a + btests/test_calcul.py :
import pytest
from monapp.calcul import additionner
def test_additionner(): assert additionner(2, 3) == 5
@pytest.mark.parametrize("a, b, attendu", [(0, 0, 0), (-1, 1, 0), (10, 5, 15)])def test_cas(a, b, attendu): assert additionner(a, b) == attendu
def test_exception(): with pytest.raises(TypeError): additionner("a", None) + 1pytest # tous les testspytest -v # détailpytest -x # s'arrête au premier échecpytest tests/test_calcul.py::test_caspytest -k "cas" # tests dont le nom contient « cas »pytest --lf # relance les derniers échecsConfiguration dans pyproject.toml :
[tool.pytest.ini_options]pythonpath = ["src"]testpaths = ["tests"]Fixtures
Section intitulée « Fixtures »import pytest
@pytest.fixturedef client(): return {"nom": "Alice", "email": "alice@exemple.com"}
def test_email(client): assert client["email"].endswith("@exemple.com")
def test_fichier(tmp_path): f = tmp_path / "donnees.txt" f.write_text("ok") assert f.read_text() == "ok"tmp_path fournit un dossier temporaire propre à chaque test.
Formatage et analyse avec ruff
Section intitulée « Formatage et analyse avec ruff »ruff check . # analyse (erreurs probables, imports inutilisés…)ruff check . --fix # corrige automatiquement ce qui peut l'êtreruff format . # formate le coderuff format --check . # vérifie seulement (CI)Configuration :
[tool.ruff]line-length = 100target-version = "py312"
[tool.ruff.lint]select = ["E", "F", "I", "B", "UP"]| Jeu de règles | Contrôle |
|---|---|
E, F |
Style et erreurs (pyflakes) |
I |
Tri des imports |
B |
Pièges fréquents (bugbear) |
UP |
Modernisation de la syntaxe |
Types avec mypy
Section intitulée « Types avec mypy »mypy src[tool.mypy]python_version = "3.12"strict = truestrict = true est exigeant : l’activer dès le début d’un projet, ou progressivement sur un projet existant.
Tout lancer en une commande
Section intitulée « Tout lancer en une commande »Script verifier.sh :
#!/bin/bashset -euo pipefailruff check .ruff format --check .mypy srcpytest -qchmod +x verifier.sh./verifier.shLa même suite s’exécute en intégration continue (voir Gitea : Actions).
Couverture
Section intitulée « Couverture »pip install pytest-covpytest --cov=monapp --cov-report=term-missingDépannage
Section intitulée « Dépannage »| Symptôme | Piste |
|---|---|
ModuleNotFoundError: No module named 'monapp' |
Ajouter pythonpath = ["src"] ou installer le projet : pip install -e . |
| Aucun test collecté | Fichiers test_*.py, fonctions test_* |
ruff: command not found |
Environnement non actif ou outil non installé |
mypy signale des paquets sans types |
pip install types-requests (paquet types-… correspondant) ou ignore_missing_imports |
| Tests qui passent en local mais échouent en CI | Versions non figées ou dépendance à l’ordre des tests |
Ubuntu 24.04 et 26.04 : différences à connaître
Section intitulée « Ubuntu 24.04 et 26.04 : différences à connaître »Ces outils fonctionnent sous Python 3.12 et 3.14. Adapter target-version et python_version à la plus ancienne version que le projet doit supporter.
Pour aller plus loin
Section intitulée « Pour aller plus loin »- pytest, ruff, mypy
- Suite : Mise en production