3.4 · Tests automatisés (pytest) & typage (type hints)

Niveau 3 · Python intermédiaire

3.4Tests automatisés (pytest) & typage (type hints)

Objectif : fiabiliser le code avec des tests automatisés pytest (nominal + limites + erreurs) et annoter les types (hints aux frontières, mypy) — les deux marques du code professionnel.
Temps estimé : 14 min · lecture autonome · Si vous n'avez que 5 min : lisez « L'essentiel en 30s » + le schéma.

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.

Erreur d'interprétation fréquente : « Les tests font perdre du temps et les hints sont obligatoires pour tourner ». Non, inversé : les tests font gagner du temps dès que le projet vit (régressions détectées, refactor sans crainte), et les hints sont optionnels à l'exécution. Tester après ou jamais + viser 100 % de couverture fétiche : deux impasses. Test avant, limites incluses, hints aux frontières.
Code prouvé et typé Tests avant, minimum ensuite, hints aux frontières test (rouge) Nominal +limites + erreurscode (vert) Minimumqui fait passerhints (frontières) Annoter,vérifier via mypy
Tests avant, minimum ensuite, hints aux frontières
Version texte du schéma (SEO / lecteurs d'écran / traduction) : 1) Test : Nabil écrit test_tva nominal + négatif (pytest.raises) avant de toucher le calcul. 2) Code : il corrige le minimum pour passer au vert, puis nettoie. 3) Hints : ses fonctions partagées sont annotées (float -> float) et vérifiées par mypy. Leçon : prouver avant et après — le bug ne revient pas sans prévenir.

🔎 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

  1. Installez pytest et écrivez une fonction (ex. tva) puis ses tests dans test_prix.py avec des assert.
  2. Lancez pytest : observez le rapport, provoquez un échec volontaire et lisez le diagnostic.
  3. Annotez vos fonctions : def moyenne(notes: list[float]) -> float.
  4. Ajoutez un test de cas limite (liste vide, prix négatif) avec pytest.raises pour l'erreur.
Vous écrivez des tests pytest et annotez vos fonctions : code sécurisé, documenté et maintenable.

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.

Checklist sortie de section
  • 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.
Auto-évaluation 1-5 : un bug pourrait-il revenir sans faire échouer un test ?mes frontières sont-elles annoté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.

Vérifiez votre compréhension

Que vérifie un bon trio minimal de tests ?

Ressources supplémentaires (optionnel, pour aller plus loin)

Examen du niveau 3Questions inédites et durcies pour valider vos acquis