16. Tests et Packaging
unittest,pytest,logging,pyproject.toml,uv, publication.
16.1 unittest
Module de test intégré :
# test_maths.py
import unittest
from mon_module import addition, division
class TestMaths(unittest.TestCase):
def test_addition(self):
self.assertEqual(addition(2, 3), 5)
self.assertEqual(addition(-1, 1), 0)
def test_division(self):
self.assertEqual(division(10, 2), 5)
with self.assertRaises(ValueError):
division(10, 0)
if __name__ == "__main__":
unittest.main()python3 -m unittest test_maths.py
python3 -m unittest discover # découverte automatique16.2 pytest
Plus concis et puissant :
# test_maths.py
from mon_module import addition, division
def test_addition():
assert addition(2, 3) == 5
assert addition(-1, 1) == 0
def test_division():
assert division(10, 2) == 5
def test_division_par_zero():
with pytest.raises(ValueError):
division(10, 0)pip install pytest
pytest # découverte automatique
pytest test_maths.py -v # verbeux
pytest -k "addition" # filtre par nom
pytest --cov=mon_module tests/ # couverture (pytest-cov)Fixtures
import pytest
@pytest.fixture
def données():
return {"nom": "Alice", "âge": 30}
def test_traitement(données):
resultat = traiter(données)
assert resultat["nom"] == "ALICE"Paramétrisation
@pytest.mark.parametrize("a,b,attendu", [
(1, 2, 3),
(-1, 1, 0),
(0, 0, 0),
])
def test_addition(a, b, attendu):
assert addition(a, b) == attenduMocking
from unittest.mock import Mock, patch
def test_api():
with patch("mon_module.requests.get") as mock_get:
mock_get.return_value.json.return_value = {"id": 1}
resultat = mon_appel_api()
assert resultat["id"] == 116.3 logging
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
logger = logging.getLogger(__name__)
logger.debug("Détail de débogage")
logger.info("Opération réussie")
logger.warning("Attention")
logger.error("Erreur critique")
logger.critical("Fatal")Bonnes pratiques :
- Utiliser
loggingpasprintpour les messages du programme. - Configurer le niveau par environnement (
DEBUG,INFO,WARNING). - Utiliser
getLogger(__name__)par module.
Configuration fichier
logging.config.dictConfig({
"version": 1,
"handlers": {
"fichier": {
"class": "logging.FileHandler",
"filename": "app.log",
},
},
"root": {
"handlers": ["fichier"],
"level": "INFO",
},
})16.4 Packaging avec pyproject.toml
Structure minimale :
mon_projet/
├── pyproject.toml
├── src/
│ └── mon_paquet/
│ ├── __init__.py
│ └── module.py
├── tests/
│ └── test_module.py
└── README.md
pyproject.toml :
[build-system]
requires = ["setuptools>=75"]
build-backend = "setuptools.backends._legacy:_Backend"
[project]
name = "mon-paquet"
version = "0.1.0"
description = "Description courte"
requires-python = ">=3.10"
dependencies = [
"numpy>=1.26",
"requests>=2.32",
]
[project.optional-dependencies]
dev = [
"pytest>=8",
"pytest-cov>=5",
"mypy>=1.10",
]
[tool.pytest.ini_options]
testpaths = ["tests"]16.5 uv — gestionnaire moderne
uv init mon_projet # créer un projet
uv add numpy # ajouter dépendance
uv add --dev pytest # dépendance de développement
uv run python script.py # exécuter dans l'environnement
uv build # construire le paquet
uv publish # publier sur PyPI16.6 Tests de performance
import timeit
# Temps d'exécution
timeit.timeit('"-".join(str(n) for n in range(100))', number=10000)
# Depuis pytest
def test_perf():
début = time.perf_counter()
fonction_lourde()
durée = time.perf_counter() - début
assert durée < 1.0 # moins d'1 seconde