python szyderczy coś bez sensu o ciebie. Nie jest łatwo sobie z Tobą poradzić i zajmuje to kilka chwil ” Ah, teraz rozumiem!”, zanim zostaniesz lepiej zrozumiany., Jesteś potężnym, fantastycznym narzędziem umożliwiającym zautomatyzowane testy jednostkowe, ale znam wielu programistów, którzy „radzą sobie” z posiadaną wiedzą i pisanie tych testów trwa dłużej niż powinno.
jest wiele artykułów o wyśmiewaniu, i tak, piszę teraz kolejny. Jednak naprawdę chcę mieć szansę na jasne wyjaśnienie tego, zwłaszcza tych „Aha!”chwile, które popchnęły moje zrozumienie.
Po Pierwsze, co to jest wyśmiewanie? Oto cytat z dokumentów:
mock jest biblioteką do testowania w Pythonie., Pozwala na zastąpienie części testowanego systemu obiektami wzorcowymi i stwierdzenie, w jaki sposób zostały one użyte.
więc „pozwala na wymianę części systemów” , ale jakie części? Okazuje się, że mock można praktycznie dowolnie zdefiniować, na przykład:
- funkcje
- Klasy
- obiekty
zamieniamy je na „mock objects”, które są instancjami klasy Mock lub MagicMock.,
następnie „tworzysz twierdzenia” o tej przykładowej instancji, co jest sposobem sprawdzenia, czy przykładowa instancja została użyta w sposób, którego oczekiwałeś.
w tym artykule omówię podstawy wymiany części systemów, ale nie będę zajmował się robieniem twierdzeń.
Zacznijmy od prostego przykładu. Zamierzam stworzyć nowy moduł o nazwie simple.py i w tym module zamierzam zdefiniować funkcję o nazwie simple_function, która po prostu zwraca ciąg znaków w ten sposób:
def simple_function():
return "You have called simple_function"
teraz zamierzam utworzyć drugi moduł o nazwie use_simple.py., W tym module zamierzam zaimportować makietę pakietu z unittest i zaimportować prosty moduł:
from unittest import mockimport simple
następnie zamierzam napisać dwie funkcje, jedną, która normalnie wywołuje simple_function i jedną, która wyśmiewa wywołanie do niego. Zauważ, że normalnie zrobiłbyś to w kontekście testowania, ale celowo usuwam to, abym mógł skupić się na części wyśmiewającej. Pierwsza funkcja:
def use_simple_function():
result = simple.simple_function()
print(result)use_simple_function()
jak widać wyświetla wynik funkcji simple_function., W szczególności, wyjście jest:
You have called simple_function
dla drugiej funkcji, do mocka simple_function zamierzam użyć mocka.patch decorator. Ten dekorator pozwala określić, co chcesz zrobić, przekazując mu ciąg znaków w formacie ' pakiet.moduł.FunctionName”., W naszym przykładzie nie ma pakietu, ale moduł nazywa się simple, a funkcja nazywa się simple_function, więc dekorator będzie wyglądał następująco:
@mock.patch('simple.simple_function')
musimy napisać naszą drugą funkcję w ten sposób:
@mock.patch('simple.simple_function')
def mock_simple_function():
nazwałem ją mock_simple_function, ale można ją nazwać dowolną. Brakuje nam tutaj parametru, ponieważ gdy dekorujesz funkcję za pomocą @ mock.patch przejdzie instancję klasy MagicMock (obiekt MagicMock), która jest używana do zastąpienia funkcji, którą wyśmiewasz., Więc będzie to wyglądało tak:
@mock.patch('simple.simple_function')
def mock_simple_function(mock_simple_func):
więc podając @mock.łatka („simple.simple_function') mówię, że chcę zastąpić simple_function obiektem MagicMock przypisanym do parametru o nazwie mock_simple_func.
początkowo uelastycznijmy funkcję przez wydrukowanie mock_simple_func, a następnie wywołajmy ją:
Po uruchomieniu wyjście to:
<MagicMock name='simple_function'>
który jest obiektem MagicMock, który zostanie wywołany zamiast simple_function., Ważne jest, aby pamiętać, że atrybut name obiektu jest rzeczą, która jest wyśmiewana, A id jest unikalnym numerem Dla obiektu.
Jeśli teraz również wydrukować simple_function (ale nie wywołać go):
co daje wyjście:
<MagicMock name='simple_function'>
<MagicMock name='simple_function'>
widać, że z pasujących id, że mock_simple_func jest tym samym obiektem MagicMock co simple_function.
na razie dobrze. Wyśmiewaliśmy simple_function. Aby być jawnym, simple_function została wyśmiana, ponieważ została zastąpiona obiektem MagicMock.,
dodajmy linie z pierwszej funkcji:
Po uruchomieniu wyjście to:
Jak widać z trzeciej linii wyjścia po wywołaniu simple_function tworzy inny obiekt MagicMock. Pamiętaj, że ponieważ wyśmiewaliśmy simple_function, jest to w rzeczywistości obiekt MagicMock, więc kiedy wywołujemy simple_function, tak naprawdę wywołujemy obiekt MagicMock!
dla mnie to właśnie tu zaczyna się zamieszanie.
- dlaczego powstaje nowy obiekt MagicMock?
- a jak można nazwać obiekt?, Zwykle otrzymujesz „TypeError:' * * * * 'obiekt nie jest wywoływalny” podczas próby.
zatrzymajmy się na chwilę, aby przyjrzeć się bliżej MagicMock.
MagicMock ma magię w swojej nazwie, ponieważ ma domyślne implementacje większości metod magicznych Pythona. Co to jest magiczna metoda Pythona? Wszystkie specjalne funkcje Pythona mają podwójne podkreślenie na początku i końcu nazwy. Listę z nich znajdziesz tutaj. Interesujący jest _ _ call__., Funkcja wywołania jest opisana jako:
wywoływana, gdy obiekt jest wywoływany jako funkcja
więc jeśli klasa implementuje tę funkcję, możesz utworzyć instancję klasy, a następnie wywołać tę instancję jak funkcję, np. :
dlatego gdy MagicMock jest wywoływany jako funkcja, __wywołana jest funkcja Call__. Implementacja tej funkcji przez MagicMock tworzy nową makietę!
więc gdy wywołamy simple_function (), tworzony jest nowy mock i zwracany. Nie tylko to, ale mamy pojęcie rodzica i dziecka wyśmiewa., Kiedy JEDEN mock tworzy drugiego, staje się rodzicem, a nowo utworzony dzieckiem. Ten dziecięcy mock może tworzyć inne kpiny, więc kończysz z dziedzictwem fałszywych przedmiotów.
Learning point 1: gdy obiekt MagicMock jest wywoływany jak funkcja, domyślnie tworzy i zwraca nowy obiekt MagicMock.
wróć do naszego przykładu. Do tej pory tylko wyśmiewaliśmy simple_function, ale nie zrobiliśmy z nią niczego innego niż wydrukowanie MagicMocks, które zostały utworzone w wyniku tego. Powiedzmy, że chciałem zmienić to, co zostało zwrócone z simple_function (), jak to zrobić?, Zrobiłbym to używając właściwości return_value MagicMock w ten sposób (zamierzam również usunąć dwa pierwsze wydruki, ponieważ nie są już potrzebne):
Po uruchomieniu wyjście to:
You have mocked simple_function
jak widać, gdy simple_function() jest teraz wywoływana MagicMock zwracana jest return_value zamiast tworzenia drugiego obiektu MagicMock.
Learning point 2: aby zmienić to, co jest zwracane, gdy obiekt MagicMock jest wywoływany jak funkcja, Ustaw return_value.
Jeśli chcesz zrobić więcej niż zmienić wartość zwracaną, możesz użyć MagicMock.funkcja side_effect., Pozwala określić całkowicie nową funkcję, która zostanie wywołana zamiast tej, którą wyśmiewasz. Zróbmy to. Po pierwsze, zdefiniuję nową funkcję, która będzie side_effect (zauważ, że musi mieć taki sam parametr jak funkcja, którą wyśmiewasz):
def side_effect_function():
return "SKABLAM!"
teraz możemy zdefiniować nową funkcję, która używa tego efektu ubocznego w następujący sposób:
wszystko jest takie samo jak funkcja mock_simple_function dalej, z wyjątkiem ustawiam side_effect mock_simple_func zamiast return_value.,
Wyjście To:
SKABLAM!
dobrym przykładem użycia efektu ubocznego jest, jeśli chcesz przetestować przepływ błędu i dlatego chcesz zgłosić wyjątek w swoim teście. Zmienię funkcję side_effect_function, aby wywołać błąd:
def side_effect_function():
raise FloatingPointError("A disastrous floating point error has occurred")
teraz wyjście jest (usunąłem część traceback dla jasności):
Traceback (most recent call last):
File "use_simple.py”, line 18, in side_effect_function
raise FloatingPointError("A disastrous floating point error has occurred”)
FloatingPointError: A disastrous floating point error has occurred
Learning point 3: aby zrobić więcej niż tylko zmienić wartość return_value MagicMock, Ustaw side_effect.
wyśmiewanie klas
do tej pory właśnie wyśmiewaliśmy funkcję, ale co z klasą?, Zdefiniujmy klasę w simple.py wywołana metodą explode:
class SimpleClass(object):
def explode(self):
return "KABOOM!"
teraz w use_simple.py napiszmy funkcję, która używa SimpleClass, a następnie wywołamy tę funkcję:
def use_simple_class():
inst = simple.SimpleClass()
print(inst.explode())use_simple_class()
Po uruchomieniu wyjście to:
KABOOM!
teraz zdefiniujmy drugą funkcję i wybierzmy makietę całej simple_class za pomocą @mock.patch decorator:
@mock.patch("simple.SimpleClass")
def mock_simple_class(mock_class):
jak podczas wyśmiewania funkcji, @mock.,patch decorator przekazuje obiekt MagicMock, który zastępuje klasę, którą wyśmiewasz, w funkcję, którą dekoruje. Obiekt MagicMock w tym przypadku jest przypisany do argumentu mock_class.
na razie po prostu wydrukuję obiekt MagicMock, a następnie uruchomię funkcję:
@mock.patch("simple.SimpleClass")
def mock_simple_class(mock_class):
print(mock_class)mock_simple_class()
Po uruchomieniu wyjście to:
<MagicMock name='SimpleClass'>
tak jak w przypadku wyśmiewania funkcji obiekt MagicMock jest tworzony i przekazywany., Teraz pozwala wydrukować SimpleClass tak, że można zobaczyć, że został wyśmiany i zastąpiony przez ten sam obiekt MagicMock:
@mock.patch("simple.SimpleClass")
def mock_simple_class(mock_class):
print(mock_class)
print(simple.SimpleClass)
wyjście jest teraz:
<MagicMock name='SimpleClass'>
<MagicMock name='SimpleClass'>
Utwórz instancję SimpleClass, tzn. wywołaj SimpleClass (), a następnie wydrukuj ją. Pamiętaj, że ponieważ wyśmiewaliśmy SimpleClass, w rzeczywistości będzie to, że będziemy wywoływać obiekt MagicMock jak funkcję:
Wyjście To:
więc zgodnie z oczekiwaniami, wywołanie SimpleClass() i wywołanie obiektu MagicMock jako funkcji tworzy nowy obiekt MagicMock.,
do tej pory wyśmiewanie klasy było podobne do wyśmiewania funkcji. Jednak w rzeczywistości, kiedy zdefiniujemy klasę, będziemy wtedy tworzyć obiekty za pomocą tej klasy i to właśnie te obiekty częściej niż nie chcemy wyśmiewać. Pytanie brzmi, jak wyśmiewać instancję stworzoną z klasy? Cóż, kiedy naśladujesz klasę, A w konsekwencji tworzony jest obiekt MagicMock, jeśli odwołujesz się do return_value na tym obiekcie MagicMock, tworzy on nowy obiekt MagicMock, który reprezentuje instancję utworzonej klasy. Cholera! To było półtora zdania! To był zdecydowanie jeden z moich „Aha!,”chwile, ale nawet zapisanie tego nie wyjaśnia. Wydrukujmy return_value, aby zobaczyć, co mam na myśli:
Wyjście To:
trzecia linia jest z wydrukowania instancji utworzonej klasy, a czwarta linia jest z wydrukowania return_value obiektu MagicMock przekazanego do funkcji. Jak widać odnoszą się one do tego samego obiektu MagicMock.
punkt 4: gdy wyśmiewasz klasę, tworzony jest obiekt MagicMock. Podczas tworzenia instancji tej klasy tworzony jest nowy obiekt MagicMock., Gdy odwołujesz się do return_value obiektu MagicMock tej klasy, otrzymujesz ten sam obiekt MagicMock, który został utworzony podczas tworzenia instancji tej klasy.
” Ahhhhh, dlaczego to takie ważne?!?!?”Słyszę, jak płaczesz!
„to ważne, jeśli chcesz wyśmiewać funkcję explode”, w zamian za to krzyczę!
następne pytanie, które musisz zadać, brzmi: czy metoda, którą chcę wyśmiewać, jest metodą klasy czy metodą instancji? Ponieważ sposób zmiany return_value metody będzie różny w zależności od odpowiedzi.,
Jeśli metoda była metodą klasową, to ustawiłbyś return_value w następujący sposób:
MagicMockObject.ClassMethodName.return_value = "A delightful return value"
ale jeśli byłaby to metoda instancji, wykonałbyś:
MagicMockObject.return_value.InstanceMethodName.return_value = "A daring return value"
Mając to na uwadze, zmienimy return_value explode, która jest metodą instancji (usunę większość z nich). Drukuj instrukcje dla jasności):
w powyższym kodzie mock_class.return_value zwraca obiekt MagicMock, który reprezentuje instancję SimpleClass.
mock_class.return_value.,explode zwraca obiekt MagicMock, który reprezentuje metodę explode instancji SimpleClass.
dlatego ustawia wartość return_value mock_class.return_value.explode ustawia return_value metody explode instancji SimpleClass.
Wyjście To:
BOO!
podobnie jak podczas wyśmiewania funkcji, możesz ustawić side_effect dla metod klas i obiektów.
mam nadzieję, że ten artykuł pomógł Ci zrozumieć. Poleć go, jeśli ci się podobało i nie krępuj się zostawić odpowiedź, ponieważ chciałbym usłyszeć od Ciebie.