• 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
  • czyszczenie lru_cache

  • 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 openfunkcja
  • podsumowanie

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.foowywoł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 to side_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ącpytest.raises(T):

uwaga: nie popełniaj błędu i nie umieszczaj żadnych twierdzeń wwith 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 dekoratorfunctools.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 do open 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łu app.aws, którą importujemy do odwołania w innym miejscu naszego kodu:

w przykładzie mock.patch są dwie kluczowe rzeczy do zauważenia:

  1. nie używamy return_value.
  2. 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_value420ec07c94 ” > .,

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 asyncio

Zacznijmy 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:

  1. spec
  2. wrap

możemy użyć makietyspec 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 rygorystycznespec_set, które wywołająAttributeError.,

najlepiej zademonstrować to na przykładzie:

parametrwrap 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 na mock.patch oznacza, że zwrócona mock.MagicMock automatycznie utworzy wszelkie atrybuty wywołane na makiecie (dzieje się tak dlatego, że funkcja open 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.