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:
- 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.
- 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.
- Mocking ist auch nützlich, wenn es schwierig ist,
if
-Anweisungen undaußer
-Blöcke zu testen. Mithilfe von Scheinobjekten können wir den Ausführungsfluss unseres Codes steuern, um solche Bereiche (wenn
undaußer
) zu erreichen und die Codeabdeckung zu verbessern. - 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
, dannpatch
aus dem Modulunittest.mock
. Danach importieren wiraddition,
, 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 Funktionread_file()
des Modulsaddition
.Das
test_calculate_sum()
hat einen zusätzlichen Parametermock_read_file
aufgrund der Verwendung des@patch
-Decorators. Dieser zusätzliche Parameter ist eine Instanz vonMagicMock
(wir könnenmock_read_file
beliebig umbenennen).Innerhalb von
test_calculate_sum()
ersetztpatch()
die Funktionaddition.read_file()
durch das Objektmock_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 mitassertEqual()
testen, ob die Summe6.0
ist oder nicht.Hier können wir einen beliebigen
Dateinamen
an die Funktioncalculate_sum()
übergeben, da das Objektmock_read_file
anstelle der Funktionaddition.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