Dekorateure werden zur Zeit der Funktionsdefinition angewendet. Bei den meisten Funktionen wird das Modul geladen. (Bei Funktionen, die in anderen Funktionen definiert sind, wird der Dekorator bei jedem Aufruf der umschließenden Funktion angewendet.)
Wenn Sie also einen Dekorateur mit Affen flicken möchten, müssen Sie Folgendes tun:
- Importieren Sie das Modul, das es enthält
- Definieren Sie die Mock Decorator-Funktion
- Stellen Sie z
module.decorator = mymockdecorator
- Importieren Sie die Module, die den Dekorator verwenden, oder verwenden Sie sie in Ihrem eigenen Modul
Wenn das Modul, das den Dekorator enthält, auch Funktionen enthält, die ihn verwenden, sind diese bereits dekoriert, wenn Sie sie sehen können, und Sie sind wahrscheinlich SOL
Bearbeiten, um Änderungen an Python widerzuspiegeln, seit ich dies ursprünglich geschrieben habe: Wenn der Dekorator verwendet functools.wraps()
und die Version von Python neu genug ist, können Sie möglicherweise die ursprüngliche Funktion mithilfe des __wrapped__
Attributs ausgraben und neu dekorieren, dies ist jedoch keinesfalls der Fall garantiert, und der Dekorateur, den Sie ersetzen möchten, ist möglicherweise auch nicht der einzige Dekorateur, der angewendet wird.
reload
Funktion, um den Python-Binärcode docs.python.org/2/library/functions.html#reload neu zu generieren und Ihren Dekorateur zu__init__
. Dadurch wurde sichergestellt, dass der Patch vor einer Testdatei geladen wurde. Wir haben einen isolierten Testordner, sodass die Strategie für uns funktioniert, dies funktioniert jedoch möglicherweise nicht für jedes Ordnerlayout.Es sollte beachtet werden, dass einige der Antworten hier den Dekorator für die gesamte Testsitzung und nicht für eine einzelne Testinstanz patchen. was unerwünscht sein kann. Hier erfahren Sie, wie Sie einen Dekorateur patchen, der nur durch einen einzigen Test erhalten bleibt.
Unsere Einheit, die mit dem unerwünschten Dekorateur getestet werden soll:
# app/uut.py from app.decorators import func_decor @func_decor def unit_to_be_tested(): # Do stuff pass
Vom Dekorateur-Modul:
# app/decorators.py def func_decor(func): def inner(*args, **kwargs): print "Do stuff we don't want in our test" return func(*args, **kwargs) return inner
Zu dem Zeitpunkt, an dem unser Test während eines Testlaufs erfasst wird, wurde der unerwünschte Dekorateur bereits auf unser zu testendes Gerät angewendet (da dies zum Zeitpunkt des Imports geschieht). Um dies zu beseitigen, müssen wir den Dekorator im Dekoratormodul manuell ersetzen und dann das Modul mit unserem Prüfling erneut importieren.
Unser Testmodul:
# test_uut.py from unittest import TestCase from app import uut # Module with our thing to test from app import decorators # Module with the decorator we need to replace import imp # Library to help us reload our UUT module from mock import patch class TestUUT(TestCase): def setUp(self): # Do cleanup first so it is ready if an exception is raised def kill_patches(): # Create a cleanup callback that undoes our patches patch.stopall() # Stops all patches started with start() imp.reload(uut) # Reload our UUT module which restores the original decorator self.addCleanup(kill_patches) # We want to make sure this is run so we do this in addCleanup instead of tearDown # Now patch the decorator where the decorator is being imported from patch('app.decorators.func_decor', lambda x: x).start() # The lambda makes our decorator into a pass-thru. Also, don't forget to call start() # HINT: if you're patching a decor with params use something like: # lambda *x, **y: lambda f: f imp.reload(uut) # Reloads the uut.py module which applies our patched decorator
Der Bereinigungsrückruf kill_patches stellt den ursprünglichen Dekorator wieder her und wendet ihn erneut auf das zu testende Gerät an. Auf diese Weise bleibt unser Patch nur durch einen einzigen Test und nicht durch die gesamte Sitzung bestehen - genau so sollte sich jeder andere Patch verhalten. Da die Bereinigung patch.stopall () aufruft, können wir auch alle anderen Patches in setUp () starten, die wir benötigen, und sie werden alle an einem Ort bereinigt.
Das Wichtige an dieser Methode ist, wie sich das Nachladen auf die Dinge auswirkt. Wenn ein Modul zu lange dauert oder eine Logik hat, die beim Import ausgeführt wird, müssen Sie möglicherweise nur den Dekorator als Teil des Geräts zucken und testen. :( Hoffentlich ist dein Code besser geschrieben. Richtig?
Wenn es einem egal ist, ob der Patch auf die gesamte Testsitzung angewendet wird , ist der einfachste Weg, dies zu tun, ganz oben in der Testdatei:
# test_uut.py from mock import patch patch('app.decorators.func_decor', lambda x: x).start() # MUST BE BEFORE THE UUT GETS IMPORTED ANYWHERE! from app import uut
Stellen Sie sicher, dass Sie die Datei mit dem Dekorator und nicht mit dem lokalen Bereich des Prüflings patchen und den Patch starten, bevor Sie das Gerät mit dem Dekorator importieren.
Interessanterweise wird auf alle bereits importierten Dateien der Patch auf den Dekorator angewendet, selbst wenn der Patch gestoppt wird. Dies ist die Umkehrung der Situation, mit der wir begonnen haben. Beachten Sie, dass diese Methode alle anderen Dateien im Testlauf patcht, die anschließend importiert werden - auch wenn sie selbst keinen Patch deklarieren.
quelle
Als ich zum ersten Mal auf dieses Problem gestoßen bin, habe ich mir stundenlang den Kopf zerbrochen. Ich habe einen viel einfacheren Weg gefunden, damit umzugehen.
Dadurch wird der Dekorateur vollständig umgangen, da das Ziel überhaupt nicht dekoriert wurde.
Dies ist in zwei Teile gegliedert. Ich schlage vor, den folgenden Artikel zu lesen.
http://alexmarandon.com/articles/python_mock_gotchas/
Zwei Fallstricke, denen ich immer wieder begegnete:
1.) Verspotten Sie den Decorator vor dem Import Ihrer Funktion / Ihres Moduls.
Die Dekoratoren und Funktionen werden zum Zeitpunkt des Ladens des Moduls definiert. Wenn Sie vor dem Import nicht verspotten, wird der Schein ignoriert. Nach dem Laden müssen Sie ein seltsames mock.patch.object ausführen, das noch frustrierender wird.
2.) Stellen Sie sicher, dass Sie den richtigen Weg zum Dekorateur verspotten.
Denken Sie daran, dass der Patch des Dekorators, den Sie verspotten, davon abhängt, wie Ihr Modul den Dekorator lädt, und nicht davon, wie Ihr Test den Dekorator lädt. Aus diesem Grund empfehle ich, für den Import immer vollständige Pfade zu verwenden. Dies erleichtert das Testen erheblich.
Schritte:
1.) Die Mock-Funktion:
from functools import wraps def mock_decorator(*args, **kwargs): def decorator(f): @wraps(f) def decorated_function(*args, **kwargs): return f(*args, **kwargs) return decorated_function return decorator
2.) Den Dekorateur verspotten:
2a.) Pfad nach innen mit.
with mock.patch('path.to.my.decorator', mock_decorator): from mymodule import myfunction
2b.) Patch oben in der Datei oder in TestCase.setUp
mock.patch('path.to.my.decorator', mock_decorator).start()
Mit beiden Methoden können Sie Ihre Funktion jederzeit in den TestCase oder dessen Methode / Testfälle importieren.
from mymodule import myfunction
2.) Verwenden Sie eine separate Funktion als Nebeneffekt des mock.patch.
Jetzt können Sie mock_decorator für jeden Dekorator verwenden, den Sie verspotten möchten. Sie müssen jeden Dekorateur einzeln verspotten, achten Sie also auf diejenigen, die Sie vermissen.
quelle
Folgendes hat bei mir funktioniert:
Es funktionierte wie ein Zauber.
quelle
Wir haben versucht, einen Dekorateur zu verspotten, der manchmal einen anderen Parameter wie einen String erhält, und manchmal nicht, z.
@myDecorator('my-str') def function() OR @myDecorator def function()
Dank einer der obigen Antworten haben wir eine Mock-Funktion geschrieben und den Dekorateur mit dieser Mock-Funktion gepatcht:
from mock import patch def mock_decorator(f): def decorated_function(g): return g if callable(f): # if no other parameter, just return the decorated function return decorated_function(f) return decorated_function # if there is a parametr (eg. string), ignore it and return the decorated function patch('path.to.myDecorator', mock_decorator).start() from mymodule import myfunction
Beachten Sie, dass dieses Beispiel für einen Dekorateur geeignet ist, der die dekorierte Funktion nicht ausführt, sondern nur einige Dinge vor dem eigentlichen Lauf ausführt. Falls der Dekorator auch die dekorierte Funktion ausführt und daher die Parameter der Funktion übertragen muss, muss die Funktion mock_decorator etwas anders sein.
Hoffe das wird anderen helfen ...
quelle
Vielleicht können Sie einen anderen Dekorator auf die Definitionen all Ihrer Dekoratoren anwenden, der im Grunde eine Konfigurationsvariable überprüft, um festzustellen, ob der Testmodus verwendet werden soll.
Wenn ja, ersetzt es den Dekorateur, den es dekoriert, durch einen Dummy-Dekorateur, der nichts tut.
Andernfalls lässt es diesen Dekorateur durch.
quelle
Konzept
Dies mag etwas seltsam klingen, aber man kann
sys.path
mit einer Kopie von sich selbst patchen und einen Import im Rahmen der Testfunktion durchführen. Der folgende Code zeigt das Konzept.from unittest.mock import patch import sys @patch('sys.modules', sys.modules.copy()) def testImport(): oldkeys = set(sys.modules.keys()) import MODULE newkeys = set(sys.modules.keys()) print((newkeys)-(oldkeys)) oldkeys = set(sys.modules.keys()) testImport() -> ("MODULE") # Set contains MODULE newkeys = set(sys.modules.keys()) print((newkeys)-(oldkeys)) -> set() # An empty set
MODULE
kann dann durch das Modul ersetzt werden, das Sie testen. (Dies funktioniert in Python 3.6 mitMODULE
ersetzt durchxml
zum Beispiel)OP
Für Ihren Fall, lassen Sie uns die Dekorateur Funktion besteht in dem Modul sagen
pretty
und die geschmückten Funktion besteht inpresent
, dann würden Sie flickenpretty.decorator
die Mock Maschinen und Ersatz VerwendungMODULE
mitpresent
. So etwas wie das Folgende sollte funktionieren (ungetestet).Klasse TestDecorator (unittest.TestCase): ...
@patch(`pretty.decorator`, decorator) @patch(`sys.path`, sys.path.copy()) def testFunction(self, decorator) : import present ...
Erläuterung
Dies funktioniert, indem
sys.path
für jede Testfunktion eine "Bereinigung" bereitgestellt wird , wobei eine Kopie des Stromssys.path
des Testmoduls verwendet wird. Diese Kopie wird erstellt, wenn das Modul zum ersten Mal analysiert wird, um sicherzustellen,sys.path
dass alle Tests konsistent sind .Nuancen
Es gibt jedoch einige Implikationen. Wenn das Testframework mehrere Testmodule unter derselben Python-Sitzung ausführt,
MODULE
bricht jedes Testmodul, das global importiert wird , jedes Testmodul, das es lokal importiert. Dies zwingt dazu, den Import überall lokal durchzuführen. Wenn das Framework jedes Testmodul unter einer separaten Python-Sitzung ausführt, sollte dies funktionieren. Ebenso können Sie möglicherweise nichtMODULE
global in ein Testmodul importieren, in das Sie importierenMODULE
lokal .Die lokalen Importe müssen für jede Testfunktion innerhalb einer Unterklasse von durchgeführt werden
unittest.TestCase
. Es ist möglicherweise möglich, dies auf dieunittest.TestCase
Unterklasse anzuwenden, um einen bestimmten Import des Moduls für alle Testfunktionen innerhalb der Klasse direkt verfügbar zu machen.Eingebaute Ins
Diejenigen , Messing mit
builtin
Importen findet ErsatzMODULE
mitsys
,os
usw. fehl, da diese auf alread werden ,sys.path
wenn Sie versuchen , es zu kopieren. Der Trick hier ist, Python mit deaktivierten eingebauten Importen aufzurufen. Ich denke, daspython -X test.py
wird es tun, aber ich vergesse das entsprechende Flag (siehepython --help
). Diese können anschließend lokal mitimport builtins
IIRC importiert werden .quelle
Um einen Dekorator zu patchen, müssen Sie entweder das Modul importieren oder neu laden, das diesen Dekorator nach dem Patchen verwendet, ODER den Verweis des Moduls auf diesen Dekorator insgesamt neu definieren.
Dekorateure werden zum Zeitpunkt des Imports eines Moduls angewendet. Wenn Sie ein Modul importieren, das einen Dekorator verwendet, den Sie oben in Ihrer Datei patchen möchten, und versuchen, es später zu patchen, ohne es erneut zu laden, hat der Patch keine Auswirkung.
Hier ist ein Beispiel für den ersten erwähnten Weg, dies zu tun - das Neuladen eines Moduls nach dem Patchen eines verwendeten Dekorators:
import moduleA ... # 1. patch the decorator @patch('decoratorWhichIsUsedInModuleA', examplePatchValue) def setUp(self) # 2. reload the module which uses the decorator reload(moduleA) def testFunctionA(self): # 3. tests... assert(moduleA.functionA()...
Hilfreiche Referenzen:
imp.reload
reload
quelle
für @lru_cache (max_size = 1000)
class MockedLruCache(object):
cache.LruCache = MockedLruCache
Wenn Sie einen Dekorator verwenden, der keine Parameter hat, sollten Sie:
def MockAuthenticated(func): return func
from tornado import web web.authenticated = MockAuthenticated
quelle