Kurs
pytest-mock Tutorial: Ein Einsteigerhandbuch für Mocking in Python
Je komplexer und umfangreicher deine Python-Projekte werden, desto wichtiger werden robuste Tests. Testen hilft sicherzustellen, dass dein Code wie erwartet funktioniert und die Qualität in allen Teilen der Anwendung erhalten bleibt.
Wenn deine Anwendung mit verschiedenen Arten von Datenbanken oder externen APIs wie OpenAI GPT oder Anthropic Claude interagiert, kann es schwierig werden, diese Abhängigkeiten zu testen. Hier kommt der Spott ins Spiel.
Mocking ermöglicht es dir, Objekte, Funktionen und Verhaltensweisen zu simulieren, wodurch deine Tests isolierter und vorhersehbarer werden. Außerdem kannst du dich auf die spezifische Funktion konzentrieren, die du testen willst, indem du sie von Abhängigkeiten isolierst, die zu Komplexität oder unerwartetem Verhalten führen könnten.
In diesem Blog erfährst du, wie du mit pytest-mock
, einem leistungsstarken Plugin für pytest, Mocks in deinen Python-Tests effizient einsetzen kannst. AmEnde dieses Tutorials wirst du in der Lage sein, Mocking-Techniken in deine Testfälle einzubauen und sie dadurch leistungsfähiger zu machen.
Was ist pytest-mock?
pytest-mock ist ein Plugin für das beliebte Python-Testframework pytest, dasy den Zugang zu Mocking-Funktionen erleichtert. Es baut auf den in Python eingebauten unittest
auf und vereinfacht das Mocking beim Testen.
pytest framework logo
pytest-mock
verbessert die Lesbarkeit und macht es einfach, Mocks in Tests mit einem pytestenativen Ansatz zu implementieren. Egal, ob du einzelne Funktionen, Klassenmethoden oder ganze Objekte mocken musst, pytest-mock
bietet die Flexibilität, die du für effektive Tests brauchst, ohne zusätzliche Komplexität.
Ein wichtiger Grund für die Lesbarkeit von pytest-mock
ist der deklarative Teststil, der es dir ermöglicht, erwartete Aufrufe und Verhaltensweisen direkt in deinen Tests zu spezifizieren. Das macht den Code einfacher zu schreiben, zu lesen und zu warten, während er gleichzeitig widerstandsfähig gegenüber Implementierungsänderungen bleibt, die das externe Verhalten des Codes nicht verändern.
Werde ein Python-Entwickler
pytest-mock einrichten
Die Erstellung einer virtuellen Umgebung für dein Projekt ist eine bewährte Methode, um sicherzustellen, dass Abhängigkeiten isoliert sind. Wenn du noch keine neue Umgebung eingerichtet hast, kannst du mit den folgenden Befehlen eine conda-Umgebung erstellen:
# create a conda environment
conda create --name yourenvname python=3.11
# activate conda environment
conda activate yourenvname
Installation von pytest und pytest-mock
Installiere pytest
und pytest-mock
direkt in deiner Umgebung mit pip oder conda:
pip install pytest
pip install pytest-mock
Bestätigen der Installation
Nachdem du pytest
und pytest-mock
installiert hast, überprüfe, ob die Installation erfolgreich war, indem du den folgenden Befehl ausführst:
pytest --version
Grundlagen des Mocking mit pytest-mock
Mocking kann man sich als das Erstellen einer "Dummy"-Version einer Komponente vorstellen, die das reale Verhalten nachahmt.
Sie ist besonders hilfreich, wenn du testen musst, wie Funktionen oder Klassen mit externen Komponenten wie Datenbanken oder externen APIs interagieren.
Einführung in Mocking
Ein Mock-Objekt simuliert das Verhalten eines echten Objekts. Sie wird oft in Unit-Tests verwendet, um Komponenten zu isolieren und ihr Verhalten zu testen, ohne abhängigen Code auszuführen.
Du könntest zum Beispiel ein Mock-Objekt verwenden, um einen Datenbankaufruf zu simulieren, ohne tatsächlich eine Verbindung zur Datenbank herzustellen.
Mocking ist besonders nützlich, wenn das echte Objekt:
- Ist schwierig zu erstellen oder zu konfigurieren.
- Die Nutzung ist zeitaufwändig (z. B. der Zugriff auf eine entfernte Datenbank).
- Hat Nebenwirkungen, die während der Prüfung vermieden werden sollten (z. B. das Versenden von E-Mails, das Entstehen von Kosten).
Verwendung der Mocker-Vorrichtung
pytest-mock bietet eine mocker
fixture, die das Erstellen und Kontrollieren von Mock-Objekten erleichtert. Die mocker
fixture ist powerful und lässt sich in deine pytest Tests integrieren.
💡Was sind Einbauten?In Python sind Fixtures wiederverwendbare Komponenten, die für Tests benötigte Ressourcen wie Datenbanken oder Dateien einrichten und abbauen. Sie sorgen für Konsistenz, reduzieren Doppelarbeit und vereinfachen die Prüfungsvorbereitung. |
Sehen wir uns ein Beispiel an:
def fetch_weather_data(api_client):
response = api_client.get("https://api.weather.com/data")
if response.status_code == 200:
return response.json()
else:
return None
Die Funktion fetch_weather_data
ist auf eine externe Wetter-API angewiesen, um Daten abzurufen, aber der Aufruf der API während der Tests könnte unnötige Kosten verursachen.
Um dies zu vermeiden, kannst du Mocking verwenden, um das Verhalten der API in deinen Tests zu simulieren und sicherzustellen, dass die Anwendung getestet wird, ohne externe Aufrufe zu tätigen. Hier siehst du, wie Mocking helfen kann:
def test_fetch_weather_data(mocker):
# Create a mock API client
mock_api_client = mocker.Mock()
# Mock the response of the API client
mock_response = mocker.Mock()
mock_response.status_code = 200
mock_response.json.return_value = {"temperature": "20C", "condition": "Sunny"}
# Set the mock API client to return the mocked response
mock_api_client.get.return_value = mock_response
# Call the function with the mock API client
result = fetch_weather_data(mock_api_client)
# Assert that the correct data is returned
assert result == {"temperature": "20C", "condition": "Sunny"}
mock_api_client.get.assert_called_once_with("https://api.weather.com/data")
In test_fetch_weather_data()
verwenden wir das mocker
Fixture, um einen Mock-API-Client mock_api_client
und einen mock_response
zu erstellen. Die Mock-Antwort ist so konfiguriert, dass sie einen echten API-Aufruf imitiert, indem sie einen Statuscode von 200 und JSON-Daten mit Wetterinformationen zurückgibt.
Die Get-Methode des Mock-API-Clients wird so eingestellt, dass sie diese Mock-Antwort zurückgibt. Wenn fetch_weather_data()
getestet wird, interagiert es mit dem Mock-API-Client, anstatt einen tatsächlichen API-Aufruf zu tätigen.
Dieser Ansatz stellt sicher, dass sich die Funktion wie erwartet verhält, während die Tests schnell, kosteneffizient und unabhängig von externen Systemen sind.
Die assert-Anweisung am Ende prüft, ob die zurückgegebenen Daten mit der erwarteten Ausgabe übereinstimmen.
Die letzte Zeile, mock_api_client.get.assert_called_once_with("https://api.weather.com/data")
, überprüft insbesondere, ob die get-Methode des Mock-API-Clients genau einmal und mit dem richtigen Argument aufgerufen wurde. Dadurch wird bestätigt, dass die Funktion auf die erwartete Weise mit der API interagiert, und der Test wird um eine zusätzliche Validierungsebene erweitert.
Häufige Anwendungsfälle für pytest-mock
Mocking ist in verschiedenen Szenarien nützlich, zum Beispiel bei Unit-Tests, bei denen du bestimmte Funktionen oder Komponenten isolieren musst. Schauen wir uns einige häufige Anwendungsfälle an:
1. Mocking von Funktionen oder Klassenmethoden
Es kann sein, dass du eine Funktion testen musst, die von anderen Funktionen oder Methoden abhängt, deren Verhalten du aber während des Tests kontrollieren möchtest. Mocking ermöglicht es dir, diese Abhängigkeiten durch vordefiniertes Verhalten zu ersetzen:
# production code
def calculate_discount(price, discount_provider):
discount = discount_provider.get_discount()
return price - (price * discount / 100)
# test code
def test_calculate_discount(mocker):
# Mock the get_discount method
mock_discount_provider = mocker.Mock()
mock_discount_provider.get_discount.return_value = 10 # Mocked discount value
# Call the function with the mocked dependency
result = calculate_discount(100, mock_discount_provider)
# Assert the calculated discount is correct
assert result == 90
mock_discount_provider.get_discount.assert_called_once()
In diesem Test wird die Methode get_discount()
der discount_provider
so manipuliert, dass sie einen vordefinierten Wert (10%) zurückgibt. Dadurch wird die Funktion calculate_discount()
von der eigentlichen Implementierung von discount_provider
isoliert.
2. Mocking von zeitabhängigem Code
Beim Testen von Funktionen, die mit Zeit oder Verzögerungen zu tun haben, hilft das Mocken von zeitbezogenen Methoden dabei, das Warten oder Manipulieren von Echtzeit zu vermeiden:
# production code
import time
def long_running_task():
time.sleep(5) # Simulate a long delay
return "Task Complete"
# test code
def test_long_running_task(mocker):
# Mock the sleep function in the time module
mocker.patch("time.sleep", return_value=None)
# Call the function
result = long_running_task()
# Assert the result is correct
assert result == "Task Complete"
Hier wird time.sleep
gepatcht, um die tatsächliche Verzögerung beim Testen zu umgehen. Dadurch wird sichergestellt, dass der Test schnell abläuft, ohne 5 Sekunden zu warten.
3. Objektattribute spötteln
Manchmal möchtest du vielleicht das Verhalten der Attribute oder Eigenschaften eines Objekts simulieren, um verschiedene Zustände während des Testens zu simulieren:
# production code
class User:
def __init__(self, name, age):
self.name = name
self.age = age
@property
def is_adult(self):
return self.age >= 18
# test code
def test_user_is_adult(mocker):
# Create a User object
user = User(name="Alice", age=17)
# Mock the is_adult property
mocker.patch.object(User, "is_adult", new_callable=mocker.PropertyMock, return_value=True)
# Assert the mocked property value
assert user.is_adult is True
In diesem Beispiel wird die Eigenschaft is_adult()
der Klasse User
so gespottet, dass sie Truezurückgibt , unabhängig vom tatsächlichen Alter. Dies ist nützlich für Testszenarien, die von Objektzuständen abhängen.
Fortgeschrittene Mocking-Techniken mit pytest-mock
Sobald du mit den grundlegenden Mocking-Funktionen vertraut bist, bietet pytest-mock
erweiterte Möglichkeiten, um komplexere Szenarien zu bewältigen.
1. Scheinnebenwirkungen
Mocks können mehr als nur Rückgabewerte simulieren - sie können auch Verhalten wie das Auslösen von Ausnahmen oder dynamische Änderungen von Rückgabewerten nachbilden. Dies wird erreicht, indem "Seiteneffekte" zu Mocks hinzugefügt werden:
import pytest
# production code
def process_payment(payment_gateway, amount):
response = payment_gateway.charge(amount)
if response == "Success":
return "Payment processed successfully"
else:
raise ValueError("Payment failed")
# test code
def test_process_payment_with_side_effects(mocker):
# Mock the charge method of the payment gateway
mock_payment_gateway = mocker.Mock()
# Add side effects: Success on first call, raise exception on second call
mock_payment_gateway.charge.side_effect = ["Success", ValueError("Insufficient funds")]
# Test successful payment
assert process_payment(mock_payment_gateway, 100) == "Payment processed successfully"
# Test payment failure
with pytest.raises(ValueError, match="Insufficient funds"):
process_payment(mock_payment_gateway, 200)
# Verify the mock's behavior
assert mock_payment_gateway.charge.call_count == 2
Die Eigenschaft side_effect
ermöglicht es dem Mock, bei späteren Aufrufen andere Werte zurückzugeben oder Ausnahmen auszulösen. In diesem Beispiel liefert der erste Aufruf von charge "Success", während der zweite Aufruf eine ValueError
auslöst. Dies ist nützlich, um verschiedene Szenarien in einem einzigen Test zu testen.
2. Ausspionieren von Funktionen
Mit dem Lernpfad kannst du verfolgen, wie eine echte Funktion aufgerufen wurde, einschließlich der Anzahl der Aufrufe und der übergebenen Argumente. Das ist besonders nützlich, wenn du sicherstellen willst, dass eine Funktion wie erwartet aufgerufen wird, aber trotzdem ihre ursprüngliche Logik ausführt:
# production code
def log_message(logger, message):
logger.info(message)
return f"Logged: {message}"
# test code
def test_log_message_with_spy(mocker):
# Spy on the info method of the logger
mock_logger = mocker.Mock()
spy_info = mocker.spy(mock_logger, "info")
# Call the function
result = log_message(mock_logger, "Test message")
# Assert the returned value
assert result == "Logged: Test message"
# Verify the spy behavior
spy_info.assert_called_once_with("Test message")
assert spy_info.call_count == 1
Die Methode spy()
merkt sich, wie eine echte Methode aufgerufen wird, ohne ihr Verhalten zu ändern. Hier spionieren wir die Info-Methode des Loggers aus, um sicherzustellen, dass sie mit der richtigen Nachricht aufgerufen wird, während wir die Methode trotzdem normal ausführen lassen.
Best Practices für die Verwendung von pytest-mock
Mocking ist mächtig, aber wenn du es unvorsichtig einsetzt, kann das zu Problemen in deinen Tests führen. Hier sind einige Best Practices, die ich dir empfehle:
1. Vermeide übermäßiges Spotten
Die Verwendung zu vieler Mocks kann zu spröden Tests führen, die abbrechen, wenn sich der interne Code ändert, auch wenn sich das externe Verhalten nicht ändert. Versuche, nur das Nötigste nachzumachen und verlasse dich auf reale Implementierungen, wann immer es möglich ist.
Übermäßiges Mocking kann dazu führen, dass deine Tests eng an die Implementierungsdetails deines Codes gekoppelt sind, was bedeutet, dass selbst kleine Überarbeitungen dazu führen können, dass Tests unnötig fehlschlagen. Konzentriere dich stattdessen darauf, nur die Teile des Systems zu mocken, die extern sind oder erhebliche Nebeneffekte haben.
2. Klare Benennungskonventionen verwenden
Wenn du Objekte mockst, verwende beschreibende Namen für deine Mocks. Dadurch werden deine Tests besser lesbar und leichter zu pflegen. Verwende zum Beispiel statt allgemeiner Namen wie mock_function()
etwas Beschreibenderes wie mock_api_response()
.
Klare Namenskonventionen helfen anderen dabei, den Zweck jedes Mocks zu verstehen, was die Verwirrung verringert und die Pflege der Testsuite im Laufe der Zeit erleichtert.
3. Halte die Tests einfach und konzentriert
Jeder Test sollte sich auf ein einziges Verhalten oder Szenario konzentrieren. Das vereinfacht die Fehlersuche und sorgt dafür, dass deine Tests leicht zu verstehen sind. Ein gut fokussierter Test gibt mit größerer Wahrscheinlichkeit ein klares Feedback, wenn etwas schief läuft, was die Identifizierung und Behebung von Problemen erleichtert.
Fazit
In diesem Blog haben wir die Grundlagen von Mocking mit pytest-mock
kennengelernt und erfahren, wie wir damit unsere Python-Tests verbessern können. Wir haben alles von grundlegendem Mocking von Funktionen und Methoden bis hin zu fortgeschritteneren Techniken wie dem Hinzufügen von Seiteneffekten und dem Ausspionieren von Funktionen behandelt.
Mocking ist ein unverzichtbares Werkzeug, um zuverlässige und wartbare Tests zu erstellen, besonders wenn du mit komplexen Systemen oder externen Abhängigkeiten arbeitest. Wenn du pytest-mock
in deine Projekte integrierst, kannst du isoliertere, vorhersehbare und leichter zu wartende Tests schreiben.
Wenn du deine Testfähigkeiten weiter ausbauen möchtest, empfehle ich dirden kostenlosen Kurs Introduction to Testing in Python auf Datacamp.
Fähigkeiten im Bereich Machine Learning aufbauen
FAQs
Wie unterscheidet sich das Mocking vom Testen echter Komponenten?
Mocking ersetzt reale Komponenten durch simulierte und ermöglicht so isolierte und kostengünstige Tests, ohne auf externe Systeme oder reale Daten angewiesen zu sein.
Kann ich pytest-mock mit anderen Test-Frameworks verwenden?
pytest-mock
wurde speziell für pytest
entwickelt und fügt sich nahtlos ein, baut aber auf Pythons unittest.mock
auf, das auch unabhängig davon verwendet werden kann.
Sind Fixtures für das Mocking in pytest-mock obligatorisch?
Während Fixtures wie Mocker die Effizienz von Mocking erhöhen, kannst du unittest.mock
auch ohne Fixtures direkt in pytest
verwenden, wenn auch mit weniger Integration.
Lerne mehr über Python mit diesen Kursen!
Kurs
Einführung in die Datenwissenschaft in Python
Kurs
Einführung in Funktionen in Python
Der Blog
Die 32 besten AWS-Interview-Fragen und Antworten für 2024
Der Blog
Die 20 besten Snowflake-Interview-Fragen für alle Niveaus
Nisha Arya Ahmed
20 Min.
Der Blog
Top 30 Generative KI Interview Fragen und Antworten für 2024
Hesam Sheikh Hassani
15 Min.