Ton script fonctionne sur ta machine, tu le déploies, et trois jours plus tard quelqu'un t'écrit que tout est cassé. Tu passes deux heures à traquer un bug que tu aurais pu attraper en dix secondes. La solution ne s'appelle pas « plus de prudence », elle s'appelle Pytest. C'est l'outil de test le plus utilisé de l'écosystème Python, il repose sur une syntaxe que tu connais déjà (le mot-clé assert), et tu peux écrire tes premiers tests dans les vingt prochaines minutes. Pas de théorie inutile, juste du code qui surveille ton code.
Étape 1 — Installe Pytest. Un pip install et c'est parti. Pytest ne t'impose aucune API compliquée : il exécute des fonctions Python ordinaires et regarde simplement si les assert passent. Bonus non négligeable : il sait aussi lancer tes vieux tests écrits avec unittest, donc rien ne se perd si tu migres un projet existant.
pip install pytestÉtape 2 — Écris ton premier test. Crée deux fichiers côte à côte : un module qui contient ta logique, et un fichier de test. Par convention, Pytest repère automatiquement les fichiers nommés test_*.py et les fonctions qui commencent par test_. C'est tout ce qu'il demande : aucune classe, aucun héritage, aucune inscription.
# calcul.py
def ajouter(a, b):
return a + b
def diviser(a, b):
if b == 0:
raise ValueError("Division par zéro")
return a / b
# test_calcul.py
from calcul import ajouter, diviser
def test_ajouter():
assert ajouter(2, 3) == 5
def test_ajouter_negatifs():
assert ajouter(-2, -3) == -5
def test_diviser():
assert diviser(10, 2) == 5Lance la commande pytest dans ton terminal. Chaque test qui passe affiche un point vert ; un échec s'affiche en rouge avec le détail de l'assertion. Pour tester la bête, modifie volontairement ajouter(2, 3) pour qu'il renvoie 6 et relance : tu verras exactement où et pourquoi ça casse. C'est là tout l'intérêt : le test échoue à ta place, avant que ce soit un utilisateur qui le découvre.
Pourquoi Pytest plutôt que unittest ? La bibliothèque unittest de la standard library fonctionne, mais elle t'impose des classes et des méthodes du genre self.assertEqual(ajouter(2, 3), 5). Pytest te laisse écrire un simple assert, ce qui rend chaque test plus court, plus lisible, et donc plus facile à maintenir quand ton projet grossit. Le rapport d'erreur est aussi nettement plus parlant.
Étape 3 — Vérifie que ton code échoue quand il doit échouer. Un bon test ne se contente pas de confirmer le cas nominal, il verrouille aussi les cas limites. Pour vérifier qu'une exception est bien levée, utilise pytest.raises. C'est indispensable pour les fonctions qui doivent refuser les entrées invalides plutôt que de renvoyer un résultat absurde en silence.
import pytest
from calcul import diviser
def test_division_par_zero():
with pytest.raises(ValueError):
diviser(10, 0)
def test_division_normale():
assert diviser(9, 3) == 3Étape 4 — Partage un contexte avec les fixtures. Quand plusieurs tests ont besoin du même objet (une connexion, une liste de données, un modèle chargé), tu ne veux pas le recréer à la main dans chaque fonction. Une fixture prépare ce contexte une seule fois, puis Pytest l'injecte automatiquement dans tous les tests qui le demandent en paramètre. Résultat : moins de duplication, et un code de test plus clair.
import pytest
@pytest.fixture
def panier():
# Prépare un panier de fruits réutilisable par tous les tests
return {"pommes": 3, "poires": 2, "bananes": 5}
def test_total_fruits(panier):
assert sum(panier.values()) == 10
def test_panier_non_vide(panier):
assert len(panier) == 3Étape 5 — Décline un test sur des dizaines de cas avec parametrize. Tester un seul exemple, c'est rassurant. Tester vingt exemples d'un coup, c'est solide. Avec @pytest.mark.parametrize, tu écris la logique de test une seule fois et Pytest la rejoue sur chaque jeu de données. Si un couple échoue, le rapport te dit immédiatement lequel pose problème, sans que tu aies à bidouiller des dizaines de fonctions.
import pytest
from calcul import ajouter
@pytest.mark.parametrize("a, b, attendu", [
(1, 2, 3),
(0, 0, 0),
(-5, 5, 0),
(100, 200, 300),
(-3, -7, -10),
])
def test_ajouter_parametre(a, b, attendu):
assert ajouter(a, b) == attenduÉtape 6 — Simule le monde extérieur avec monkeypatch. Dans la vraie vie, tes fonctions appellent des APIs, une base de données ou un modèle de langage. Tu ne veux pas dépendre d'un réseau réel ni payer un appel à chaque exécution de tes tests. monkeypatch te permet de remplacer n'importe quelle fonction par un faux le temps d'un test, afin d'isoler ton code et de ne tester que ce qui t'appartient.
import requests
def test_prix_avec_fausse_api(monkeypatch):
def faux_get(url):
return {"prix": 42}
monkeypatch.setattr(requests, "get", faux_get)
# Ton code qui appelle requests.get tourne ici sans réseau
assert recuperer_prix("pomme") == 42Étape 7 — Lis le rapport et prends le réflexe. Quand un test échoue, Pytest ne te dit pas juste « ça a planté ». Il affiche la valeur attendue, la valeur obtenue et la ligne exacte qui a déraillé. Utilise pytest -v pour voir chaque test nommé, et pytest -k ajouter pour ne lancer qu'une famille de tests. Cette précision transforme le débogage : tu passes de « quelque chose ne va pas » à « ici, précisément, et voici pourquoi ». Lance pytest avant chaque commit, glisse un dossier tests/ dans tes projets, et considère un bug comme corrigé seulement quand un test le prouve.
Enfin, muscle ta ligne de commande : pytest -q pour un rapport compact, pytest --maxfail=1 pour t'arrêter au premier échec, et pytest --pdb pour plonger directement dans le débogueur dès qu'un test casse. Ces trois options couvrent 90 % de ton quotidien de testeur, le reste viendra avec la pratique.
Pytest ne remplace pas ta vigilance, il la démultiplie. La prochaine fois que tu modifies une fonction, tes tests te diront en quelques secondes si tu as cassé quelque chose ailleurs. C'est un filet de sécurité qui coûte quelques minutes d'écriture et t'en rend des heures. Adopte-le aujourd'hui, et tu déploieras sans trembler. Ton sommeil te remerciera.






