3.4Tests automatisés (pytest) & typage (type hints)
L'essentiel en 30 secondes
- Le test est une affirmation exécutable : def test_tva(): assert tva(100) == 120 — pytest découvre les test_*.py et les joue ; on teste les limites, pas que le nominal.
- Le cycle rouge-vert-refactor protège : test d'abord (échoue), minimum ensuite (passe), nettoyage enfin — tout bug corrigé commence par un test qui reproduit.
- Les hints documentent sans contraindre : def tva(prix: float) -> float — lisible, vérifié par mypy, typage progressif aux frontières d'abord.
Mini-cas filé — Nabil prouve que ses montants sont justes
Nabil, data analyst junior à Alger, corrige un taux de TVA dans son calcul de ventes et casse silencieusement trois rapports : personne ne s'en aperçoit pendant une semaine. Il écrit des tests pytest (nominal, prix négatif qui lève ValueError), adopte le cycle rouge-vert-refactor, annote ses fonctions (prix: float) -> float et vérifie avec mypy. La prochaine régression échoue au premier pytest -q.
Dernier épisode du fil Nabil — du module rangé à l'environnement isolé jusqu'au code prouvé.
1. Le test pytest : preuve exécutable et cycle
Un test automatisé vérifie automatiquement qu'une portion du programme fait ce qu'on attend, réexécutable à tout moment : def test_tva(): assert tva(100) == 120. Avec pytest, on écrit des fonctions test_*() avec assert dans des fichiers test_*.py, lancées par la commande pytest. On teste le nominal, les cas limites (vide, zéro, négatif — là que vivent les bugs) et les erreurs (pytest.raises).
Le cycle qui protège est rouge-vert-refactor : écrire le test avant (il échoue : rouge), coder le minimum (il passe : vert), nettoyer (refactor). Chaque correction de bug commence par un test qui reproduit : le bug ne reviendra jamais sans prévenir. Nabil rejoue pytest -q après chaque changement de taux.
2. Les type hints : la doc qui se vérifie
Les annotations de type (type hints) indiquent les types attendus : def moyenne(notes: list[float]) -> float. Python les ignore à l'exécution — le code tourne sans eux — mais elles documentent, aident l'éditeur (autocomplétion, détection) et facilitent la maintenance. Typage progressif : les frontières d'abord (API, fonctions partagées), l'interne ensuite.
L'outil mypy vérifie la cohérence des hints sans exécuter le code (analyse statique), idéalement en intégration continue. Nabil annote ses fonctions de calcul partagées à Alger, teste les bords (liste vide, montant négatif) et vise des tests utiles plutôt que la course au 100 % de couverture. Hints + tests : un code qui se relit et qui prouve.
🔎 Exemple concret — Premier test qui protège le calcul
Nabil sécurise sa TVA : nominal, limite et erreur.
def tva(prix_ht: float, taux: float = 0.2) -> float:
if prix_ht < 0:
raise ValueError("prix negatif")
return prix_ht * (1 + taux)
import pytest
from prix import tva
def test_tva_nominal():
assert tva(100) == 120
def test_tva_negatif():
with pytest.raises(ValueError):
tva(-5)
Lecture : nominal + limite + erreur : le trio minimal qui protège.
Leçon : relancer pytest -q après chaque changement de taux.
Vocabulaire de la section
- Test automatisé
- Code vérifiant automatiquement qu'une portion du programme fonctionne comme prévu, réexécutable à tout moment.
Exemple simple : Les tests de TVA de Nabil rejoués après chaque modif.
Ce que ce n'est pas : un essai manuel oublié après deux jours.
Réflexe : tester nominal + limites + erreurs.
- pytest
- Framework de test : fonctions test_*() avec assert dans test_*.py, lancées par la commande pytest.
Exemple simple : Le pytest -q de Nabil après chaque changement.
Ce que ce n'est pas : des prints de vérification jetés après usage.
Réflexe : un fichier test_*.py par module testé.
- assert
- Vérifie qu'une condition est vraie, sinon le test échoue : assert tva(100) == 120.
Exemple simple : L'égalité nominale de la TVA de Nabil.
Ce que ce n'est pas : un commentaire « ça devrait marcher ».
Réflexe : asserter le résultat attendu exact.
- Type hints
- Annotations des types attendus (a: int) -> str : documentent sans contraindre l'exécution.
Exemple simple : Le (prix_ht: float, taux: float = 0.2) -> float de Nabil.
Ce que ce n'est pas : une contrainte bloquant l'exécution.
Réflexe : annoter les frontières partagées d'abord.
- mypy
- Outil vérifiant la cohérence des hints sans exécuter le code (analyse statique).
Exemple simple : La vérification des calculs de Nabil en intégration continue.
Ce que ce n'est pas : un test d'exécution remplaçant les hints.
Réflexe : hints + mypy + tests : code prouvé.
En pratique — Tester avec pytest et annoter les types
- Installez pytest et écrivez une fonction (ex. tva) puis ses tests dans test_prix.py avec des assert.
- Lancez pytest : observez le rapport, provoquez un échec volontaire et lisez le diagnostic.
- Annotez vos fonctions : def moyenne(notes: list[float]) -> float.
- Ajoutez un test de cas limite (liste vide, prix négatif) avec pytest.raises pour l'erreur.
Script prêt à dire — présenter ses tests (30 s)
« Calculs couverts par pytest : nominal, limites et erreurs via pytest.raises, cycle rouge-vert-refactor, pytest -q après chaque changement. Fonctions partagées annotées en hints et vérifiées par mypy. Toute régression échoue avant d'atteindre les rapports. »
À dire en revue ; montrer le trio nominal + limite + erreur.
Modèle copier-coller — fonction typée + tests pytest
def moyenne(notes: list[float]) -> float:
if not notes:
raise ValueError("liste vide")
return sum(notes) / len(notes)
import pytest
from stats import moyenne
def test_moyenne_nominale():
assert moyenne([10.0, 20.0]) == 15.0
def test_moyenne_vide():
with pytest.raises(ValueError):
moyenne([])
Indépendant
Trois tests (nominal + limite + erreur) suffisent à sécuriser les calculs mensuels.
PME à Alger
Nabil couvre TVA et moyennes avant d'envoyer les rapports aux responsables.
Équipe produit
Imposer pytest -q + mypy en intégration continue pour chaque fusion.
Débutant
Cette semaine : une fonction annotée et deux tests, dont un cas limite.
✓ Bonnes pratiques
- Écrire le test avant (rouge), puis le minimum (vert), puis nettoyer.
- Tester limites et erreurs, pas seulement le cas nominal.
- Annoter les frontières partagées et vérifier avec mypy.
✗ Pièges fréquents
- Tester après (ou jamais) et découvrir la régression en production.
- Ne tester que le nominal (les bords cassent en premier).
- Viser 100 % de couverture comme fétiche au lieu de tests utiles.
Points clés à retenir
- Les TESTS prouvent que le code marche — pytest : test_*() + assert, commande pytest.
- ROUGE-VERT-REFACTOR : test avant, minimum après, nettoyage — bug = test qui reproduit.
- Les HINTS documentent les types SANS changer l'exécution ; mypy les VÉRIFIE.
- LIMITES + erreurs d'abord (vide, négatif, zéro) : c'est là que ça casse.
Défi 48h — appliquez demain
Couvrez 1 fonction par 3 tests (nominal + limite + erreur) et annotez-la en hints.
- Mes tests couvrent nominal, limite et erreur.
- Je pratique rouge-vert-refactor sur chaque bug.
- Mes fonctions partagées sont annotées et vérifiées.
Questions fréquentes
Écrire des tests, n'est-ce pas une perte de temps ?
Non, c'est rentable : les tests détectent la régression aussitôt, autorisent le refactor sans crainte et documentent le comportement attendu. Sur un projet qui vit, le temps gagné à ne pas chasser des bugs dépasse largement l'écriture des tests.
Les type hints sont-ils obligatoires ?
Non, Python les ignore à l'exécution. Mais ils améliorent autocomplétion, détection d'erreurs et lisibilité — fortement recommandés en équipe ou sur grand projet, et vérifiables avec mypy.
Que vérifie un bon trio minimal de tests ?