Mock-Patch Eine Funktion, die von einer anderen Funktion in Python aufgerufen wird

Mehvish Ashiq 21 Juni 2023
  1. Scheinobjekte und ihre Bedeutung
  2. Python Patch() und seine Verwendung
Mock-Patch Eine Funktion, die von einer anderen Funktion in Python aufgerufen wird

Komponententests sind unerlässlich, wenn wir robusten Code schreiben. Sie helfen uns, unsere Anwendungslogik zu überprüfen und alle anderen Aspekte der Code-Anforderungen zu untersuchen; Hürden wie Komplexität und externe Abhängigkeiten erschweren jedoch das Schreiben qualitativ hochwertiger Testfälle.

An dieser Stelle hilft uns unittest.mock, eine Python-Mock-Bibliothek, diese Hürden zu überwinden. Heute lernen wir Mock-Objekte, ihre Bedeutung und verschiedene Beispielcodes mit Python patch() kennen, die das Ziel vorübergehend durch die Mock-Objekte ersetzen.

Scheinobjekte und ihre Bedeutung

Scheinobjekte ahmen das Verhalten realer Objekte auf kontrollierte Weise nach, beispielsweise in der Testumgebung, in der wir verschiedene Tests durchführen, um sicherzustellen, dass der Code wie erwartet funktioniert. Der Prozess der Verwendung von Mock-Objekten wird Mocking genannt; Es ist ein leistungsstarkes Tool zur Verbesserung der Qualität unserer Tests.

Nun, der Punkt ist, warum müssen wir durch Spott gehen? Was sind die wesentlichen Gründe für die Verwendung?

Verschiedene Ursachen zeigen, wie wichtig es ist, Mocking in Python zu verwenden. Werfen wir einen Blick auf sie unten:

  1. Ein Hauptgrund besteht darin, das Verhalten unseres Codes durch die Verwendung von Scheinobjekten zu verbessern. Beispielsweise können wir sie verwenden, um HTTP-Anforderungen zu testen (eine Anforderung, die von einem Client an den benannten Host gestellt wird, der sich auf einem Server befindet) und sicherzustellen, ob sie einen zeitweiligen Fehler verursachen oder nicht.
  2. Wir können die tatsächlichen HTTP-Anforderungen durch die Mock-Objekte ersetzen, wodurch wir einen Ausfall eines externen Dienstes und vollständig erfolgreiche Antworten auf vorhersehbare Weise vortäuschen können.
  3. Mocking ist auch nützlich, wenn es schwierig ist, if-Anweisungen und außer-Blöcke zu testen. Mithilfe von Scheinobjekten können wir den Ausführungsfluss unseres Codes steuern, um solche Bereiche (wenn und außer) zu erreichen und die Codeabdeckung zu verbessern.
  4. Ein weiterer Grund, der die Bedeutung der Verwendung von Python-Mock-Objekten erhöht, ist das richtige Verständnis dafür, wie wir ihre Gegenstücke in unserem Code verwenden können.

Python Patch() und seine Verwendung

Die unittest.mock, eine Mock-Objektbibliothek in Python, hat einen patch(), der das Ziel vorübergehend durch das Mock-Objekt ersetzt. Hier kann das Ziel eine Klasse, eine Methode oder eine Funktion sein.

Um den Patch zu verwenden, müssen wir verstehen, wie man ein Ziel identifiziert und die Funktion patch() aufruft.

Um das Ziel zu erkennen, stellen Sie sicher, dass das Ziel importierbar ist, und patchen Sie dann das Ziel dort, wo es verwendet wird, und nicht dort, wo es herkommt. Wir können patch() auf drei Arten aufrufen; als Dekorateur für eine Klasse/Funktion, als Kontextmanager oder als manueller Start/Stopp.

Das Ziel wird durch das neue Objekt ersetzt, wenn wir patch() als Decorator einer Klasse/Funktion verwenden oder patch() im Kontextmanager innerhalb einer with-Anweisung verwenden. In beiden Szenarien wird der Patch rückgängig gemacht, wenn die with-Anweisung oder die Funktion vorhanden ist.

Lassen Sie uns einen Startcode erstellen und in der Datei addition.py speichern, die wir importieren werden, um patch() als Decorator, Kontextmanager und manuellen Start/Stopp zu verwenden.

Startup-Beispielcode (gespeichert in der Datei addition.py):

def read_file(filename):
    with open(filename) as file:
        lines = file.readlines()
        return [float(line.strip()) for line in lines]


def calculate_sum(filename):
    numbers = read_file(filename)
    return sum(numbers)


filename = "./test.txt"
calculate_sum(filename)

Inhalt test.txt:

1
2
3

read_file() nimmt den filename, um Zeilen zu lesen und konvertiert jede Zeile in Float-Typ. Es gibt eine Liste dieser konvertierten Zahlen zurück, die wir in der Variablen numbers innerhalb der Funktion calculate_sum() speichern.

Nun summiert calculate_sum() alle Zahlen in der numbers-Liste und gibt sie wie folgt als Ausgabe zurück:

AUSGANG:

6.0

Verwenden Sie patch() als Decorator

Lassen Sie uns Schritt für Schritt eintauchen, um die Verwendung von patch() als Dekorateur zu lernen. Der vollständige Quellcode wird am Ende aller Schritte angegeben.

  • Bibliotheken und Module importieren.
    import unittest
    from unittest.mock import patch
    import addition
    

    Zuerst importieren wir die Bibliothek unittest, dann patch aus dem Modul unittest.mock. Danach importieren wir addition,, unseren Startup-Code.

  • Dekorieren Sie die Methode test_calculate_sum().
    @patch('addition.read_file')
    def test_calculate_sum(self, mock_read_file):
        # ....
    

    Als nächstes schmücken wir die Testmethode test_calculate_sum() mit dem Decorator @patch. Hier ist das Ziel die Funktion read_file() des Moduls addition.

    Das test_calculate_sum() hat einen zusätzlichen Parameter mock_read_file aufgrund der Verwendung des @patch-Decorators. Dieser zusätzliche Parameter ist eine Instanz von MagicMock (wir können mock_read_file beliebig umbenennen).

    Innerhalb von test_calculate_sum() ersetzt patch() die Funktion addition.read_file() durch das Objekt mock_read_file.

  • Weisen Sie mock_read_file.return_value eine Liste zu.
    mock_read_file.return_value = [1, 2, 3]
    
  • Rufen Sie calculate_sum() auf und testen Sie es.
    result = addition.calculate_sum("")
    self.assertEqual(result, 6.0)
    

    Jetzt können wir calculate_sum() aufrufen und mit assertEqual() testen, ob die Summe 6.0 ist oder nicht.

    Hier können wir einen beliebigen Dateinamen an die Funktion calculate_sum() übergeben, da das Objekt mock_read_file anstelle der Funktion addition.read_file() aufgerufen wird.

  • Hier ist der komplette Quellcode.

    Beispielcode (gespeichert in der Datei test_sum_patch_decorator.py)

    import unittest
    from unittest.mock import patch
    import addition
    
    
    class TestSum(unittest.TestCase):
        @patch("addition.read_file")
        def test_calculate_sum(self, mock_read_file):
            mock_read_file.return_value = [1, 2, 3]
            result = addition.calculate_sum("")
            self.assertEqual(result, 6.0)
    

    Führen Sie einen Test durch:

    python -m unittest test_sum_patch_decorator -v
    

    Führen Sie den Test mit dem obigen Befehl aus, um die folgende Ausgabe zu erhalten.

    AUSGANG:

    test_calculate_sum (test_sum_patch_decorator.TestSum) ... ok
    
    ----------------------------------------------------------------------
    Ran 1 test in 0.001s
    
    OK
    

Verwenden Sie patch() als Kontextmanager

Beispielcode (gespeichert in der Datei test_sum_patch_context_manager.py):

import unittest
from unittest.mock import patch
import addition


class TestSum(unittest.TestCase):
    def test_calculate_sum(self):
        with patch("addition.read_file") as mock_read_file:
            mock_read_file.return_value = [1, 2, 3]
            result = addition.calculate_sum("")
            self.assertEqual(result, 6)

Dieser Code ähnelt dem letzten Codebeispiel, in dem wir patch() als Decorator verwendet haben, mit Ausnahme einiger hier besprochener Unterschiede.

Jetzt haben wir keine Codezeile @patch('addition.read_file'), während test_calculate_sum() nur den self-Parameter (der ein Standardparameter ist) akzeptiert.

Das with patch('addition.read_file') as mock_read_file bedeutet patch addition.read_file() unter Verwendung des mock_read_file-Objekts im Kontextmanager.

In einfachen Worten können wir sagen, dass der patch() das Objekt addition.read_file() durch das Objekt mock_read_file innerhalb des with-Blocks ersetzen wird.

Führen Sie nun den Test mit dem folgenden Befehl aus.

python -m unittest test_sum_patch_context_manager -v

AUSGANG:

test_calculate_sum (test_sum_patch_context_manager.TestSum) ... ok

----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

Verwenden Sie patch() zum manuellen Starten/Stoppen

Beispielcode (gespeichert in der Datei test_sum_patch_manually.py):

import unittest
from unittest.mock import patch
import addition


class TestSum(unittest.TestCase):
    def test_calculate_sum(self):
        patcher = patch("addition.read_file")
        mock_read_file = patcher.start()
        mock_read_file.return_value = [1, 2, 3]
        result = addition.calculate_sum("")
        self.assertEqual(result, 6.0)
        patcher.stop()

Dieser Code-Fence macht dasselbe wie die beiden vorherigen Codebeispiele, aber hier verwenden wir patch() manuell. Wie? Lassen Sie es uns unten verstehen.

Zuerst importieren wir die benötigten Bibliotheken. Innerhalb von test_calculate_sum() rufen wir patch() auf, um einen Patch mit der Zielfunktion read_file() des Moduls addition zu starten.

Dann erstellen wir ein Mock-Objekt für die Funktion read_file(). Weisen Sie danach die Liste der Zahlen mock_read_file.return_value zu, rufen Sie calculate_sum() auf und testen Sie die Ergebnisse mit assertEqual().

Schließlich beenden wir das Patchen, indem wir die Methode stop() des patcher-Objekts aufrufen. Führen Sie nun zum Testen den folgenden Befehl aus.

python -m unittest test_sum_patch_manually -v

AUSGANG:

test_calculate_sum (test_sum_patch_manually.TestSum) ... ok

----------------------------------------------------------------------
Ran 1 test in 0.001s

OK
Mehvish Ashiq avatar Mehvish Ashiq avatar

Mehvish Ashiq is a former Java Programmer and a Data Science enthusiast who leverages her expertise to help others to learn and grow by creating interesting, useful, and reader-friendly content in Computer Programming, Data Science, and Technology.

LinkedIn GitHub Facebook