Se moquer en Python : unittest.mock, patch et les erreurs qui font mentir les tests

  1. Accueil
  2. Blog
  3. Automatisation des tests

Automatisation des tests

Se moquer en Python : unittest.mock, patch et les erreurs qui font mentir les tests

Les simulations vous permettent de tester du code sans véritable réseau, base de données ou horloge. Utilisés avec négligence, ils laissent également passer du code défectueux. Voici comment bien utiliser unittest.mock, avec chaque exemple exécuté.

8 min de lecture
Mocking en Python : unittest.mock, patch et les erreurs qui font mentir les tests

À retenir

  • Un simulacre remplace un objet réel, tel qu’un client API, afin que les tests s’exécutent rapidement et hors ligne.
  • Utilisez return_value pour une réponse fixe et side_effect pour des erreurs ou une séquence.
  • Corrigez le nom là où il est utilisé, pas là où il est défini. C’est le bug le plus courant.
  • Utilisez spec ou autospec pour que les fautes de frappe et les arguments erronés échouent au lieu de passer silencieusement.

Votre fonction appelle une API météo. Votre test ne doit pas dépendre d’Internet, d’une clé API ou de la météo du lendemain. Le unittest.mock intégré de Python vous permet de remplacer cet appel par un remplaçant qui renvoie exactement ce que vous choisissez.

Nous avons écrit un petit module weather et neuf tests, tous exécutés avec le test unitaire de Python 3.13. Les neuf tests ont réussi et les résultats imprimés ci-dessous proviennent de cette exécution.

Pourquoi se moquer ?

  • Vitesse : aucun appel réseau ni configuration de base de données.
  • Fiabilité : les tests n’échouent pas parce qu’un service tiers est en panne.
  • Contrôle : vous pouvez simuler des délais d’attente, des erreurs et des données impaires à la demande.
  • Isolement : vous testez votre logique, pas le serveur de quelqu’un d’autre.

Les simulations appartiennent principalement aux tests unitaires. Les tests d’intégration doivent toujours parler de dépendances réelles ou réalistes.

Les bases des simulations

from unittest.mock import Mock

api = Mock()
api.get_price.return_value = 499

print(api.get_price("pen"))                # 499
api.get_price.assert_called_once_with("pen")
print(api.get_price.call_args_list)        # [call('pen')]
print(api.anything_at_all())               # <Mock name='mock.anything_at_all()' ...>

Un Mock enregistre chaque appel afin que vous puissiez vérifier comment il a été utilisé. Notez cependant la dernière ligne. Un simple Mock invente n’importe quel attribut que vous demandez. Cette flexibilité est pratique, mais aussi dangereuse, comme nous le verrons.

MagicMock est la même idée mais prend également en charge les méthodes « magiques » comme __len__ et __iter__. patch() utilise MagicMock par défaut.

side_effect : erreurs et séquences

side_effect contrôle ce qui se passe à chaque appel. Définissez-le sur une exception pour simuler un échec :

api.fetch.side_effect = ConnectionError("down")
api.fetch()   # raises ConnectionError

Définissez-le sur une liste pour renvoyer des résultats différents lors d’appels successifs. C’est parfait pour tester la logique de nouvelle tentative :

def fetch_with_retry(api, attempts=3):
    for _ in range(attempts):
        try:
            return api.fetch()
        except ConnectionError:
            continue
    raise ConnectionError("all attempts failed")

api = Mock()
api.fetch.side_effect = [ConnectionError("busy"), {"ok": True}]
print(fetch_with_retry(api))    # {'ok': True}
print(api.fetch.call_count)     # 2

Le premier appel a échoué, le second a réussi et le test a prouvé que la fonction avait réessayé exactement une fois. Simuler cela avec un service réel serait presque impossible.

patch() : remplace quelque chose temporairement

Souvent, la dépendance n’est pas transmise. Elle est importée dans le module. patch() l’échange pendant la durée d’un test et le restaure ensuite :

# weather.py
from http_client import get as requests_get

def report(city):
    data = requests_get(f"https://api.example.com/weather?city={city}")
    return f"{city}: {data['temp_c']}°C"
from unittest.mock import patch

with patch("weather.requests_get") as fake_get:
    fake_get.return_value = {"temp_c": 31}
    print(weather.report("Pune"))   # Pune: 31°C
    fake_get.assert_called_once_with(
        "https://api.example.com/weather?city=Pune")

Vous pouvez utiliser patch comme gestionnaire de contexte comme celui-ci, ou comme décorateur, qui transmet la simulation comme argument.

Patch là où il est utilisé, pas là où il est défini

C’est l’erreur que presque tout le monde commet une fois. La vraie fonction get se trouve dans http_client, il semble donc naturel de l’y patcher :

with patch("http_client.get") as fake_get:
    fake_get.return_value = {"temp_c": 99}
    weather.report("Pune")

# ConnectionError: no network in tests: https://api.example.com/weather?city=Pune

La vraie fonction s’est exécutée. Lorsque weather.py a fait from http_client import get as requests_get, il a créé son propre nom pointant vers la fonction d’origine. Le correctif http_client.get a changé un nom différent, donc weather n’a jamais vu la simulation. Notre http_client.get relance volontairement, donc l’erreur était flagrante. Dans un projet réel, il pourrait appeler discrètement une API en direct.

Règle : corrigez le nom dans le module testé, écrit sous la forme "module_under_test.name". Si le module a fait import http_client et appelle http_client.get(), alors le correctif "http_client.get" est correct, car la recherche a lieu au moment de l’appel.

spec et autospec : rendre les simulations strictes

Un simple Mock accepte tout, y compris les fautes de frappe. Avec une spécification, cela n’autorise que les attributs de la vraie classe :

api = Mock(spec=weather.WeatherClient)
api.get_temprature("Pune")
# AttributeError: Mock object has no attribute 'get_temprature'

loose = Mock()
loose.get_temprature("Pune")    # returns a Mock, no error at all

Le modèle libre acceptait volontiers la méthode des fautes d’orthographe, de sorte qu’un test l’utilisant pouvait réussir même si le vrai code plantait. autospec va plus loin et vérifie les signatures de méthodes :

with patch("weather.WeatherClient", autospec=True) as Fake:
    client = Fake.return_value
    client.get_temperature("Pune", "extra", "args")
# TypeError: too many positional arguments

Préférez autospec=True ou create_autospec() lorsque vous vous moquez de vos propres classes. Cela permet de garder les simulations honnêtes à mesure que le vrai code change.

Dates et heures moqueuses

Le code qui dépend de la date du jour est difficile à tester. Enveloppez l’horloge dans une petite fonction et corrigez cela :

def today():
    return datetime.date.today()

def is_weekend():
    return today().weekday() >= 5

@patch("weather.today")
def test_date(self, fake_today):
    fake_today.return_value = datetime.date(2026, 11, 7)
    print(weather.is_weekend())   # True (a Saturday)

Vous ne pouvez pas facilement patcher datetime.date.today directement, car les types intégrés ne permettent pas de définir leurs attributs. Des bibliothèques telles que Freezegun et Time-Machine gèrent cela de manière plus complète.

Affirmations sur les moqueries

Méthode Chèques
assert_called() Appelé au moins une fois
assert_called_once() Appelé exactement une fois
assert_called_with(...) Le dernier appel a utilisé ces arguments
assert_called_once_with(...) Exactement un appel, avec ces arguments
assert_any_call(...) Tout appel a utilisé ces arguments
assert_not_called() Jamais appelé

Lorsqu’une assertion échoue, le message est clair. Nous avons appelé assert_called_once() sur une méthode qui n’a jamais fonctionné :

AssertionError: Expected 'save' to have been called once. Called 0 times.

Les anciennes versions de Python laissent passer les méthodes d’assertion mal orthographiées, telles que assret_called_once, en silence. Les versions modernes génèrent une erreur pour les noms commençant par assert ou pour les fautes d’orthographe courantes, mais une spécification reste la meilleure protection.

pytest et se moque

Tout ce qui précède fonctionne également dans pytest. De nombreux utilisateurs de pytest utilisent également le luminaire intégré monkeypatch pour des remplacements simples, ou le plugin pytest-mock, qui fournit un luminaire mocker qui annule automatiquement les correctifs. La règle du « patch où il est utilisé » s’applique à tous. Consultez notre tutoriel pytest pour les appareils et la configuration.

Quand ne pas se moquer

  • Ne vous moquez pas de ce que vous testez. Si la simulation fait tout le travail, le test ne prouve rien.
  • Évitez de vous moquer des objets à valeur simple. Utilisez des objets réels.
  • Ne vous moquez pas de tout. Les tests remplis de simulations échouent souvent chaque fois que le code est refactorisé, même lorsque le comportement est correct.
  • Effectuez de vrais tests d’intégration. Les simulations encodent vos hypothèses sur un service, et ces hypothèses peuvent être fausses.

Les simulations gonflent également facilement la couverture du code, car les chemins simulés s’exécutent sans aucun comportement réel derrière eux.

Erreurs moqueuses courantes

  • Corriger le mauvais nom. Couvert ci-dessus, et toujours la cause numéro un de “ma simulation est ignorée”.
  • Oublier de définir return_value. Le code testé reçoit alors un objet Mock au lieu de données. Il peut passer directement par votre fonction et faire passer un test par accident, par exemple lorsque vous vérifiez seulement qu’un résultat est véridique.
  • Affirmer la simulation au lieu du résultat. Vérifier que save() a été appelé est plus faible que vérifier ce qui a été enregistré. Utilisez assert_called_once_with(...) avec de vrais arguments, ou mieux, affirmez le résultat de la fonction.
  • Fuite de correctifs. L’appel de patcher.start() sans stop() laisse le faux en place pour des tests ultérieurs. Utilisez le gestionnaire de contexte, le décorateur ou addCleanup(patcher.stop).
  • Décorateurs empilés dans le mauvais ordre. Avec plusieurs @patch décorateurs, celui du bas est passé comme premier argument. Les mélanger silencieusement configure la mauvaise simulation.
  • Se moquer d’une bibliothèque que vous ne possédez pas à travers de nombreux tests. Enveloppez-le dans votre propre petite classe et moquez-vous-en à la place. Vos tests décrivent ensuite vos besoins, pas l’API de la bibliothèque.

Mocks, talons et contrefaçons

Ces mots sont souvent utilisés de manière vague, mais les différences sont utiles lors des discussions sur les tests. Un stub renvoie simplement des réponses prédéfinies. Une simulation enregistre également les appels afin que vous puissiez vérifier comment elle a été utilisée. Un fake est une implémentation fonctionnelle et simplifiée, telle qu’un référentiel en mémoire au lieu d’une base de données. Python Mock peut servir de stub ou de simulation. Les faux sont des classes ordinaires que vous écrivez vous-même, et ils conduisent souvent à des tests qui survivent mieux au refactoring, car ils vérifient le comportement plutôt que les appels exacts.

Une bonne règle de base consiste à se moquer des bords de votre système : le réseau, l’horloge, les nombres aléatoires, le système de fichiers et les services tiers payants. À l’intérieur de ces bords, préférez les objets réels. Les tests construits de cette manière restent rapides et appliquent toujours le code que vous avez réellement écrit.

Pour conclure

La moquerie est un outil précis : remplacez les dépendances lentes ou imprévisibles, contrôlez ce qu’elles renvoient et vérifiez comment votre code les a utilisées. Deux habitudes préviennent la plupart des problèmes. Corrigez le nom dans le module testé et utilisez la spécification automatique pour que vos simulations rejettent les appels que l’objet réel rejetterait.

Questions fréquentes

Qu’est-ce qui se moque en Python ?

Mocking remplace un objet réel, tel qu’un client API ou une base de données, par un faux que vous contrôlez lors d’un test. Python l’inclut dans le module unittest.mock.

Quelle est la différence entre Mock et MagicMock ?

MagicMock est un Mock qui prend également en charge les méthodes magiques telles que __len__, __iter__ et __enter__. patch() crée un MagicMock par défaut.

Pourquoi mon patch ne fonctionne-t-il pas ?

Généralement parce que vous avez corrigé l’endroit où l’objet est défini au lieu de l’endroit où il est utilisé. Corrigez le nom à l’intérieur du module testé.

Quelle est la différence entre return_value et side_effect ?

return_value définit un résultat fixe. side_effect peut déclencher une exception, renvoyer les valeurs d’une liste dans l’ordre ou appeler une fonction.

Que fait la spécification automatique ?

Il construit une simulation à partir de l’objet réel, donc les attributs manquants et les arguments erronés génèrent des erreurs, tout comme ils le feraient avec le code réel.

Continuez à apprendre

Travailler avec Stack Quora

Besoin d’aide pour les tests, WordPress ou le référencement ?

Nous aidons les équipes à proposer de meilleurs logiciels et à développer leurs sites. Dites-nous sur quoi vous travaillez et nous vous proposerons une prochaine étape claire.

Continuez à lire

Plus de guides sur tests, IA et développement moderne

Vous avez apprécié cet article ? Choisissez un sujet et accédez directement à nos nouveaux didacticiels.

Intelligence artificielle

Les hallucinations de l'IA expliquées : pourquoi les modèles de langage inventent les choses et comment les réduire

Les hallucinations de l'IA ont expliqué : ce qu'elles sont, pourquoi les modèles de langage les produisent, les types courants, comment les réduire …

Lire l'article →
DevOps

Variables d'environnement expliquées : fichiers .env, secrets et configuration correctement effectués

Variables d'environnement expliquées avec des exemples Python que nous avons exécutés : lecture et définition, pourquoi chaque valeur est une chaîne…

Lire l'article →
Développement logiciel

Limitation du débit de l'API expliquée : algorithmes, en-têtes et gestion des erreurs 429

Limitation du débit de l'API expliquée avec une simulation que nous avons exécutée : compartiment de jetons, fenêtre fixe, fenêtre glissante, 429 ré…

Lire l'article →
Automatisation des tests

Se moquer en Python : unittest.mock, patch et les erreurs qui font mentir les tests

Moquerie en Python avec unittest.mock, testée : Mock et return_value, side_effect, correctifs où un nom est utilisé, spec et autospec, dates moqueus…

Lire l'article →
Tests logiciels

Couverture du code expliquée : couverture des lignes par rapport aux branches et ce que signifie réellement 100 %

Couverture du code expliquée avec une véritable exécution de cover.py : couverture des lignes, des branches et des conditions, lecture d'un rapport,…

Lire l'article →
Bases de données et SQL

DELETE vs TRUNCATE vs DROP dans SQL : différences, restauration et quand les utiliser

DELETE vs TRUNCATE vs DROP expliqué avec SQL testé : ce que chacun supprime, restauration, déclencheurs, réinitialisation d'identité, vitesse et en …

Lire l'article →
Bases de données et SQL

Vues SQL expliquées : créez, utilisez et évitez les pièges, avec des exemples

Vues SQL expliquées avec les requêtes que nous avons exécutées : création d'une vue, résultats en direct, masquage des colonnes, performances, vues …

Lire l'article →
Développement logiciel

Interface TypeScript vs Type : différences réelles, testées avec le compilateur

Interface TypeScript vs type, vérifiée avec le vrai compilateur : ce que les deux peuvent faire, ce qu'un seul peut faire, la fusion des déclaration…

Lire l'article →
Développement logiciel

Python *args et **kwargs expliqués : fonctions flexibles avec des exemples clairs

Python *args et **kwargs expliqués avec le code que nous avons exécuté : emballage et déballage, ordre des paramètres, arguments de mot-clé uniqueme…

Lire l'article →
Développement logiciel

Fonctions Python Lambda : syntaxe, utilisations réelles et quand ne pas les utiliser

Fonctions Python lambda expliquées avec le code que nous avons exécuté : syntaxe, tri avec clé, carte et filtre, fermetures, piège de liaison tardiv…

Lire l'article →
Intelligence artificielle

Que sont les agents IA ? Comment ils fonctionnent, avec une simple boucle d'agent que vous pouvez exécuter

Que sont les agents IA ? Un guide simple sur la boucle d'agent, les outils, la mémoire et les garde-fous, avec une petite boucle Python que nous avo…

Lire l'article →
DevOps

Surveillance vs observabilité : la vraie différence, avec les journaux, les métriques et les traces

Surveillance et observabilité expliquées : échecs connus et inconnus, journaux, métriques et traces, présentation pas à pas des incidents de paiemen…

Lire l'article →
DevOps

Volumes Docker vs montages Bind : conserver les données du conteneur de la bonne manière

Volumes Docker et montages de liaison expliqués : pourquoi les données du conteneur disparaissent, volumes nommés, montages de liaison, tmpfs, exemp…

Lire l'article →
Automatisation des tests

TDD vs BDD : principales différences, une fonctionnalité conçue dans les deux sens et quand les utiliser

TDD vs BDD expliqué avec une fonctionnalité de remise construite dans les deux sens : refactor rouge-vert avec le code que nous avons exécuté, scéna…

Lire l'article →
Développement logiciel

Git merge vs rebase : différences, exemples et quand utiliser chacun

Git merge vs rebase expliqué avec de vraies sorties de log, un tableau comparatif, la règle d'or du rebase et un flux de travail sûr pour les équipes.

Lire l'article →
Développement logiciel

List comprehension Python : syntaxe, exemples et cas à éviter

La list comprehension Python expliquée : syntaxe, filtres, if-else, boucles imbriquées, temps mesurés et cas où une boucle for classique est préféra…

Lire l'article →
Bases de données et SQL

Jointures SQL expliquées avec exemples (Inner, Left, Right, Full)

Comprenez les jointures SQL pas à pas avec des tables et requêtes d'exemple : INNER, LEFT, RIGHT, FULL, CROSS et SELF, plus erreurs courantes et con…

Lire l'article →