je to jen fakt života, protože kód roste nakonec budete muset začít přidávat posměšky do testovací sady. To, co začalo jako roztomilý malý projekt dvou tříd, nyní mluví s externími službami a už ho nemůžete pohodlně otestovat.
to je důvod, proč Python lodě s unittest.mock
, výkonný součástí standardní knihovny pro klučky závislostí a zesměšňovat vedlejší účinky.
unittest.mock
však není příliš intuitivní.,
našla jsem mnohokrát přemýšlel, proč můj recept nefunguje pro konkrétní případ, tak jsem dal dohromady tento taháku pomoci sobě a ostatním se vysmívá pracovat rychle.
příklady kódu najdete v úložišti GitHub v článku. Budu používat Python 3.6, pokud používáte 3.2 nebo níže, budete muset použít balíček mock
PyPI.,
příklady jsou psány pomocí unittest.TestCase
kurzy pro jednoduchost v provedení bez závislostí, ale můžete je zapsat jako funkce pomocí pytest
téměř přímo,unittest.mock
bude fungovat v pohodě. Pokud jste uživatel pytest
, doporučuji vám podívat se na vynikající knihovnu pytest-mock
.
středový bod modulu unittest.mock
je samozřejmě třída Mock
., Hlavní charakteristikou Mock
objektu je, že se vrátí další Mock
například, když:
- přístup k jedné z jeho atributy
- volání samotný objekt
Toto je výchozí chování, ale to může být přepsána v různými způsoby. Například můžete přiřadit hodnotu atributu v Mock
od:
- Přiřadit přímo, jako byste dělat s žádnou Python objektu.,
- použijte metodu
configure_mock
na instanci. - nebo předejte argumenty klíčových slov do třídy
Mock
při vytváření.
přepsat volání do makety musíte konfigurovat jeho return_value
majetek, k dispozici také jako klíčové slovo argument v Mock
inicializátor., Mock
bude vždy vrátit stejnou hodnotu pro všechny hovory, znovu, to může také být nakonfigurován tak pomocí side_effect
atribut:
- pokud chcete vrátit jiné hodnoty na každé volání můžete přiřadit iterable
side_effect
. - Pokud chcete při volání makety vyvolat výjimku, můžete objekt výjimky jednoduše přiřadit
side_effect
.,
všechny tyto nástroje nyní můžeme vytvořit pahýly pro v podstatě jakýkoli Python objekt, který bude pracovat skvělé pro vstupy do našeho systému. Ale co výstupy?
Pokud vytváříte program CLI ke stažení celého Internetu, pravděpodobně nechcete stahovat celý Internet na každém testu. Místo toho by stačilo tvrdit, žerequests.download_internet
(nikoli skutečná metoda) byla volána vhodně. Mock
vám dává užitečné metody.,
Poznámka: v našem příkladu assert_called_once
nepodařilo, to vitríny další klíčový aspekt Mock
objekty, zaznamenávat všechny interakce s nimi, a pak můžete prohlédnout tyto interakce.
například, můžete použít call_count
načíst počet volání na Mock
a pomocí call_args
nebo call_args_list
zkontrolujte, zda nejsou argumenty do posledního nebo všech hovorů v tomto pořadí.,
Pokud je to nevhodné v každém okamžiku můžete použít reset_mock
metoda k vymazání zaznamenaných interakcí, poznámka: konfigurace nebude resetovat, jen interakce.
a Konečně, dovolte mi představit MagicMock
, podtřídu Mock
, který implementuje výchozí magie nebo dunder metody. Díky tomu je MagicMock
ideální pro zesměšňování chování třídy, a proto je to výchozí třída při záplatování.,
nyní jsme připraveni začít zesměšňovat a izolovat jednotku pod testy. Zde je několik receptů, aby mějte na paměti:
Náplasti na dovozní
hlavní způsob, jak používat unittest.mock
je patch dovoz v modulu v rámci testu pomocí patch
funkce.
patch
budou zachytit import
prohlášení identifikovány řetězec (více o tom později), a vrátit Mock
například můžete nakonfigurovat pomocí technik, které jsme diskutovali výše.,
Představte si, že chceme vyzkoušet tento velmi jednoduchý funkce:
Všimněte si, že jsme import os
volat getcwd
získat aktuální pracovní adresář. Nechceme to skutečně nazvat na našich testech, protože to není důležité pro náš kód a návratová hodnota se může lišit mezi prostředími, na kterých běží.
jak je uvedeno výše, musíme dodatpatch
řetězcem představujícím náš konkrétní import., Nechceme, aby nabídky jednoduše os.getcwd
vzhledem k tomu, že by patch pro všechny moduly, namísto toho se chceme dodat modul v rámci testu je import
os
, např. work.os
. Když je modul importován patch
bude pracovat své kouzlo a vrátit Mock
místo.,
Alternativně, můžeme použít dekoratér verze patch
poznámka: tento test má navíc parametr: mocked_os
, který Mock
je vstřikován do testu.,
patch
předá klíčové slovo argumenty Mock
třídy, takže konfigurace return_value
jednoduše přidat jako jednu:
Zesměšňovat třídy
je To docela běžné opravy tříd zcela nebo částečně., Helper
"db"
Worker
vrací očekávané cestě poskytnuté Helper
Za účelem testování Worker
v úplné izolaci potřebujeme na opravu celé Helper
třídy:
Poznámka: double return_value
v příkladu, jednoduše pomocí MockHelper.get_path.return_value
nebude fungovat, protože v kódu: get_path
na instanci, ne samotné třídy.,
řetězení syntaxe je mírně matoucí, ale pamatujte si, MagicMock
vrací další MagicMock
hovory __init__
. Tady jsme konfiguraci žádné falešné Helper
instance vytvořené MockHelper
vrátit to, co očekáváme, že na volání do get_path
což je jediný způsob, staráme se o tom v našem testu.,
Class speccing
o důsledek flexibility, Mock
je, že jakmile jsme se posmívali třídy Python nebude zvyšovat AttributeError
tak to prostě vrátí nové instance MagicMock
pro v podstatě všechno. To je obvykle dobrá věc, ale může vést k nějaké matoucí chování a potenciálně chyby. Například psaní následující test,
bude tiše projít bez varování zcela chybí překlep v assrt_called_once
.,
Navíc, pokud bychom měli přejmenovat Helper.get_path
Helper.get_folder
, ale nezapomeňte aktualizovat volání v Worker
naše testy budou ještě projít:
a Naštěstí Mock
přichází s nástrojem, aby se zabránilo tyto chyby, speccing.
jednoduše řečeno, předkonfiguruje zesměšňování, aby reagovalo pouze na metody, které skutečně existují ve třídě spec., Existuje několik způsobů, jak definovat specifikace, ale nejjednodušší je jednoduše složit autospec=True
patch
hovor, který se bude konfigurovat Mock
chovat jako objektu, který je terčem posměchu, získávání výjimky pro chybějící atributy a nesprávné podpisy podle potřeby., Například:
Částečné třídy zesměšňovat
Pokud jste méně náchylní k testování v naprosté izolaci můžete také částečně patch třídy pomocí patch.object
:
Zde patch.object
což nám umožňuje konfigurovat posmívali verze get_path
, přičemž zbytek chování beze změny., Samozřejmě to znamená, že test již není to, co byste striktně zvažovali test jednotky, ale s tím můžete být v pořádku.
Zesměšňovat vestavěné funkce a proměnné prostředí
V předchozích příkladech jsme zanedbali test jeden konkrétní aspekt naší jednoduché třídy, print
volání. Pokud je to v kontextu vaší aplikace důležité, například příkaz CLI, musíme proti tomuto volání vznést tvrzení.,
print
je, samozřejmě zabudovanou funkci v Pythonu, což znamená, že není třeba importovat na náš modul, který jde proti tomu, co bylo uvedeno výše o záplatování na import., Ještě představte si, měli jsme to trochu složitější verze naší funkce
můžeme napsat test, jako je následující:
Všimněte si pár věcí:
- můžeme vysmívat
print
s tím žádný problém a tvrdí, tam byl hovor, v návaznosti na „patch na import“ pravidlo. Jednalo se však o změnu zavedenou ve 3.,5, dříve jste potřebovali přidatcreate=True
patch
volání pro signálunittest.mock
vytvořitMock
i když žádné dovozní odpovídá identifikátor. - jsme pomocí
patch.dict
aplikovat dočasné proměnné prostředí vos.environ
, to je rozšiřitelný do jiných slovník bychom chtěli zesměšňovat., - jsme hnízdění několika
patch
context manager hovorů, ale pouze pomocías
v první z nich, protože to je jediná musíme použít k voláníassert_called_once_with
.,
Pokud si nejste rádi hnízdí kontextu manažeři také můžete napsat patch
hovory v dekoratér formy:
Všimněte si však pořadí argumentů k testu odpovídá pořadí překrývání malíři, a také to, že patch.dict
nemá aplikovat argument.,tento velmi běžný scénář:
můžete, samozřejmě, přidat skutečné svítidlo souboru, ale v reálném světě případech nemusí být možnost, místo toho můžeme vysmívat souvislosti manager je výstup StringIO
předmět:
není nic zvláštního, kromě magie __enter__
metoda, jen musíme mít na paměti základní mechaniky z kontextu manažery a některé chytré využití našeho věrného MagicMock
.,
zesměšňující atributy třídy
existuje mnoho způsobů, jak toho dosáhnout, ale některé jsou více bláznivé, že ostatní. Že jste napsal následující kód:
můžete otestovat kód bez jakékoliv vysmívá dvěma způsoby:
- Pokud kód v rámci testu přistupuje k atributu pomocí
self.ATTRIBUTE
, což je případ v tomto příkladu, můžete jednoduše nastavit atribut přímo v instanci. To je poměrně bezpečné, protože změna je omezena na tuto jedinou instanci., - alternativně můžete také nastavit atribut v importované třídě v testu před vytvořením instance. To se však změní atribut class jste dovezené v testu, které by mohly mít vliv na následující testy, takže si musíte pamatovat, aby jej resetovat.
hlavní nevýhodou z tohoto non-Mock přístupů je, že pokud budete kdykoliv přejmenovat atribut vaše testy selžou a chyba nebude přímo poukázat toto pojmenování nesoulad.,
k vyřešení, že můžeme použít patch.object
na importované třídě, která si bude stěžovat, pokud třída nemá zadaný atribut.
Zde jsou některé testy, pomocí každé techniky:
Zesměšňovat třídy pomocníci
následující příklad je kořen mnoha problémů, pokud jde monkeypatching pomocí Mock. Obvykle se objevuje na vyspělejších kódových základech, které začínají využívat rámce a pomocníky při definici třídy., Například, představte si tuto hypotetickou Field
class helper:
Jeho účelem je zabalit a zvýšit atribut v jiné třídě, poměrně vzor jste běžně mohli vidět v ORMs nebo formuláře knihovny. Nedělejte si příliš starosti s jeho podrobnostmi, stačí si uvědomit, že existuje type
a default
hodnota předána.,
Teď vzít další vzorek výrobní kód:
Tato třída využívá Field
třídy určením jeho country
atribut jako ten, jehož typ je str
default
jako první prvek COUNTRIES
konstantní. Testovaná logika spočívá v tom, že v závislosti na zemi se uplatňuje sleva.,
Pro který můžeme napsat následující test:
Ale to by NEMĚLO projít.,
Pojďme se projít test:
- První to skvrny výchozí zemí v
pricer
seznam s jedním vstupemGB
, - To by mělo
CountryPricer.country
atribut default na ten vstup, protože to je definice zahrnujedefault=COUNTRIES
- To pak instanciates
CountryPricer
a žádá za zvýhodněnou cenu proGB
!
tak co se děje?,
příčinou tohoto spočívá v Pythonu chování při importu, nejlepší je popsáno v Luciano Ramalho je vynikající Plynulý Python na kapitole 21:
Pro třídy, příběh je jiný: při dovozu čas, tlumočník vykonává tělo každé třídě, i tělo třídy vnořené v jiných třídách. Provedení těla třídy znamená, že jsou definovány atributy a metody třídy a pak je vytvořen objekt třídy.,
Použití toto na našem příkladu:
-
country
atributField
instance je postaven před zkouškou se běžel na čas importu, - Python přečte tělo třídy, absolvování
COUNTRIES
, který je definován v tomto boděField
instance - Naše testovací kód běží, ale je příliš pozdě pro nás chcete-patch
COUNTRIES
a získat správné tvrzení.,
Z výše uvedeného popisu, můžete zkusit zpoždění import modulu, dokud uvnitř zkoušky, něco jako:
Toto, nicméně, bude to stále ještě není předat jako mock.patch
importovat modul a pak monkeypatch, což má za následek stejné situaci jako předtím.,
Chcete-li obejít toto musíme přijmout stav CountryPricer
třídy v době testu a patch default
na již inicializován Field
příklad:
Toto řešení není ideální, protože to vyžaduje znalosti z niternosti Field
, kterou vy nemáte, nebo chcete použít, pokud používáte externí knihovnu, ale funguje to na tento poněkud zjednodušený případ.,
tento problém s časem importu je jedním z hlavních problémů, se kterými jsem se setkal při použití unittest.mock
. Při používání je třeba si uvědomit, že při importu je spuštěn kód na nejvyšší úrovni modulů, včetně třídních orgánů.
Pokud je logika, kterou testujete, závislá na kterékoli z této logiky, možná budete muset přehodnotit, jak používáte patch
.,
Balení
Tento úvod a cheatsheet by se vysmíváš se s unittest.mock
, ale doporučuji si přečíst dokumentaci důkladně, je tam spousta další zajímavé triky naučit.
je To stojí za zmínku, že tam jsou alternativy k programu unittest.mock
, zejména Alex Gaynor je pretend
knihovny v kombinaci s pytest
‚monkeypatch
svítidlo. Jak Alex zdůrazňuje, pomocí těchto dvou knihoven můžete zaujmout jiný přístup, aby se zesměšňování stalo ještě více předvídatelným., Rozhodně přístup stojí za prozkoumání, ale mimo rozsah tohoto článku.
unittest.mock
je v současné době standard pro zesměšňovat v Pythonu a najdete ho prakticky v každé codebase. Mít solidní pochopení toho vám pomůže psát lepší testy rychleji.