Spott in Python: unittest.mock, Patch und die Fehler, die Tests zum Lügen machen

  1. Startseite
  2. Blog
  3. Testautomatisierung

Testautomatisierung

Spott in Python: unittest.mock, Patch und die Fehler, die Tests lügen lassen

Mit Mocks können Sie Code ohne echtes Netzwerk, Datenbank oder Uhr testen. Bei unvorsichtigem Einsatz lassen sie auch kaputten Code durch. Hier erfahren Sie, wie Sie unittest.mock bei jedem Beispiellauf gut verwenden.

8 Min. Lesezeit
Verspottung in Python: Unittest.mock, Patch und die Fehler, die Tests lügen lassen

Das Wichtigste

  • Ein Mock steht für ein reales Objekt, beispielsweise einen API-Client, sodass Tests schnell und offline ausgeführt werden können.
  • Verwenden Sie return_value für eine feste Antwort und side_effect für Fehler oder eine Reihenfolge.
  • Patzen Sie den Namen dort, wo er verwendet wird, nicht dort, wo er definiert ist. Dies ist der häufigste Fehler.
  • Verwenden Sie spec oder autospec, damit Tippfehler und falsche Argumente fehlschlagen, anstatt stillschweigend weitergegeben zu werden.

Ihre Funktion ruft eine Wetter-API auf. Ihr Test sollte nicht vom Internet, einem API-Schlüssel oder dem Wetter von morgen abhängen. Mit der in Python integrierten Funktion unittest.mock können Sie diesen Aufruf durch einen Ersatzaufruf ersetzen, der genau das zurückgibt, was Sie auswählen.

Wir haben ein kleines weather Modul und neun Tests geschrieben, die alle mit dem Unittest von Python 3.13 ausgeführt werden. Alle neun haben bestanden, und die folgenden gedruckten Ergebnisse stammen aus diesem Lauf.

Warum spotten?

  • Geschwindigkeit: keine Netzwerkanrufe oder Datenbankeinrichtung.
  • Zuverlässigkeit: Tests schlagen nicht fehl, weil ein Drittanbieterdienst ausgefallen ist.
  • Kontrolle: Sie können bei Bedarf Zeitüberschreitungen, Fehler und ungerade Daten simulieren.
  • Isolation: Sie testen Ihre Logik, nicht den Server eines anderen.

Mocks gehören hauptsächlich in Unit-Tests. Integrationstests sollten immer noch auf echte oder realistische Abhängigkeiten eingehen.

Scheingrundlagen

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()' ...>

Ein Mock zeichnet jeden Anruf auf, sodass Sie überprüfen können, wie er verwendet wurde. Beachten Sie jedoch die letzte Zeile. Ein einfacher Mock erfindet jedes von Ihnen gewünschte Attribut. Diese Flexibilität ist praktisch, aber auch gefährlich, wie wir sehen werden.

MagicMock ist die gleiche Idee, unterstützt aber auch „magische“ Methoden wie __len__ und __iter__. patch() verwendet standardmäßig MagicMock.

Nebeneffekt: Fehler und Sequenzen

side_effect steuert, was bei jedem Anruf passiert. Legen Sie eine Ausnahme fest, um einen Fehler zu simulieren:

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

Legen Sie es auf eine Liste fest, um bei aufeinanderfolgenden Aufrufen unterschiedliche Ergebnisse zurückzugeben. Dies ist perfekt zum Testen der Wiederholungslogik:

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

Der erste Aufruf schlug fehl, der zweite war erfolgreich und der Test ergab, dass die Funktion genau einmal wiederholt wurde. Dies mit einem realen Dienst zu simulieren, wäre nahezu unmöglich.

patch(): etwas vorübergehend ersetzen

Oft wird die Abhängigkeit nicht übergeben. Sie wird innerhalb des Moduls importiert. patch() tauscht es für die Dauer eines Tests aus und stellt es anschließend wieder her:

# 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")

Sie können patch als Kontextmanager wie diesen oder als Dekorator verwenden, der den Mock als Argument übergibt.

Patch dort, wo es verwendet wird, nicht dort, wo es definiert ist

Das ist der Fehler, den fast jeder einmal macht. Die eigentliche Funktion get befindet sich in http_client, daher ist es naheliegend, sie dort zu patchen:

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

Die eigentliche Funktion wurde ausgeführt. Als weather.py from http_client import get as requests_get ausführte, erstellte es einen eigenen Namen, der auf die ursprüngliche Funktion verweist. Beim Patchen von http_client.get wurde ein anderer Name geändert, sodass weather den Schein nie gesehen hat. Unsere http_client.get erhöhen absichtlich, daher war der Fehler laut. In einem echten Projekt könnte es stillschweigend eine Live-API aufrufen.

Regel: Patchen Sie den Namen im zu testenden Modul, geschrieben als "module_under_test.name". Wenn das Modul import http_client ausgeführt hat und http_client.get() aufruft, ist das Patchen von "http_client.get" korrekt, da die Suche zum Zeitpunkt des Aufrufs erfolgt.

spec und autospec: Machen Sie Mocks streng

Ein einfacher Mock akzeptiert alles, auch Tippfehler. Bei einer Spezifikation sind nur Attribute zulässig, über die die tatsächliche Klasse verfügt:

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

Der lose Mock akzeptierte die Falschbuchstabe-Methode gerne, so dass ein Test mit dieser Methode bestehen konnte, obwohl der echte Code abstürzen würde. autospec geht noch weiter und prüft Methodensignaturen:

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

Bevorzugen Sie autospec=True oder create_autospec(), wenn Sie Ihre eigenen Klassen verspotten. Es sorgt dafür, dass Mocks ehrlich bleiben, wenn sich der echte Code ändert.

Verspottung von Datum und Uhrzeit

Code, der vom heutigen Datum abhängt, ist schwer zu testen. Wickeln Sie die Uhr in eine kleine Funktion ein und patchen Sie Folgendes:

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)

Sie können datetime.date.today nicht einfach direkt patchen, da die Attribute integrierter Typen nicht festgelegt werden können. Bibliotheken wie Freezegun und Time-Machine bewältigen dies umfassender.

Behauptungen über Mocks

Methode Schecks
assert_called() Mindestens einmal angerufen
assert_called_once() Habe genau einmal angerufen
assert_called_with(...) Beim letzten Aufruf wurden diese Argumente verwendet
assert_called_once_with(...) Genau ein Anruf mit diesen Argumenten
assert_any_call(...) Bei jedem Anruf wurden diese Argumente verwendet
assert_not_called() Habe nie angerufen

Wenn eine Behauptung fehlschlägt, ist die Botschaft klar. Wir haben assert_called_once() für eine Methode aufgerufen, die nie ausgeführt wurde:

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

Ältere Python-Versionen lassen falsch geschriebene Assertionsmethoden wie assret_called_once stillschweigend passieren. Moderne Versionen verursachen einen Fehler bei Namen, die mit assert beginnen, oder bei häufigen Schreibfehlern davon, aber eine Spezifikation ist immer noch der stärkere Schutz.

Pytest und Mocks

Alles oben Genannte funktioniert auch in Pytest. Viele Pytest-Benutzer verwenden auch die integrierte monkeypatch-Fixture für einfache Ersetzungen oder das Pytest-Mock-Plugin, das eine mocker-Fixture bereitstellt, die Patches automatisch rückgängig macht. Für alle gilt die Regel „Patch dort, wo es verwendet wird“. Informationen zu Vorrichtungen und Einrichtung finden Sie in unserem Pytest-Tutorial.

Wann man sich nicht lustig machen sollte

  • Machen Sie sich nicht über das lustig, was Sie testen. Wenn das Modell die ganze Arbeit erledigt, beweist der Test nichts.
  • Vermeiden Sie es, einfache Wertobjekte zu verspotten. Verwenden Sie das Echte.
  • Verspotten Sie nicht alles. Tests voller Verspottungen brechen oft ab, wenn der Code umgestaltet wird, selbst wenn das Verhalten korrekt ist.
  • Führen Sie einige echte Integrationstests durch. Mocks kodieren Ihre Annahmen über einen Dienst, und diese Annahmen können falsch sein.

Mocks erhöhen auch leicht die Codeabdeckung, da gemockte Pfade ohne echtes Verhalten dahinter verlaufen.

Häufige Spottfehler

  • Patching des falschen Namens. Oben behandelt, und immer noch die häufigste Ursache für „mein Spott wird ignoriert“.
  • Vergessen, return_value festzulegen. Der zu testende Code empfängt dann ein Mock-Objekt anstelle von Daten. Es kann passieren, dass es Ihre Funktion direkt durchläuft und versehentlich einen Test durchläuft, beispielsweise wenn Sie nur überprüfen, ob ein Ergebnis wahr ist.
  • Behauptung anhand des Scheins statt anhand des Ergebnisses. Die Überprüfung, ob save() aufgerufen wurde, ist schwächer als die Überprüfung, was gespeichert wurde. Verwenden Sie assert_called_once_with(...) mit echten Argumenten, oder besser, bestätigen Sie das Ergebnis der Funktion.
  • Undichte Patches. Wenn Sie patcher.start() ohne stop() aufrufen, bleibt die Fälschung für spätere Tests bestehen. Verwenden Sie den Kontextmanager, den Decorator oder addCleanup(patcher.stop).
  • Gestapelte Dekoratoren in der falschen Reihenfolge. Bei mehreren @patch Dekoratoren wird der unterste als erstes Argument übergeben. Wenn man sie stillschweigend verwechselt, wird das falsche Modell konfiguriert.
  • Verspottung einer Bibliothek, die Sie nicht besitzen in vielen Tests. Packen Sie es in eine kleine eigene Klasse ein und machen Sie sich stattdessen darüber lustig. Ihre Tests beschreiben dann Ihre Anforderungen, nicht die API der Bibliothek.

Mocks, Stubs und Fakes

Diese Wörter werden oft lose verwendet, aber die Unterschiede helfen bei der Diskussion von Tests. Ein Stub gibt lediglich vorgefertigte Antworten zurück. Ein Mock zeichnet auch Anrufe auf, sodass Sie überprüfen können, wie es verwendet wurde. Ein Fake ist eine funktionierende, vereinfachte Implementierung, beispielsweise ein In-Memory-Repository anstelle einer Datenbank. Pythons Mock kann als Stub oder Mock fungieren. Fakes sind gewöhnliche Klassen, die Sie selbst schreiben, und sie führen oft zu Tests, die das Refactoring besser überstehen, weil sie das Verhalten prüfen und nicht exakte Aufrufe.

Eine gute Faustregel besteht darin, sich über die Grenzen Ihres Systems lustig zu machen: das Netzwerk, die Uhr, Zufallszahlen, das Dateisystem und kostenpflichtige Dienste von Drittanbietern. Bevorzugen Sie innerhalb dieser Kanten echte Objekte. Auf diese Weise erstellte Tests bleiben schnell und verwenden weiterhin den Code, den Sie tatsächlich geschrieben haben.

Fazit

Mocking ist ein präzises Werkzeug: Ersetzen Sie langsame oder unvorhersehbare Abhängigkeiten, steuern Sie, was sie zurückgeben, und überprüfen Sie, wie Ihr Code sie verwendet. Zwei Gewohnheiten verhindern die meisten Probleme. Patchen Sie den Namen im zu testenden Modul und verwenden Sie Autospec, damit Ihre Mock-Reject-Aufrufe vom realen Objekt abgelehnt werden.

Häufige Fragen

Was ist Spott in Python?

Beim Mocking wird ein reales Objekt, etwa ein API-Client oder eine Datenbank, durch eine Fälschung ersetzt, die Sie während eines Tests kontrollieren. Python fügt dies in das Modul unittest.mock ein.

Was ist der Unterschied zwischen Mock und MagicMock?

MagicMock ist ein Mock, der auch magische Methoden wie __len__, __iter__ und __enter__ unterstützt. patch() erstellt standardmäßig einen MagicMock.

Warum funktioniert mein Patch nicht?

Normalerweise, weil Sie dort gepatcht haben, wo das Objekt definiert ist, und nicht dort, wo es verwendet wird. Patchen Sie den Namen innerhalb des zu testenden Moduls.

Was ist der Unterschied zwischen return_value und side_effect?

return_value legt ein festes Ergebnis fest. side_effect kann eine Ausnahme auslösen, Werte aus einer Liste der Reihe nach zurückgeben oder eine Funktion aufrufen.

Was macht Autospec?

Es erstellt eine Simulation des realen Objekts, sodass fehlende Attribute und falsche Argumente Fehler verursachen, genau wie beim echten Code.

Lerne weiter

Arbeiten Sie mit Stack Quora

Benötigen Sie Hilfe beim Testen, WordPress oder SEO?

Wir helfen Teams, bessere Software zu liefern und ihre Websites zu erweitern. Sagen Sie uns, woran Sie gerade arbeiten, und wir schlagen Ihnen einen klaren nächsten Schritt vor.

Lesen Sie weiter

Weitere Anleitungen finden Sie unter Tests, KI und moderne Entwicklung

Hat Ihnen dieser Artikel gefallen? Wählen Sie ein Thema und springen Sie direkt in unsere neuesten Tutorials.

Künstliche Intelligenz

KI-Halluzinationen erklärt: Warum Sprachmodelle Dinge erfinden und wie man sie reduziert

KI-Halluzinationen erklärt: Was sie sind, warum Sprachmodelle sie produzieren, gängige Typen, wie man sie mit Erdung und Eingabeaufforderungen reduz…

Artikel lesen →
DevOps

Erklärte Umgebungsvariablen: .env-Dateien, Geheimnisse und Konfiguration richtig gemacht

Umgebungsvariablen anhand von Python-Beispielen erklärt, die wir ausgeführt haben: Sie lesen und festlegen, warum jeder Wert ein String ist, .env-Da…

Artikel lesen →
Softwareentwicklung

API-Ratenbegrenzung erklärt: Algorithmen, Header und Behandlung von 429-Fehlern

API-Ratenbegrenzung anhand einer von uns durchgeführten Simulation erklärt: Token-Bucket, festes Fenster, gleitendes Fenster, 429 Antworten, Ratenbe…

Artikel lesen →
Testautomatisierung

Spott in Python: unittest.mock, Patch und die Fehler, die Tests zum Lügen machen

Mocking in Python mit unittest.mock, getestet: Mock und return_value, side_effect, Patchen, wo ein Name verwendet wird, Spezifikation und Autospec, …

Artikel lesen →
Softwaretests

Code-Abdeckung erklärt: Leitungs- und Zweigstellenabdeckung und was 100 % wirklich bedeutet

Erklärung der Codeabdeckung anhand eines echten Coverage.py-Laufs: Zeilen-, Zweig- und Bedingungsabdeckung, Lesen eines Berichts, warum bei 100 % Ab…

Artikel lesen →
Datenbanken & SQL

DELETE vs. TRUNCATE vs. DROP in SQL: Unterschiede, Rollback und Verwendungszweck

DELETE vs. TRUNCATE vs. DROP erklärt mit getestetem SQL: was jeweils entfernt wird, Rollback, Trigger, Identitätsrücksetzung, Geschwindigkeit und wi…

Artikel lesen →
Datenbanken & SQL

Erklärte SQL-Ansichten: Erstellen, nutzen und vermeiden Sie Fallstricke mit Beispielen

SQL-Ansichten anhand der von uns ausgeführten Abfragen erklärt: Erstellen einer Ansicht, Live-Ergebnisse, Ausblenden von Spalten, Leistung, aktualis…

Artikel lesen →
Softwareentwicklung

TypeScript-Schnittstelle vs. Typ: Echte Unterschiede, getestet mit dem Compiler

TypeScript-Schnittstelle vs. Typ, überprüft mit dem echten Compiler: Was beide können, was nur einer kann, Deklarationszusammenführung, Erweiterunge…

Artikel lesen →
Softwareentwicklung

Python *args und **kwargs erklärt: Flexible Funktionen mit klaren Beispielen

Python *args und **kwargs erklärt mit Code, den wir ausgeführt haben: Packen und Entpacken, Parameterreihenfolge, Nur-Schlüsselwort- und Nur-Positio…

Artikel lesen →
Softwareentwicklung

Python-Lambda-Funktionen: Syntax, tatsächliche Verwendung und wann man sie nicht verwendet

Python-Lambda-Funktionen erklärt mit Code, den wir ausgeführt haben: Syntax, Sortierung mit Schlüssel, Karte und Filter, Abschlüsse, die Late-Bindin…

Artikel lesen →
Künstliche Intelligenz

Was sind KI-Agenten? Wie sie funktionieren, mit einer einfachen Agentenschleife, die Sie ausführen können

Was sind KI-Agenten? Eine einfache Anleitung zur Agentenschleife, zu Tools, Speicher und Leitplanken, mit einer kleinen Python-Schleife, die wir aus…

Artikel lesen →
DevOps

Überwachung vs. Beobachtbarkeit: Der wahre Unterschied, mit Protokollen, Metriken und Traces

Überwachung vs. Beobachtbarkeit erklärt: bekannte und unbekannte Fehler, Protokolle, Metriken und Spuren, eine exemplarische Vorgehensweise für Chec…

Artikel lesen →
DevOps

Docker-Volumes vs. Bind Mounts: Containerdaten auf die richtige Art und Weise beibehalten

Docker-Volumes vs. Bind-Mounts erklärt: Warum Containerdaten verschwinden, benannte Volumes, Bind-Mounts, tmpfs, Compose-Beispiele, Backups, Berecht…

Artikel lesen →
Testautomatisierung

TDD vs. BDD: Hauptunterschiede, eine Funktion, die auf beide Arten entwickelt wurde und wann sie jeweils verwendet werden sollte

TDD vs. BDD erklärt mit einer Rabattfunktion, die in beide Richtungen aufgebaut ist: Rot-Grün-Refaktor mit Code, den wir ausgeführt haben, Gegeben-W…

Artikel lesen →
Softwareentwicklung

Git merge vs rebase: Unterschiede, Beispiele und wann man was nutzt

Git merge vs rebase erklärt mit echter Log-Ausgabe, einer Vergleichstabelle, der goldenen Regel des Rebase und einem sicheren Team-Workflow.

Artikel lesen →
Softwareentwicklung

Python List Comprehension: Syntax, Beispiele und wann man darauf verzichtet

Python List Comprehension erklärt: Syntax, Filter, if-else, verschachtelte Schleifen, gemessene Zeiten und Fälle, in denen eine normale for-Schleife…

Artikel lesen →
Datenbanken & SQL

SQL Joins erklärt mit Beispielen (Inner, Left, Right, Full)

SQL Joins Schritt für Schritt mit Beispieltabellen und Abfragen verstehen: INNER, LEFT, RIGHT, FULL, CROSS und SELF, plus typische Fehler und Perfor…

Artikel lesen →