Direkt zum Inhalt

pytest-mock Tutorial: Ein Einsteigerhandbuch für Mocking in Python

Lerne, wie du pytest-mock verwendest, von der Einrichtung und den Grundlagen bis hin zu fortgeschrittenen Techniken für praktische Tests.
Aktualisierte 19. Dez. 2024  · 15 Min. Lesezeit

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 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

Erwerbe die Programmierkenntnisse, die alle Python-Entwickler/innen brauchen.
Kostenloses Lernen Beginnen

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

Bringe deine Fähigkeiten im maschinellen Lernen auf Produktionsniveau.

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.


Moez Ali's photo
Author
Moez Ali
LinkedIn
Twitter

Datenwissenschaftler, Gründer und Schöpfer von PyCaret

Themen

Lerne mehr über Python mit diesen Kursen!

Zertifizierung verfügbar

Kurs

Einführung in Python für Entwickler

3 hr
35.9K
Beherrsche die Grundlagen der Programmierung in Python. Keine Vorkenntnisse erforderlich!
Siehe DetailsRight Arrow
Kurs Starten
Mehr anzeigenRight Arrow
Verwandt

Der Blog

Die 32 besten AWS-Interview-Fragen und Antworten für 2024

Ein kompletter Leitfaden zur Erkundung der grundlegenden, mittleren und fortgeschrittenen AWS-Interview-Fragen, zusammen mit Fragen, die auf realen Situationen basieren. Es deckt alle Bereiche ab und sorgt so für eine abgerundete Vorbereitungsstrategie.
Zoumana Keita 's photo

Zoumana Keita

30 Min.

Der Blog

Die 20 besten Snowflake-Interview-Fragen für alle Niveaus

Bist du gerade auf der Suche nach einem Job, der Snowflake nutzt? Bereite dich mit diesen 20 besten Snowflake-Interview-Fragen vor, damit du den Job bekommst!
Nisha Arya Ahmed's photo

Nisha Arya Ahmed

20 Min.

Der Blog

Top 30 Generative KI Interview Fragen und Antworten für 2024

Dieser Blog bietet eine umfassende Sammlung von Fragen und Antworten zu generativen KI-Interviews, die von grundlegenden Konzepten bis hin zu fortgeschrittenen Themen reichen.
Hesam Sheikh Hassani's photo

Hesam Sheikh Hassani

15 Min.

See MoreSee More