- wprowadzenie
- unittest.,mock lub mock
- dekorator
- lokalizacja zasobów
- Mock return_value vs side_effect
- Mock wywołania zagnieżdżone
- weryfikacja WYJĄTKÓW
- Mock poziom modułu/zmienne globalne
- Mock metoda instancji
- Mock metoda Klasy
- Mock cała klasa
- Mock asynchroniczne wywołania
- typy Mock instancji
- mock builtin
open
funkcja - podsumowanie
czyszczenie lru_cache
wprowadzenie
wyśmiewanie zasobów podczas pisania testów w Pythonie może być mylące, jeśli nie jesteś zaznajomiony z robieniem takich rzeczy., W tym poście omówię różne aspekty szydzenia z kodu, który, mam nadzieję, będzie przydatnym źródłem dla tych, którzy są nieco zablokowani.
Uwaga: w przykładach kodu używam pytest, ale w większości nie powinno to mieć znaczenia.
mock lub mock
w celu „mock” zasobu będziemy najpierw potrzebować modułu mock
I to jest nasza pierwsza przeszkoda: której wersji potrzebujemy? tzn. są dwa i oba wyglądają na oficjalne (mock
I unittest.mock
).,
modułmock
jest biblioteką kompatybilną wstecz, którą można pobrać z PyPy, gdzie jakounittest.mock
jest tym samym, ale kompatybilnym tylko z używaną wersją Pythona.,
tak więc w prawie wszystkich przypadkach będziesz chciał zaimportować go w następujący sposób:
import unittest.mock as mock
aby uzyskać więcej przykładów, zobacz ten przewodnik
dekorator
najczęstszy sposób mock resources polega na użyciu dekoratora Pythona wokół funkcji testowej:
@mock.patch("thing")def test_stuff(mock_thing): mock_thing.return_value = 123
w tym przypadku to, co łatamy (thing
) może być zmienną lub funkcją.,
Kiedy to zrobisz, będziesz musiał przekazać argument do swojej funkcji (możesz ją nazwać jak chcesz†), który będzie MagicMock
.
oznacza to, że jeśli nie zrobisz nic innego, wywołanie thing
spowoduje (przynajmniej w powyższym przykładzie) zwrócenie wartości 123
.
† konwencja jest nazwa zmiennej
mock_<noun>
.,
Jeśli wyśmiewasz wiele rzeczy, stosujesz wzorcowe dekoratory na sobie i przekazujesz je do funkcji testowej:
@mock.patch("third")@mock.patch("second")@mock.patch("first")def test_stuff(mock_first, mock_second, mock_third): ...
lokalizacja zasobu
ważne jest, aby wiedzieć, że podczas wyśmiewania powinieneś określić lokalizację zasobu, który ma być wyśmiewany, istotne dla Ciebie.gdzie jest importowany., Jest to najlepiej wyjaśnione na przykładzie…
wyobraź sobie, że mam moduł app.foo
I wewnątrz tego modułu importuję inną zależność w ten sposób:
from app.bar import thing
możesz pomyśleć, że kiedy wywołujesz mock.patch
przekazujesz mu odniesienie do zasobu, takie jak app.bar.thing
Byłoby to istotne tylko wtedy, gdyby zasób był wywoływany z tą pełną ścieżką w module app.foo
(np. jeśli app.foo
wywołany app.bar.thing(...)
).,
Jeśli ścieżka do pełnej przestrzeni nazw nie jest odwołana, czego nie ma w powyższym przykładzie (zauważ, że importujemy tylko zasób thing
). Oznacza to, że musimy określić przestrzeń nazw odniesienia do mocka jako miejsce importowania:
@mock.patch('app.foo.thing')
tak więc mimo że thing
istnieje w app.bar
określamy app.foo.thing
jako app.foo.thing
div id = „19715b226b” > jest miejscem, w którym zaimportowaliśmy go do użytku. To łapie ludzi cały czas.,
Mock return_value vs side_effect
Jeśli twoja funkcja ma wokół siebie try/except, możesz użyć side_effect
, aby wywołać wyjątek jako zwracaną wartość:
@mock.patch('app.aws.sdk.confirm_sign_up', side_effect=Exception('whoops'))
uwaga: jeśli użyłeś
return_value=Exception('whoops')
, to mock zwróci reprezentację ciągu znaków wyjątku zamiast wywoływać wyjątek, jak robi toside_effect
.,metoda na wyśmiewanym obiekcie została wywołana:powód, dla którego może to być bardziej skomplikowane, wynika z tego, jak mock zwróci nową makietę podczas uzyskiwania dostępu do właściwości na makiecie:
powyższy kod spowoduje błąd:
AssertionError: expected call not found.Expected: listen(8080)Actual: listen(123)
musisz upewnić się, że uzyskasz makietę we właściwym czasie:
zweryfikuj wyjątki
Jeśli chcemy zweryfikować, że niektóre fragment kodu rzuca
Exception
Typ kiedy tego potrzebujemy możemy wyśmiewać konkretne zasoby, aby rzucić wyjątek, a następnie użyćpytest.raises
jako menedżera kontekstu wokół wywołującego nasz kod do weryfikacji.,możemy wyłapać i tworzyć twierdzenia przeciwko temu oczekiwanemu zachowaniu, najpierw wyśmiewając zasób, który chcemy rzucić wyjątek i zmuszając go do rzucenia własnego fałszywego wyjątku za pomocą parametru
side_effect
.następnie określamy dokładny typ wyjątku, który ma zostać podniesiony, używając
pytest.raises(T)
:uwaga: nie popełniaj błędu i nie umieszczaj żadnych twierdzeń w
with
menedżer kontekstowy., Po wywołaniu wyjątku przez funkcję wwith
menedżer kontekstu, cały kod po nim wewnątrz bloku jest pomijany.czyszczenie lru_cache
Jeśli funkcja, którą chcesz przetestować, ma zastosowany dekorator
functools.lru_cache
, musisz pamiętać o szydzeniu z odpowiedzi tej funkcji, ponieważ będzie ona buforowana w jednym teście, a wynik buforowany zostanie zwrócony podczas ponownego wywołania funkcji, aby przetestować inne zachowanie (i może cię zmylić, gdy zobaczysz nieoczekiwaną odpowiedź).,aby rozwiązać ten problem jest bardzo proste, ponieważ
lru_cache
zapewnia dodatkowe funkcje podczas dekorowania funkcji, zapewnia:
cache_info
cache_clear
ten ostatni (
cache_clear
) jest tym, co musisz wywołać. Jest to pokazane poniżej:Uwaga: debugowanie nie zawsze jest oczywiste., Później zademonstrowałem, jak wyśmiewać wbudowaną funkcję
open
I w tym scenariuszu natknąłem się na ten problem, ponieważ chociaż nie wyśmiewałem samej funkcji najwyższego poziomu (wyśmiewałem wywołanie doopen
wewnątrz), zawartość otwieranego pliku była tym, co zostało ZWRÓCONE i było buforowane.Mock poziom modułu/zmienne globalne
za pomocą zmiennej modułu można ustawić wartość bezpośrednio lub użyć
mock.patch
.,w poniższym przykładzie mamy zmienną
client_id
, która jest zmienną globalną wewnątrz modułuapp.aws
, którą importujemy do odwołania w innym miejscu naszego kodu:w przykładzie
mock.patch
są dwie kluczowe rzeczy do zauważenia:
- nie używamy
return_value
.- do funkcji testowej nie jest przekazywana Żadna przykładowa instancja.,
dzieje się tak dlatego, że modyfikujemy zmienną, a nie funkcję bezpośrednią lub 'wywoływalną', więc nie ma potrzeby przekazywania makiety do funkcji testowej (jeśli chcesz zmienić wartość kilka razy w samym teście, to makietę zmiennej, ale nie od razu przypisać wartość w dekoratorze).
metoda Mock Instance
istnieje wiele sposobów na wyśmiewanie metody instancji., Jedną z powszechnych metod jest użycie
mock.patch.object
, w ten sposób:inne podejście polega na naśmiewaniu się z metody, tak jak w przypadku zwykłej funkcji, ale odwołujesz się do metody za pomocą nazwy klasy:
inne (choć bardziej ciężkie) podejście do wyśmiewania metody instancji klasy polega na wykorzystaniu faktu, że Mock zwróci nową instancję, gdy zostanie wywołana:
@mock.patch("foo.bar.SomeClass")def test_stuff(mock_class): mock_class.return_value.made_up_function.return_value = "123"
@mock.patch("foo.bar.SomeClass")def test_stuff(mock_class): mock_class.return_value.made_up_function.return_value = "123"
@mock.patch("foo.bar.SomeClass")def test_stuff(mock_class): mock_class.return_value.made_up_function.return_value = "123"
@mock.patch("foo.bar.SomeClass")def test_stuff(mock_class): mock_class.return_value.made_up_function.return_value = "123"
uwaga: w powyższym przykładzie wyśmiewamy całą klasę, która może nie być tym, czego chcesz. Jeśli nie, użyj poprzedniego przykładu
mock.patch.object
.,
powyższy przykład działa, ponieważ ustawiamyreturn_value
na naszej makiecie. Ponieważ jest to MagicMock
każdy odwołany atrybut zwraca nową instancję mocka (funkcja lub właściwość, którą wywołujesz na mocku, nie musi istnieć), więc wywołujemy made_up_function
na zwracanej mocku, a na nowo utworzonej mocku ustawiamy końcową return_value
na return_value
420ec07c94 ” > .,
ale jak wspomniano w powyższej notce, podejście to może być trochę zbyt tępe w zależności od tego, jakie są Twoje potrzeby (czy zależy ci na tym, czy masz jakąś funkcjonującą klasę, czy nie).
Mock Class Method
mock class method to mock a class method is a similar approach to mocking a instance method.,
jedno podejście może być takie, że wyśmiewasz całą klasę (ale teraz masz o jedną mniej return_value
do przypisania):
mock_class.ClassMethodName.return_value = "123"
lub jeszcze lepiej powinieneś wyśmiewać ją tak, jak każdą normalną funkcję, ale po prostu odwołaj się do metody za pomocą klasy:
wyśmiewaj całą klasę
aby wyśmiewać całą klasę Klasa musisz ustawić return_value
jako nową instancję klasy.,
Zobacz inne porady dotyczące szydzenia związane z klasami tutaj
Mock asynchroniczne wywołania
szydzenie z kodu asynchronicznego jest prawdopodobnie najbardziej mylącym aspektem szydzenia. Moje rozwiązanie „przejdź do” wyjaśnię najpierw, ale potem podzielę się kilkoma alternatywnymi metodami, które widziałem i próbowałem w przeszłości.,
najpierw rozważmy ten kod asynchroniczny wewnątrz app.foo
moduł:
import app.stuffasync def do_thing(x): return await app.stuff.some_concurrent_function(x)
Jeśli musimy wyśmiewać coroutine app.stuff.some_concurrent_function
, możemy rozwiązać ten problem, tworząc funkcję, która działa jak coroutine i pozwala jej na możliwość konfiguracji dla różnych typów odpowiedzi:
Uwaga: przykład wykorzystuje tornado do uruchomienia testu asynchronicznego.,e alternatywy…
AsyncMock
Uwaga: To wykorzystuje pakiet
pytest-asyncio
aby pomóc w testowaniu kodu asyncioZacznijmy od kodu, który ma być wyśmiewany…
import asyncioasync def sum(x, y): await asyncio.sleep(1) return x + y
teraz oto, jak to wyśmiewamy…
Monkey Patch
podklasa magicmock
asynchroniczna funkcja inline
typy Mock instancji
podczas szydzenia z obiektu okaże się, że makieta zastępuje cały obiekt, co może spowodować, że testy zostaną zakończone (lub nie) w nieoczekiwany sposób.,
oznacza to, że jeśli chcesz zrobić makietę bardziej podobną do konkretnego interfejsu, są dwa sposoby:
spec
wrap
możemy użyć makiety
spec
funkcja naśladująca wszystkie metody / atrybuty wyśmiewanego obiektu. Zapewnia to, że mocki mają takie samo api jak obiekty, które zastępują.Uwaga: Istnieje bardziej rygorystyczne
spec_set
, które wywołająAttributeError
.,najlepiej zademonstrować to na przykładzie:
parametr
wrap
z drugiej strony pozwala na 'szpiegowanie' implementacji, a także wpływa na jej zachowanie.,z powyższego, który wyśmiewa cały obiekt, a nie tylko pojedynczą metodę:makieta wbudowanej funkcji otwartej
biblioteka makiety Pythona zapewnia abstrakcję do wyśmiewania wbudowanej
open
funkcja o wiele prostsza…
create=True
param ustawiony namock.patch
oznacza, że zwróconamock.MagicMock
automatycznie utworzy wszelkie atrybuty wywołane na makiecie (dzieje się tak dlatego, że funkcjaopen
spróbuje uzyskać dostęp do wielu różnych rzeczy i łatwiej jest Mock ' owi wyśmiewać to wszystko dla ciebie).,podsumowanie
tam skończymy. Mam nadzieję, że ta lista technik wyśmiewania będzie w stanie przeprowadzić Cię przez nawet najbardziej złożony kod, który trzeba przetestować. Daj mi znać, co myślisz na Twitterze.