Dekoratory są stosowane w czasie definiowania funkcji. W przypadku większości funkcji ma to miejsce podczas ładowania modułu. (Funkcje zdefiniowane w innych funkcjach mają dekorator stosowany za każdym razem, gdy wywoływana jest funkcja otaczająca).
Więc jeśli chcesz małpa łatać dekorator, musisz zrobić:
- Zaimportuj moduł, który go zawiera
- Zdefiniuj funkcję dekoratora pozorowanego
- Ustaw np
module.decorator = mymockdecorator
- Zaimportuj moduły korzystające z dekoratora lub użyj go we własnym module
Jeśli moduł zawierający dekorator zawiera również funkcje, które go używają, te są już ozdobione, zanim je zobaczysz, i prawdopodobnie jesteś SOL
Edytuj, aby odzwierciedlić zmiany w Pythonie od czasu, gdy pierwotnie napisałem to: Jeśli dekorator używa, functools.wraps()
a wersja Pythona jest wystarczająco nowa, możesz być w stanie wykopać oryginalną funkcję za pomocą __wrapped__
atrybutu i ponownie ją ozdobić, ale w żadnym wypadku nie jest to jest gwarantowane, a dekorator, którego chcesz wymienić, może nie być jedynym zastosowanym dekoratorem.
reload
funkcji, aby ponownie wygenerować kod binarny Pythona docs.python.org/2/library/functions.html#reload i monkeypatch your decorator__init__
. To zapewniało, że łatka została załadowana przed jakimkolwiek plikiem testowym. Mamy izolowany folder testów, więc strategia działa dla nas, ale może to nie działać dla każdego układu folderów.Należy zauważyć, że kilka z przedstawionych tutaj odpowiedzi załatuje dekorator dla całej sesji testowej, a nie dla jednej instancji testowej; co może być niepożądane. Oto jak załatać dekorator, który utrzymuje się tylko przez jeden test.
Nasza jednostka do przetestowania z niepożądanym dekoratorem:
# app/uut.py from app.decorators import func_decor @func_decor def unit_to_be_tested(): # Do stuff pass
Z modułu dekoratorów:
# 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
Zanim nasz test zostanie zebrany podczas przebiegu testowego, niepożądany dekorator został już zastosowany do naszej testowanej jednostki (ponieważ dzieje się to w czasie importu). Aby się tego pozbyć, musimy ręcznie wymienić dekorator w module dekoratora, a następnie ponownie zaimportować moduł zawierający nasz testowany egzemplarz.
Nasz moduł testowy:
# 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
Wywołanie zwrotne czyszczenia, kill_patches, przywraca oryginalny dekorator i ponownie stosuje go do testowanej jednostki. W ten sposób nasza łatka utrzymuje się tylko przez jeden test, a nie przez całą sesję - i dokładnie tak powinna zachowywać się każda inna łatka. Ponadto, ponieważ czyszczenie wywołuje patch.stopall (), możemy uruchomić dowolne inne poprawki w setUp (), których potrzebujemy, i zostaną one wyczyszczone w jednym miejscu.
Ważne jest, aby zrozumieć tę metodę, jak przeładowanie wpłynie na rzeczy. Jeśli moduł trwa zbyt długo lub ma logikę działającą podczas importu, wystarczy wzruszyć ramionami i przetestować dekorator jako część jednostki. :( Mam nadzieję, że twój kod jest lepiej napisany.
Jeśli nie obchodzi nas, czy łatka zostanie nałożona na całą sesję testową , najłatwiej to zrobić bezpośrednio na górze pliku testowego:
# 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
Pamiętaj, aby załatać plik za pomocą dekoratora, a nie lokalnego zakresu testowanego egzemplarza, i uruchomić poprawkę przed zaimportowaniem jednostki za pomocą dekoratora.
Co ciekawe, nawet jeśli łatka zostanie zatrzymana, wszystkie pliki, które zostały już zaimportowane, nadal będą miały poprawkę nałożoną na dekorator, co jest odwrotnością sytuacji, od której zaczęliśmy. Należy pamiętać, że ta metoda załatuje wszystkie inne pliki w przebiegu testowym, które są później importowane - nawet jeśli same nie deklarują poprawki.
źródło
Kiedy pierwszy raz natknąłem się na ten problem, przez wiele godzin męczyłem mózg. Znalazłem znacznie łatwiejszy sposób, aby sobie z tym poradzić.
To całkowicie ominie dekoratora, tak jakby cel nie był w ogóle udekorowany.
Dzieli się to na dwie części. Proponuję przeczytać następujący artykuł.
http://alexmarandon.com/articles/python_mock_gotchas/
Dwie pułapki, na które wpadałem:
1.) Mock the Decorator przed zaimportowaniem funkcji / modułu.
Dekoratory i funkcje są definiowane w momencie ładowania modułu. Jeśli nie kpisz przed zaimportowaniem, zignoruje to próbę. Po załadowaniu musisz wykonać dziwny mock.patch.object, co jest jeszcze bardziej frustrujące.
2.) Upewnij się, że kpisz z właściwej ścieżki do dekoratora.
Pamiętaj, że poprawka dekoratora, z którego kpisz, zależy od tego, jak moduł ładuje dekorator, a nie od tego, jak test ładuje dekorator. Dlatego radzę zawsze używać pełnych ścieżek do importu. To znacznie ułatwia testowanie.
Kroki:
1.) Funkcja Mock:
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.) Kpiny z dekoratora:
2a.) Ścieżka wewnątrz z.
with mock.patch('path.to.my.decorator', mock_decorator): from mymodule import myfunction
2b.) Poprawka na górze pliku lub w TestCase.setUp
mock.patch('path.to.my.decorator', mock_decorator).start()
Każdy z tych sposobów pozwoli ci zaimportować twoją funkcję w dowolnym momencie w TestCase lub jej metodach / przypadkach testowych.
from mymodule import myfunction
2.) Użyj oddzielnej funkcji jako efektu ubocznego makiety.
Teraz możesz użyć mock_decorator dla każdego dekoratora, z którego chcesz kpić. Będziesz musiał kpić z każdego dekoratora osobno, więc uważaj na tych, których tęsknisz.
źródło
Pracowały dla mnie:
Zadziałało jak urok.
źródło
Próbowaliśmy kpić z dekoratora, który czasami pobiera inny parametr, jak string, a czasami nie, np .:
@myDecorator('my-str') def function() OR @myDecorator def function()
Dzięki jednej z powyższych odpowiedzi napisaliśmy funkcję próbną i załataliśmy dekorator z tą funkcją makiety:
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
Zauważ, że ten przykład jest dobry dla dekoratora, który nie uruchamia funkcji dekorowania, a tylko wykonuje pewne czynności przed właściwym uruchomieniem. W przypadku, gdy dekorator uruchamia również funkcję dekorowania, a zatem musi przekazać parametry funkcji, funkcja mock_decorator musi być nieco inna.
Mam nadzieję, że to pomoże innym ...
źródło
Może możesz zastosować inny dekorator do definicji wszystkich swoich dekoratorów, który zasadniczo sprawdza niektóre zmienne konfiguracyjne, aby zobaczyć, czy ma być użyty tryb testowania.
Jeśli tak, zastępuje dekoratora, który ozdabia, sztucznym dekoratorem, który nic nie robi.
W przeciwnym razie przepuszcza ten dekorator.
źródło
Pojęcie
Może to zabrzmieć trochę dziwnie, ale można załatać
sys.path
kopię samego siebie i wykonać import w zakresie funkcji testowej. Poniższy kod przedstawia koncepcję.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
można wtedy zastąpić testowanym modułem. (Działa to w Pythonie 3.6 zMODULE
podstawionymxml
na przykład)OP
W Twoim przypadku, powiedzmy, że mieszka funkcyjne dekorator w module
pretty
i urządzone mieszka funkcyjnychpresent
, wtedy można załataćpretty.decorator
za pomocą makiety maszyn i namiastkęMODULE
zpresent
. Powinno działać coś podobnego do następującego (nie przetestowano).class TestDecorator (unittest.TestCase): ...
@patch(`pretty.decorator`, decorator) @patch(`sys.path`, sys.path.copy()) def testFunction(self, decorator) : import present ...
Wyjaśnienie
Działa to poprzez zapewnienie "czystego"
sys.path
dla każdej funkcji testowej przy użyciu kopii prądusys.path
modułu testowego. Ta kopia jest tworzona podczas pierwszej analizy modułu, zapewniając spójnośćsys.path
wszystkich testów.Niuanse
Istnieje jednak kilka konsekwencji. Jeśli środowisko testowe uruchamia wiele modułów testowych w tej samej sesji Pythona, każdy moduł testowy, który importuje
MODULE
globalnie, uszkadza każdy moduł testowy, który importuje go lokalnie. To zmusza do wykonywania importu lokalnie wszędzie. Jeśli framework uruchamia każdy moduł testowy w oddzielnej sesji Pythona, to powinno działać. Podobnie nie możesz importowaćMODULE
globalnie w ramach modułu testowego, gdy importujeszMODULE
lokalnie.Lokalne importy należy wykonać dla każdej funkcji testowej w podklasie
unittest.TestCase
. Być może można to zastosowaćunittest.TestCase
bezpośrednio do podklasy, udostępniając określony import modułu dla wszystkich funkcji testowych w klasie.Wbudowane Ins
Ci, brudząc z
builtin
importu znajdzie wymianieMODULE
zsys
,os
itd. Nie powiedzie się, ponieważ są one alread nasys.path
kiedy próbujesz go skopiować. Sztuczka polega na wywołaniu Pythona z wyłączonymi wbudowanymi importami, myślę, żepython -X test.py
zrobię to, ale zapomnę o odpowiedniej fladze (zobaczpython --help
). Można je następnie importować lokalnie przy użyciuimport builtins
IIRC.źródło
Aby załatać dekorator, musisz zaimportować lub przeładować moduł, który używa tego dekoratora po jego łataniu, LUB całkowicie przedefiniować odniesienie modułu do tego dekoratora.
Dekoratory są stosowane podczas importowania modułu. Dlatego jeśli zaimportowałeś moduł, który używa dekoratora, który chcesz załatać na początku pliku, i spróbujesz go załatać później bez ponownego ładowania, łatka nie odniesie skutku.
Oto przykład pierwszego wspomnianego sposobu robienia tego - przeładowania modułu po łataniu dekoratora, którego używa:
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()...
Przydatne referencje:
imp.reload
reload
źródło
dla @lru_cache (max_size = 1000)
class MockedLruCache(object):
cache.LruCache = MockedLruCache
jeśli używasz dekoratora, który nie ma parametrów, powinieneś:
def MockAuthenticated(func): return func
from tornado import web web.authenticated = MockAuthenticated
źródło