Wprowadzenie do Mocking w Pythonie

jak uruchamiać testy jednostkowe w Pythonie bez testowania swojej cierpliwości

najczęściej pisane przez nas oprogramowanie współdziała bezpośrednio z tym, co nazwalibyśmy „brudnymi” usługami. W kategoriach laika: usługi, które są kluczowe dla naszej aplikacji, ale których interakcje mają zamierzone, ale niepożądane skutki uboczne—to znaczy niepożądane w kontekście autonomicznego uruchomienia testowego.,Facebook to Facebook, ale nie chcemy publikować postów na Facebooku za każdym razem, gdy uruchamiamy nasz zestaw testów.

na przykład: być może piszemy aplikację społecznościową i chcemy przetestować naszą nową funkcję „Publikuj na Facebooku”, ale nie chcemy publikować na Facebooku za każdym razem, gdy uruchamiamy nasz zestaw testów.

Biblioteka Pythonaunittest zawiera podpakiet o nazwieunittest.mock—lub jeśli zadeklarujesz to jako zależność, po prostumock—który zapewnia niezwykle potężne i użyteczne środki, za pomocą których mock i stub z tych niepożądanych efektów ubocznych.,

Uwaga:mock jest nowo dołączona do standardowej biblioteki Pythona 3.3; wcześniejsze dystrybucje będą musiały używać wzorcowej biblioteki do pobrania przez PyPI.

wywołania systemowe a szydzenie w Pythonie

aby dać ci kolejny przykład, z którym będziemy pracować przez resztę artykułu, rozważ wywołania systemowe., Nietrudno zauważyć, że są to prime kandydaci do wyśmiewania się: niezależnie od tego, czy piszesz skrypt do wysuwania napędu CD, serwer WWW, który usuwa przestarzałe pliki pamięci podręcznej z /tmp, czy serwer gniazd, który wiąże się z portem TCP, wywołują one niepożądane efekty uboczne w kontekście testów jednostkowych.

jako programista, zależy Ci bardziej, że biblioteka pomyślnie wywołała funkcję systemową do wyrzucania płyty CD, a nie doświadczając, że taca CD jest otwarta za każdym razem, gdy uruchamiany jest test.,

jako programista bardziej zależy ci na tym, aby Twoja biblioteka pomyślnie wywołała funkcję systemową do wyrzucania płyty CD (z poprawnymi argumentami itp.) w przeciwieństwie do rzeczywistego doświadczania, że taca CD jest otwarta za każdym razem, gdy test jest uruchamiany. (Lub, co gorsza, wiele razy, ponieważ wiele testów odwołuje się do kodu wysuwania podczas jednego testu jednostkowego!)

podobnie, utrzymanie wydajności testów jednostkowych oznacza utrzymanie jak największej ilości „wolnego kodu” z automatycznych uruchomień testów, czyli systemu plików i dostępu do sieci.,

w naszym pierwszym przykładzie zmienimy standardowy przypadek testowy Pythona z oryginalnego formularza na jeden, używając mock. Pokażemy, jak pisanie przypadku testowego z mocks sprawi, że nasze testy będą mądrzejsze, szybsze i będą w stanie ujawnić więcej o tym, jak działa oprogramowanie.

Prosta funkcja usuwania

wszyscy musimy od czasu do czasu usuwać pliki z naszego systemu plików, więc napiszmy funkcję w Pythonie, która ułatwi to naszym skryptom.,

#!/usr/bin/env python# -*- coding: utf-8 -*-import osdef rm(filename): os.remove(filename)

oczywiście nasza metodarm w tym momencie nie zapewnia znacznie więcej niż podstawowa metodaos.remove, ale nasza baza kodowa ulegnie poprawie, umożliwiając nam dodanie większej funkcjonalności tutaj.

napiszmy tradycyjny przypadek testowy, tzn. bez moków:

Nasz przypadek testowy jest dość prosty, ale za każdym razem, gdy jest uruchamiany, tworzony jest plik tymczasowy, a następnie usuwany., Dodatkowo, nie mamy możliwości sprawdzenia, czy nasza metoda rm poprawnie przekazuje argument do wywołania os.remove. Możemy założyć, że tak jest na podstawie powyższego testu, ale wiele pozostaje do życzenia.

Refaktoryzacja za pomocą Pythona wyśmiewa

refaktoryzacja naszego przypadku testowego za pomocąmock:

dzięki tym refaktoryzacjom zasadniczo zmieniliśmy sposób działania testu. Teraz mamy insider, obiekt, którego możemy użyć, aby zweryfikować funkcjonalność innego.,

potencjalne pułapki wyśmiewania Pythona

jedną z pierwszych rzeczy, które powinny się wyróżniać, jest to, że używamy dekoratora metodymock.patch do makiety obiektu znajdującego się wmymodule.os I wstrzykiwamy tę makietę do naszej metody przypadku testowego. Czy nie byłoby bardziej sensowne, aby po prostu wyśmiewać os, zamiast odwoływać się do niego w mymodule.os?

Cóż, Python jest nieco podstępnym wężem, jeśli chodzi o importowanie i Zarządzanie modułami., W czasie wykonywania mymodule moduł ma swój własnyos, który jest importowany do własnego zakresu lokalnego w module. Tak więc, jeśli zrobimy makietę os, nie zobaczymy efektów makiety w modulemymodule.

mantra do powtarzania jest następująca:

Mock elementu, w którym jest używany, a nie skąd pochodzi.,

Jeśli chcesz zrobić makietę modułutempfiledlamyproject.app.MyElaborateClass, prawdopodobnie musisz zastosować makietę domyproject.app.tempfile, ponieważ każdy moduł zachowuje swój własny import.

z tą pułapką na drodze, kpij dalej.

dodanie walidacji do 'rm'

zdefiniowana wcześniej metodarm jest dość uproszczona. Chcemy sprawdzić, czy ścieżka istnieje i jest plikiem, zanim po prostu ślepo spróbujemy ją usunąć., Niech refactor rm aby być nieco mądrzejszy:

#!/usr/bin/env python# -*- coding: utf-8 -*-import osimport os.pathdef rm(filename): if os.path.isfile(filename): os.remove(filename)

świetnie. A teraz dostosujmy naszą sprawę testową, aby utrzymać zasięg.

nasz paradygmat testowania całkowicie się zmienił. Teraz możemy zweryfikować i zweryfikować wewnętrzną funkcjonalność metod bez żadnych skutków ubocznych.

usuwanie plików jako usługa z Mock Patch

do tej pory pracowaliśmy tylko z dostarczaniem Mock ' ów dla funkcji, ale nie dla metod na obiektach i przypadkach, w których mocking jest niezbędny do wysyłania parametrów. Najpierw omówmy metody obiektowe.,

zaczniemy od refaktoringu metodyrm do klasy service. Tak naprawdę nie ma uzasadnionej potrzeby, aby zamknąć tak prostą funkcję w obiekcie, ale to przynajmniej pomoże nam zademonstrować kluczowe pojęcia w mock. Refactor:

zauważysz, że niewiele się zmieniło w naszym przypadku testowym:

świetnie, więc teraz wiemy, że RemovalService działa zgodnie z planem., Stwórzmy inną usługę, która deklaruje ją jako zależność:

ponieważ mamy już pokrycie testowe naRemovalService, nie będziemy sprawdzać wewnętrznej funkcjonalności metodyrmw naszych testachUploadService. Raczej po prostu przetestujemy (oczywiście bez skutków ubocznych), że UploadService wywołuje metodę RemovalService.rm, którą znamy „just works™” z naszego poprzedniego przypadku testowego.

są dwa sposoby, aby to zrobić:

  1. wykop RemovalService.rm metoda sama w sobie.,
  2. dostarcza wyśmiewaną instancję w konstruktorzeUploadService.

ponieważ obie metody są często ważne w testowaniu jednostkowym, przejrzymy obie.

Opcja 1: Mocking Instance Methods

biblioteka mock ma specjalny dekorator metod do mockingu metod i właściwości instancji obiektu, @mock.patch.object dekorator:

świetnie! Potwierdziliśmy, że UploadService pomyślnie wywołuje metodę rm. Zauważyłeś coś ciekawego?, Mechanizm łatania zastąpił rm metodę wszystkich instancji RemovalService w naszej metodzie testowej. Oznacza to, że możemy faktycznie sprawdzić same instancje. Jeśli chcesz zobaczyć więcej, spróbuj upuścić punkt przerwania w swoim szyderczym kodzie, aby dobrze poczuć, jak działa mechanizm łatania.

Mock Patch Pitfall: kolejność dekoratorów

podczas używania wielu dekoratorów w metodach testowych kolejność jest ważna i jest trochę myląca. Zasadniczo, podczas mapowania dekoratorów do parametrów metody, wykonaj pracę wstecz., Rozważ ten przykład:

zauważ, jak nasze parametry są dopasowane do odwrotnej kolejności dekoratorów? To częściowo ze względu na sposób, w jaki działa Python. W przypadku dekoratorów wielu metod, oto kolejność wykonania w pseudokodzie:

patch_sys(patch_os(patch_os_path(test_something)))

ponieważ łata do sys jest najbardziej oddaloną łatą, zostanie wykonana jako ostatnia, co czyni ją ostatnim parametrem w rzeczywistych argumentach metody testowej. Zwróć na to uwagę i użyj debuggera podczas uruchamiania testów, aby upewnić się, że odpowiednie parametry są wstrzykiwane w odpowiedniej kolejności.,

Opcja 2: Tworzenie przykładowych instancji

zamiast szydzić z konkretnej metody instancji, możemy zamiast tego po prostu dostarczyć wyśmiewaną instancję doUploadService za pomocą jej konstruktora. Wolę opcję 1 powyżej, ponieważ jest o wiele bardziej precyzyjna, ale jest wiele przypadków, w których Opcja 2 może być skuteczna lub konieczna. Powtórzmy nasz test:

w tym przykładzie nie musieliśmy nawet łatać żadnej funkcjonalności, po prostu tworzymy auto-spec dla klasy RemovalService, a następnie wstrzykujemy tę instancję do naszej UploadService, aby zweryfikować funkcjonalność.,

metodamock.create_autospec tworzy funkcjonalnie równoważną instancję do podanej klasy. Oznacza to, praktycznie rzecz biorąc, że gdy zwracana instancja jest interakcyjna, wywoła wyjątki, jeśli zostanie użyta w nielegalny sposób. Dokładniej, jeśli metoda zostanie wywołana z niewłaściwą liczbą argumentów, zostanie podniesiony wyjątek. Jest to niezwykle ważne, ponieważ zdarzają się refaktory. W miarę zmian w bibliotece testy ulegają awarii i tego się oczekuje. Bez użycia auto-spec, nasze testy nadal przechodzą, nawet jeśli podstawowa implementacja jest zepsuta.,

Pułapka: mock.Mock I mock.MagicMock klasy

mock biblioteka zawiera również dwie ważne klasy, na których zbudowana jest większość wewnętrznych funkcji: mock.Mock i mock.MagicMock. Po wybraniu opcji użycia wystąpienia mock.Mock, wystąpienia mock.MagicMock lub auto-spec zawsze preferuje użycie auto-spec, ponieważ pomaga utrzymać testy przy zdrowych zmysłach w przypadku przyszłych zmian., Dzieje się tak dlatego, że mock.Mock I mock.MagicMock akceptują wszystkie wywołania metod i przypisania właściwości niezależnie od bazowego API. Rozważ następujący przypadek użycia:

class Target(object): def apply(value): return valuedef method(target, value): return target.apply(value)

możemy to przetestować za pomocą mock.Mock przykład taki:

ta logika wydaje się rozsądna, ale zmodyfikujmy Target.apply metoda, aby pobrać więcej parametrów:

class Target(object): def apply(value, are_you_sure): if are_you_sure: return value else: return None

uruchom ponownie test, a przekonasz się, że nadal przechodzi. Dzieje się tak dlatego, że nie jest on zbudowany w oparciu o rzeczywiste API., Dlatego zawsze należy używać metody create_autospec oraz parametruautospec z dekoratorami@patch I@patch.object.Facebook Facebook API

aby zakończyć, napiszmy bardziej stosowny przykład Pythona w prawdziwym świecie, o którym wspominaliśmy we wstępie: publikowanie wiadomości na Facebooku.

Napiszemy ładną klasę owijania i odpowiadającą jej sprawę testową.,

oto nasz przypadek testowy, który sprawdza, czy publikujemy wiadomość bez jej wysyłania:

jak widzieliśmy do tej pory, bardzo łatwo jest zacząć pisać mądrzejsze testy z mock w Pythonie.

podsumowanie

Biblioteka Pythonamock, jeśli jest trochę myląca, jest zmieniaczem dla testów jednostkowych. Mamy nadzieję, że ten artykuł pomoże programistom Pythona pokonać początkowe przeszkody i napisać doskonały, przetestowany kod.,

Share

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *