- Einführung
- unittest.,verspotten oder zu verspotten
- Decorator
- Resource location
- Modell return_value vs side_effect
- Modell Geschachtelte Aufrufe
- stellen Sie sicher Exceptions
- Clearing lru_cache
- Modell-Modul-Ebene/Globale Variablen
- Modell-Instanz-Methode
- Modell-Klasse-Methode
- Mock Gesamte Klasse
- Modell Async Calls
- Modell-Instance-Typen
- Modell builtin
open
Funktion - Vertragsschluss
Einführung
Mocking Ressourcen beim schreiben von tests in Python kann verwirrend sein, wenn Sie nicht vertraut sind mit, solche Dinge tun., In diesem Beitrag werde ich verschiedene Aspekte des Verspottungscodes behandeln, die hoffentlich eine nützliche Ressource für diejenigen sind, die ein bisschen stecken bleiben.
Hinweis: In den Codebeispielen verwende ich pytest, aber das sollte größtenteils keine Rolle spielen.
unittest.mock oder mock
Um eine Ressource zu verspotten, benötigen wir zuerst das Modul mock
, und dies ist unser erster Stolperstein: Welche Version benötigen wir? dh es gibt zwei und beide scheinen offiziell zu sein (mock
und unittest.mock
).,
Das Modul mock
ist eine abwärtskompatible Bibliothek, die Sie von PyPy herunterladen können, wobei unittest.mock
dasselbe ist, aber nur mit der von Ihnen verwendeten Python-Version kompatibel ist.,
In fast allen Fällen möchten Sie es folgendermaßen importieren:
import unittest.mock as mock
Weitere Beispiele finden Sie in diesem Referenzhandbuch
Decorator
Die häufigste Methode zum Verspotten von Ressourcen ist die Verwendung eines Python-Dekorators um Ihre Testfunktion:
@mock.patch("thing")def test_stuff(mock_thing): mock_thing.return_value = 123
In diesem Fall kann das, was wir patchen (thing
), eine Variable oder eine Funktion sein.,
Wenn Sie dies tun, müssen Sie ein Argument an Ihre Funktion übergeben (Sie können es beliebig benennen†), das eine MagicMock
.
Dies bedeutet, wenn Sie nichts anderes tun, führen Aufrufe von thing
(zumindest im obigen Beispiel) dazu, dass der Wert 123
zurückgegeben wird.
† Konvention ist, die Variable
mock_<noun>
zu benennen.,
Wenn Sie mehrere Dinge verspotten, stapeln Sie die Scheindekorateure übereinander und geben sie an die Testfunktion weiter:
@mock.patch("third")@mock.patch("second")@mock.patch("first")def test_stuff(mock_first, mock_second, mock_third): ...
Ressourcenstandort
Es ist wichtig zu wissen, dass Sie beim Verspotten den Speicherort der zu verspottenden Ressource angeben sollten, der für den Importort relevant ist., Dies lässt sich am besten anhand eines Beispiels erklären…
Stellen Sie sich vor, ich habe ein Modul app.foo
und in diesem Modul importiere ich eine andere Abhängigkeit wie folgt:
from app.bar import thing
Sie könnten denken, dass Sie beim Aufruf von mock.patch
einen Verweis auf die Ressource übergeben, z. B. app.bar.thing
. Dies wäre nur relevant, wenn die Ressource mit diesem vollständigen Pfad innerhalb des Moduls app.foo
aufgerufen würde (z. B. wenn app.foo
app.bar.thing(...)
).,
Wenn auf den vollständigen Namespace-Pfad nicht verwiesen wird, was im obigen Beispiel nicht der Fall ist (beachten Sie, dass wir nur die Ressource thing
importieren). Es bedeutet, dass wir den Referenznamespace angeben müssen, um zu verspotten, wo er importiert wird:
@mock.patch('app.foo.thing')
Obwohl thing
in app.bar
vorhanden ist, geben wir app.foo.thing
an, da app.foo
dort ist, wo wir ihn importiert haben verwenden. Das fängt die Leute die ganze Zeit ein.,
Mock return_value vs side_effect
Wenn Ihre Funktion einen try/except hat, können Sie side_effect
verwenden, um den Aufruf der Funktion zu veranlassen, eine Ausnahme als zurückgegebenen Wert auszulösen:
@mock.patch('app.aws.sdk.confirm_sign_up', side_effect=Exception('whoops'))
Hinweis: Wenn Sie
return_value=Exception('whoops')
dann würde der Mock die Zeichenfolgendarstellung der Ausnahme zurückgeben, anstatt eine Ausnahme wieside_effect
auszulösen.,methode für ein verspottetes Objekt wurde aufgerufen:Der Grund, warum dies komplizierter werden kann, liegt darin, wie ein Mock einen neuen Mock zurückgibt, wenn er auf eine Eigenschaft in einem Mock zugreift:
Der obige Code wird Fehler:
AssertionError: expected call not found.Expected: listen(8080)Actual: listen(123)
Sie müssen sicherstellen, dass Sie den Mock zur richtigen Zeit bestätigen:
Ausnahmen überprüfen
Wenn wir überprüfen möchten, ob ein Teil des Codes ein
Exception
– Typ, wenn wir ihn benötigen, können wir bestimmte Ressourcen verspotten, um eine Ausnahme auszulösen, und dannpytest.raises
als Kontextmanager um den Aufrufer unseres zu überprüfenden Codes verwenden.,Wir können dieses erwartete Verhalten abfangen und Zusicherungen machen, indem wir zuerst die Ressource verspotten, die wir eine Ausnahme auslösen möchten, und sie dazu bringen, unsere eigene gefälschte Ausnahme mit dem Parameter
side_effect
.Als nächstes geben wir den genauen Ausnahmetyp an, den wir erwarten, mit
pytest.raises(T)
:Hinweis: Machen Sie nicht den Fehler, Behauptungen in den Kontextmanager
with
zu setzen., Sobald die Ausnahme durch die Funktion ausgelöst wird, die im Kontextmanagerwith
aufgerufen wird, wird der gesamte Code danach innerhalb des Blocks übersprungen.ru_cache
Wenn auf eine Funktion, die Sie testen möchten, der Dekorator
functools.lru_cache
angewendet wurde, müssen Sie die Antwort dieser Funktion verspotten, da sie in einem Test zwischengespeichert wird und das zwischengespeicherte Ergebnis zurückgegeben wird, wenn Sie die Funktion erneut aufrufen, um ein anderes Verhalten zu testen (und Sie wahrscheinlich verwirren, wenn Sie die unerwartete Antwort sehen).,Um dieses Problem zu beheben, ist es sehr einfach, da
lru_cache
beim Dekorieren Ihrer Funktionen zusätzliche Funktionen bereitstellt:
cache_info
cache_clear
Letzteres (
cache_clear
div>) ist das, was Sie anrufen müssten. Dies wird unten gezeigt:Hinweis: Das Debuggen ist nicht immer offensichtlich., Später demonstriere ich, wie man die eingebaute
open
Funktion verspottet, und in diesem Szenario stolperte ich über dieses Problem, denn obwohl ich die oberste Funktion selbst nicht verspottete (ich verspottete den Aufruf vonopen
), wurde der Inhalt der Datei, die geöffnet wurde, zurückgegeben und zwischengespeichert.Modulebene/Globale Variablen verspotten
Mit einer Modulvariablen können Sie den Wert entweder direkt festlegen oder
mock.patch
verwenden.,Im folgenden Beispiel haben wir die Variable
client_id
, die eine globale Variable innerhalb desapp.aws
– Moduls ist, auf das wir importieren, um an anderer Stelle in unserem Code zu verweisen:Im
mock.patch
– Beispiel gibt es zwei wichtige Dinge zu beachten:
- Wir verwenden
return_value
.- Es wird keine Scheininstanz an die Testfunktion übergeben.,
Dies liegt daran, dass wir eine Variable und keine direkte Funktion oder „aufrufbar“ ändern, sodass kein Mock an die Testfunktion übergeben werden muss (wenn Sie den Wert einige Male innerhalb des Tests selbst ändern möchten, dann würden Sie die Variable verspotten, aber nicht sofort einen Wert im Dekorator zuweisen).
Mock-Instanzmethode
Es gibt mehrere Möglichkeiten, das Verspotten einer Instanzmethode zu erreichen., Ein gängiger Ansatz ist die Verwendung von
mock.patch.object
wie folgt:Ein anderer Ansatz besteht darin, die Methode wie eine normale Funktion zu verspotten, aber Sie verweisen auf die Methode über den Klassennamen:
Ein anderer (wenn auch schwerfälliger) Ansatz zum Verspotten einer Klasseninstanzmethode besteht darin, die Tatsache zu nutzen, dass ein Mock eine neue Mock-Instanz zurückgibt, wenn er aufgerufen wird:
@mock.patch("foo.bar.SomeClass")def test_stuff(mock_class): mock_class.return_value.made_up_function.return_value = "123"
Hinweis: Im obigen Beispiel verspotten wir die gesamte Klasse, die möglicherweise nicht Ihren Wünschen entspricht. Wenn nicht, verwenden Sie stattdessen das vorherige Beispiel
mock.patch.object
.,Der Grund, warum das obige Beispiel funktioniert, ist, dass wir
return_value
auf unseren mock setzen. Da dies eineMagicMock
ist, gibt jedes Attribut, auf das verwiesen wird, eine neue Mock-Instanz zurück (eine Funktion oder Eigenschaft, die Sie auf einem mock aufrufen, muss nicht vorhanden sein), und so rufen wirmade_up_function
für den zurückgegebenen Mock auf, und für diesen neu erstellten Mock setzen wir die endgültigereturn_value
auf123
.,Aber wie in der obigen Anmerkung erwähnt, ist dieser Ansatz möglicherweise etwas zu stumpf, je nachdem, was Ihre Bedürfnisse sind (ob es Ihnen wichtig ist, ob Sie eine funktionierende Klasse haben oder nicht).
Mock Class Method
Um eine Klassenmethode zu verspotten, ist ein ähnlicher Ansatz zum Verspotten einer Instanzmethode.,
Ein Ansatz könnte sein, dass Sie die gesamte Klasse verspotten (aber jetzt müssen Sie eine weniger
return_value
zuweisen):mock_class.ClassMethodName.return_value = "123"
Oder noch besser, Sie sollten es verspotten, wie Sie es tun würden jede normale Funktion, aber verweisen Sie einfach auf die Methode über die Klasse:
Verspotten Sie die gesamte Klasse
Um eine ganze Klasse zu verspotten, müssen Sie div id=“6f2aa2b896″> um eine neue Instanz der Klasse zu sein.,
Weitere klassenbezogene Spotttipps finden Sie hier
Verspotten Sie asynchrone Aufrufe
Das Verspotten von asynchronem Code ist wahrscheinlich der verwirrendste Aspekt des Verspottens. Meine Lösung „Gehe zu“ werde ich zuerst erklären, aber danach werde ich einige alternative Methoden teilen, die ich in der Vergangenheit gesehen und ausprobiert habe.,
Betrachten Sie zuerst diesen asynchronen Code innerhalb eines
app.foo
Moduls:import app.stuffasync def do_thing(x): return await app.stuff.some_concurrent_function(x)
Wenn wir die Coroutine verspotten müssen
app.stuff.some_concurrent_function
, dann können wir dies lösen, indem wir eine Funktion erstellen, die als Coroutine fungiert und es für verschiedene Arten von Antworten konfigurierbar macht:Hinweis: Im Beispiel wird tornado zum Ausführen eines asynchronen Tests verwendet.,e>…
AsyncMock
Hinweis: Dies verwendet das Paket
pytest-asyncio
um beim Testen des asyncio-Codes zu helfenBeginnen wir mit dem zu verspottenden Code…
import asyncioasync def sum(x, y): await asyncio.sleep(1) return x + y
So würden wir es verspotten…
Monkey Patch
MagicMock Subclass
Async Inline Function
Mock Instance Types
Wenn Sie ein Objekt verspotten, werden Sie feststellen, dass der Mock das gesamte Objekt ersetzt und Tests auf unerwartete Weise bestehen (oder fehlschlagen) kann.,
Das heißt, wenn Sie ein Mock mehr wie die konkrete Schnittstelle machen müssen, dann gibt es zwei Möglichkeiten, das zu tun:
spec
wrap
Wir können Mocks
spec
feature verwenden, um alle Methoden/Attribute von das Objekt wird verspottet. Dies stellt sicher, dass Ihre Mocks die gleiche API wie die Objekte haben, die sie ersetzen.Hinweis: Es gibt eine strengere
spec_set
, die eineAttributeError
.,Dies wird am besten anhand eines Beispiels demonstriert:
Mit dem Parameter
wrap
können Sie andererseits die Implementierung „ausspionieren“ und ihr Verhalten beeinflussen.,eines der oben genannten, das das gesamte Objekt verspottet, nicht nur eine einzige Methode:Mock builtin open function
Pythons Mock-Bibliothek bietet eine Abstraktion zum Verspotten der eingebauten
open
– Funktion viel einfacher…Die – Parameter, die auf
mock.patch
div id=“c3af3af75c“> returned erstellt automatisch alle Attribute, die auf dem mock aufgerufen werden (dies liegt daran, dass dieopen
– Funktion versucht, auf viele verschiedene Dinge zuzugreifen, und es ist einfacher für mock, all das für Sie zu verspotten).,Fazit
Dort enden wir. Hoffentlich kann diese Liste von Spotttechniken Sie auch durch den komplexesten Code führen, der getestet werden muss. Lassen Sie mich wissen, was Sie auf Twitter denken.