este doar un fapt de viață, pe măsură ce codul crește în cele din urmă, va trebui să începeți să adăugați batjocuri în suita dvs. de testare. Ceea ce a început ca un proiect drăguț cu două clase este acum să vorbești cu Serviciile Externe și nu îl mai poți testa confortabil.
De aceea Python livrează cu unittest.mock
, o parte puternică a Bibliotecii standard pentru dependențele de stubbing și efectele secundare batjocoritoare.
cu toate acestea,unittest.mock
nu este deosebit de intuitiv.,m-am trezit de multe ori întrebându-mă de ce rețeta mea nu funcționează pentru un anumit caz, așa că am pus împreună această cheatsheet pentru a mă ajuta pe mine și pe alții să-și bată joc de lucru rapid.
puteți găsi exemplele de cod în depozitul Github al articolului. Voi folosi Python 3.6, dacă utilizați 3.2 sau mai jos, va trebui să utilizați pachetul mock
PyPI.,
exemplele sunt scrise folosind unittest.TestCase
cursuri pentru simplitate în executarea ei, fără dependențe, dar ai putea să le scrii ca funcții folosind pytest
aproape direct,unittest.mock
va funcționa bine. Dacă sunteți un pytest
utilizator, deși vă încurajez să aibă o privire la excelent pytest-mock
bibliotecă.
punctul central al modulului unittest.mock
este, desigur, clasa Mock
., Principala caracteristica a unui Mock
obiectul este că va reveni un alt Mock
exemplu atunci când:
- accesarea unul dintre atributele sale
- apelarea obiect în sine
Acesta este comportamentul implicit, dar poate fi suprascrisă în moduri diferite. De exemplu, puteți atribui o valoare unui atribut în Mock
prin:
- atribuiți-l direct, așa cum ați face cu orice obiect Python.,
- utilizați metoda
configure_mock
pe o instanță. - sau treceți argumentele cuvintelor cheie la clasa
Mock
la creare.
Pentru a trece peste telefoane la bat joc va trebui să configurați return_value
proprietate, de asemenea, disponibil ca un cuvânt cheie argument în Mock
inițializare., Mock
va întoarce întotdeauna aceeași valoare pe toate apelurile, acest lucru, din nou, de asemenea, poate fi configurat cu ajutorul side_effect
atribute:
- dacă ai vrea să se întoarcă valori diferite pe fiecare apel aveți posibilitatea să atribuiți o iterable să
side_effect
. - dacă doriți să ridicați o excepție atunci când apelați macheta, puteți pur și simplu să atribuiți obiectul excepție la
side_effect
.,
Cu toate aceste instrumente ne pot crea acum stubs pentru practic orice obiect Python, care va lucra mare pentru intrări în sistemul nostru. Dar cum rămâne cu ieșirile?dacă faceți un program CLI pentru a descărca întregul Internet, probabil că nu doriți să descărcați întregul Internet la fiecare test. În schimb, ar fi suficient să afirmăm că requests.download_internet
(nu o metodă reală) a fost numită în mod corespunzător. Mock
vă oferă metode utile pentru a face acest lucru.,
Notă în exemplul nostru assert_called_once
nu a reușit, aceasta prezintă un alt aspect cheie al Mock
obiecte, ele înregistrează toate interacțiunile cu ei și apoi puteți inspecta aceste interacțiuni.
De exemplu, puteți utiliza call_count
pentru a prelua numărul de apeluri la Mock
, și de a folosi call_args
sau call_args_list
pentru a inspecta argumente pentru ultimul sau toate apelurile respectiv.,
dacă acest lucru este incomod în orice moment, puteți utiliza metoda reset_mock
pentru a șterge interacțiunile înregistrate, rețineți că configurația nu va fi resetată, ci doar interacțiunile.
în cele din Urmă, permiteți-mi să introducă MagicMock
, o subclasă de Mock
care implementează default magie sau dunder metode. Acest lucru face ca MagicMock
să fie ideal pentru a batjocori comportamentul clasei, motiv pentru care este clasa implicită la corecții.,
acum suntem gata să începem să batjocorim și să izolăm unitatea în teste. Aici sunt cateva retete pentru a păstra în minte:
Patch-uri pe import
principalul mod de a utiliza unittest.mock
este de a patch-uri importurile în modul de testare folosind patch
function.
patch
va intercepta import
situațiilor identificate printr-un șir (mai mult pe aceasta mai târziu), și să se întoarcă un Mock
exemplu, puteți preconfigura folosind tehnicile de care am discutat mai sus.,
Imaginează-ți că vrei pentru a testa aceasta functie foarte simplu:
Notă importam os
și de asteptare getcwd
pentru a obține directorul curent de lucru. Nu vrem să apelăm la testele noastre, deoarece nu este important pentru codul nostru, iar valoarea returnată ar putea diferi între mediile pe care rulează.după cum sa menționat mai sus, trebuie să furnizăm patch
cu un șir care reprezintă importul nostru specific., Nu vrem să furnizeze pur și simplu os.getcwd
deoarece asta ar patch-uri pentru toate modulele, în loc să ne dorim să furnizeze modul în test e import
de os
, adică work.os
. Când modulul este importat patch
va lucra magia și va returna un Mock
în schimb.,
în mod Alternativ, putem folosi decorator versiune de patch
, rețineți acest timp de testare a unui parametru suplimentar: mocked_os
care Mock
este injectat în test.,
patch
va transmite cuvinte cheie argumente Mock
clasa, deci pentru a configura un return_value
pur și simplu adăugați-l ca unul:
Batjocoritor clase
este destul de comună pentru a patch-uri de clase complet sau parțial., Helper
cu "db"
Worker
returnează așteptat calea furnizate de Helper
În scopul de a testa Worker
în izolare completă, avem nevoie pentru a patch-uri tot Helper
clasa:
Notă dublu return_value
în exemplu, pur și simplu, folosind MockHelper.get_path.return_value
nu ar funcționa, deoarece în codul numim get_path
pe un exemplu, nu clasa în sine.,
înlănțuirea sintaxa este ușor confuz, dar amintiți-vă MagicMock
returnează un alt MagicMock
pe apeluri __init__
. Aici suntem configurarea orice fals Helper
cazuri creat de MockHelper
pentru a reveni ceea ce ne-am aștepta la apelurile get_path
care este singura metodă care ne pasă în testul nostru.,
Clasa speccing
O consecință a flexibilității Mock
este că, odată ce ne-am bătut joc de o clasă Python nu va ridica AttributeError
pur și simplu se va întoarce de noi cazuri de MagicMock
pentru, practic, totul. Acesta este de obicei un lucru bun, dar poate duce la un comportament confuz și potențial bug-uri. De exemplu, scris următorul test,
va trece în tăcere, fără niciun avertisment, lipsește complet greșeală de scriere în assrt_called_once
.,
în Plus, dacă am fost pentru a redenumi Helper.get_path
și Helper.get_folder
, dar uitați să actualizați apel în Worker
testele noastre va trece în continuare:
din Fericire Mock
vine cu un instrument pentru a preveni aceste erori, speccing.
pune pur și simplu, preconfigurează batjocura pentru a răspunde doar la metodele care există de fapt în clasa spec., Există mai multe moduri de a defini specificatiile, dar cel mai simplu este de a trece pur și simplu autospec=True
la patch
apel, care va configura Mock
să se comporte ca obiect fiind batjocorit, ridicarea de excepții pentru lipsă atribute și incorecte de semnături necesare., De exemplu:
Parțială clasa bate joc
Dacă ești mai puțin înclinat să-și testare în izolare completă, puteți, de asemenea, parțial patch o clasă folosind patch.object
:
Aici patch.object
ne permite să configurați o batjocorit versiune de get_path
doar, lăsând restul de comportament neatins., Desigur, acest lucru înseamnă că testul nu mai este ceea ce ați considera strict un test de unitate, dar este posibil să fiți în regulă cu asta.
batjocorirea funcțiilor încorporate și a variabilelor de mediu
în exemplele anterioare am neglijat să testăm un aspect particular al clasei noastre simple, apelul print
. Dacă în contextul aplicației dvs. acest lucru este important, cum ar fi o comandă CLI, de exemplu, trebuie să facem afirmații împotriva acestui apel.,
print
este, desigur, o funcție încorporată în Python, ceea ce înseamnă că nu trebuie să o importăm în modulul nostru, ceea ce contravine celor discutate mai sus despre patch-uri la import., Încă imaginați-vă că am avut puțin mai complicat versiune de funcția noastră:
putem scrie un test, cum ar fi următoarele:
Rețineți câteva lucruri:
- putem bate joc
print
cu nici o problemă și afirmând că a fost un apel urmând „patch-uri pe import” regula. Aceasta a fost totuși o schimbare introdusă în 3.,5, anterior, ai nevoie pentru a adăugacreate=True
lapatch
suna pentru a semnalaunittest.mock
pentru a crea unMock
chiar dacă nu importa meciuri de identificare. - folosim
patch.dict
pentru a injecta o temporară variabilă de mediu înos.environ
, asta este extensibil pentru orice alte dicționar ne-ar plăcea să-și bată joc., - suntem mai multe cuiburi
patch
contextul manager apeluri, dar numai folosindas
în primul deoarece este cel de care avem nevoie pentru a utiliza pentru a numiassert_called_once_with
.,
Daca nu esti pasionat de cuiburi context manageri de asemenea, puteți scrie patch
apeluri în decorator de forma:
Notă cu toate acestea, ordinea de argumente pentru a meciurilor test ordinea de stivuire de decoratori, și, de asemenea, că patch.dict
nu injecta un argument.,acest scenariu foarte comun:
Ai putea, desigur, se adaugă o prindere actuale de fișiere, dar în lumea reală cazuri s-ar putea să nu fie o opțiune, în loc să ne putem bate joc de contextul manager de ieșire să fie un StringIO
obiect:
Nu e nimic special aici, cu excepția magic __enter__
metoda, trebuie doar să-și amintească de fond al sistemului mecanica de context, managerii și unele inteligent de a ne folosi de încredere MagicMock
.,
atributele clasei batjocoritoare
există multe moduri în care se poate realiza acest lucru, dar unele sunt mai prost decât altele. Spune că ai scris următorul cod:
Ai putea testa codul fără orice își bate joc în două moduri:
- în Cazul în care codul de testare în conformitate accesează atribut prin
self.ATTRIBUTE
, care este situația în acest exemplu, puteți seta pur și simplu atribut direct în instanță. Acest lucru este destul de sigur, deoarece schimbarea este limitată la această singură instanță., - alternativ, puteți seta, de asemenea, atributul în clasa importată în test înainte de a crea o instanță. Totuși, acest lucru schimbă atributul de clasă pe care l-ați importat în test, ceea ce ar putea afecta următoarele teste, deci va trebui să vă amintiți să îl resetați.
principalul dezavantaj al acestei abordări non-Mock este că, dacă în orice moment redenumiți atributul, testele dvs. vor eșua și eroarea nu va indica direct această nepotrivire de denumire.,
pentru a rezolva că putem face uz de patch.object
pe clasa importate care va plânge în cazul în care clasa nu are atributul specificat.
Aici sunt unele teste folosind fiecare tehnica:
Batjocoritor clasa ajutoare
următorul exemplu este rădăcina multor probleme în ceea ce privește monkeypatching folosind Machete. De obicei, apare pe codebases mai mature care încep să utilizeze cadre și ajutoare la definirea clasei., De exemplu, imaginați-vă acest ipotetic Field
clasa helper:
scopul Său este de a încheia și de a spori un atribut într-o altă clasă, un model destul de frecvent s-ar putea vedea în a implementa reforme sau forma biblioteci. Nu te preocupa prea mult cu detaliile doar rețineți că există o valoaretype
șidefault
trecut în.,
Acum, ia celălalt eșantion de cod de producție:
Această clasă face uz de Field
clasa prin definirea acesteia country
atribut ca fiind unul al cărui tip este str
și default
ca primul element COUNTRIES
constanta. Logica testată este că, în funcție de țară, se aplică o reducere.,
Pentru care am putea scrie următorul test:
Dar asta NU ar trece.,
Să mergem prin testul:
- Primul patch-uri implicit țări în
pricer
să fie o listă cu o singură intrareGB
, - Acest lucru ar trebui să facă
CountryPricer.country
atribut implicit la intrare, deoarece este definiția includedefault=COUNTRIES
, - atunci instanciates
CountryPricer
și cere prețul redus pentruGB
!
deci, ce se întâmplă?,
cauza principala a acestui fapt constă în Python comportamentul în timpul importului, cel mai bine descris în Luciano Ramalho este excelent Fluent Python pe capitolul 21:
Pentru clasele, povestea este diferită: la import de timp, interpretul execută corpul de fiecare clasa, chiar corpul de clase imbricate în alte clase. Executarea unui corp de clasă înseamnă că atributele și metodele clasei sunt definite, iar apoi obiectul clasei în sine este construit.,
Aplică acest lucru la exemplul nostru:
-
country
atributuluiField
exemplu este construit înainte de test este fugit la import de timp, - Python citește corpul de clasa, trecerea
COUNTRIES
care este definit în acel moment laField
exemplu - testul Nostru cod ruleaza dar e prea târziu pentru noi să patch-uri
COUNTRIES
și a obține o corectă afirmația.,
Din descrierea de mai sus s-ar putea încerca amânarea importul de module până în teste, ceva de genul:
Acest lucru, cu toate acestea, încă nu va trece ca mock.patch
va importa modulul și apoi monkeypatch, rezultând în aceeași situație ca înainte.,
Pentru a lucra în jurul valorii de acest lucru trebuie să ne bucurăm de starea de CountryPricer
clasa la momentul testului și patch-uri default
pe deja inițializat Field
exemplu:
Această soluție nu este ideal, deoarece necesită cunoștințe de interne ale Field
care poate nu au sau nu doresc să folosească dacă utilizați o bibliotecă extern dar se lucrează la acest caz simplificat, desigur.,
această problemă de timp de import este una dintre principalele probleme pe care le-am întâmpinat în timpul utilizării unittest.mock
. Trebuie să vă amintiți în timp ce îl utilizați că la momentul importării codul de la nivelul superior al modulelor este executat, inclusiv corpurile de clasă.
dacă logica pe care o testați depinde de oricare dintre aceste logici, poate fi necesar să regândiți modul în care utilizați patch
în consecință.,
Ambalaj
Această introducere și cheatsheet ar trebui să îți bați joc de cu unittest.mock
, dar am să vă încurajez să citiți documentația bine, exista o multime de mai interesante trucuri pentru a învăța.
este de remarcat faptul că există alternative la unittest.mock
, în special Alex Gaynor e pretend
bibliotecă în combinație cu pytest
‘s monkeypatch
prindere. După cum subliniază Alex, folosind aceste două biblioteci, puteți adopta o abordare diferită pentru a face batjocura mai strictă și mai previzibilă., Cu siguranță o abordare care merită explorată, dar în afara domeniului de aplicare al acestui articol.
unittest.mock
este în prezent standardul pentru batjocură în Python și îl veți găsi în aproape fiecare codebase. Având o înțelegere solidă a acesteia vă va ajuta să scrieți teste mai bune mai repede.