Python batjocoritor nu este ceva neintuitiv despre tine. Nu sunt ușor pentru a ajunge la trântă cu și este nevoie de mai multe „Ah, acum am înțeles!”momente înainte de a fi mai bine înțeles., Sunteți un instrument puternic, fantastic pentru activarea testelor unitare automate, dar știu mulți dezvoltatori care „se descurcă” cu cunoștințele pe care le au și scrierea acestor teste durează mai mult decât ar trebui.
există multe articole despre batjocură și da, acum scriu altul. Cu toate acestea, chiar vreau să am un du-te la explicând în mod clar, în special cele ” Aha!”momente care mi-au împins înțelegerea.
În primul rând, ce este batjocorirea? Iată un citat din documente:
mock este o bibliotecă pentru testarea în Python., Vă permite să înlocuiți părți ale sistemului testat cu obiecte batjocoritoare și să faceți afirmații despre modul în care au fost utilizate.
deci „vă permite să înlocuiți părți ale sistemelor dvs.”, dar ce părți? Se pare că puteți bate joc de aproape orice puteți defini, cum ar fi:
- funcții
- clase
- obiecte
le înlocuiți cu „obiecte mock” care sunt instanțe ale clasei Mock sau MagicMock.,
apoi „faceți afirmații” despre acea instanță batjocoritoare, care este o modalitate de a verifica dacă instanța batjocoritoare a fost folosită în modul în care vă așteptați.
în acest articol voi acoperi elementele de bază ale înlocuirii unor părți ale sistemelor dvs., dar nu voi acoperi afirmațiile.
să începem cu un exemplu simplu. Am de gând pentru a crea un nou modul numit simple.py și în acest modul am de gând să definim o funcție numită simple_function că doar returnează un șir de caractere astfel:
def simple_function():
return "You have called simple_function"
Acum am de gând pentru a crea un al doilea modul numit use_simple.py., În acest modul am de gând să importe mock pachetului de unittest și de import de la modul simplu:
from unittest import mockimport simple
Apoi am de gând să o scrie două funcții, una care necesită simple_function în mod normal și unul care își bate joc de apel la ea. Rețineți că, în mod normal, ați face acest lucru într-un context de testare, dar în mod intenționat îl îndepărtez, astfel încât să mă pot concentra pe partea batjocoritoare. Prima funcție:
def use_simple_function():
result = simple.simple_function()
print(result)use_simple_function()
după Cum puteți vedea doar afiseaza rezultatul simple_function., Mai exact, ieșirea este:
You have called simple_function
pentru a doua funcție, pentru a bate joc simple_function voi folosi macheta.decorator de patch-uri. Acest decorator vă permite să specificați ceea ce doriți să batjocorească prin trecerea unui șir de caractere în formatul ‘pachet.modulul.Numele funcției”., În exemplul nostru nu există nici un pachet, dar modulul este numit simplu și funcția este numit simple_function, deci, decoratorul va arata astfel:
@mock.patch('simple.simple_function')
apoi trebuie să scriem doua funcție ca așa:
@mock.patch('simple.simple_function')
def mock_simple_function():
am numit-o mock_simple_function, dar acesta poate fi numit nimic. Ne lipsește un parametru aici, deoarece atunci când decora o funcție cu @ mock.patch va trece o instanță a clasei MagicMock (un obiect MagicMock) care este folosit pentru a înlocui funcția pe care o batjocoriți., Deci, va arăta astfel:
@mock.patch('simple.simple_function')
def mock_simple_function(mock_simple_func):
deci, specificând @mock.patch („simplu.simple_function’) am zis că vreau să înlocuiți simple_function cu MagicMock obiect atribuit parametru numit mock_simple_func.
Inițial să aflu funcția de imprimare mock_simple_func și apoi spune:
atunci Când a alerga, de ieșire este:
<MagicMock name='simple_function'>
Care este un MagicMock obiect care va fi numit în loc de simple_function., Este important să rețineți că atributul de nume al obiectului este lucrul care este batjocorit, iar id-ul este un număr unic pentru obiect.
Dacă am acum, de asemenea, imprima simple_function (dar nu-l numesc):
Care produce la ieșire:
<MagicMock name='simple_function'>
<MagicMock name='simple_function'>
puteți vedea că de id-uri de potrivire, care mock_simple_func este același MagicMock obiect ca simple_function.până acum atât de bine. Am batjocorit simple_function. Pentru a fi explicit, simple_function a fost batjocorit, deoarece a fost înlocuit cu un obiect MagicMock.,
să adăugăm liniile din prima funcție:
când rulați, ieșirea este:
după cum puteți vedea din a treia linie de ieșire când simple_function este de fapt numit creează un alt obiect MagicMock. Amintiți-vă că de când ne-am bătut joc simple_function, este de fapt un MagicMock obiect, așa că atunci când am apel simple_function ne sunt de fapt de asteptare MagicMock obiect!pentru mine, aici se instalează confuzia.de ce este creat un nou obiect MagicMock?
să facem o pauză pentru un minut pentru a ne uita mai atent la MagicMock.
MagicMock are magie în numele său, deoarece are implementări implicite ale majorității metodelor magice python. Ce este o metodă magică python vă întreb? Este toate funcțiile speciale python care au subliniere dublă la începutul și la sfârșitul numelui lor. Puteți găsi o listă a acestora aici. Cel de interes aici este _ _ call__., Funcția de apel este descris ca:
Numit atunci când un obiect este numit ca o funcție
Deci, dacă o clasă implementează această funcție puteți crea o instanță a clasei, și apoi apel ca instanță ca o funcție anume :
prin Urmare, atunci când MagicMock este numit în funcție, la __call__ funcția este numit. Implementarea MagicMock a acestei funcții creează o nouă machetă!deci, atunci când numim simple_function () o nouă machetă este creată și returnată. Nu numai asta, dar avem conceptul de părinte și copil își bate joc., Când o machetă creează alta devine părintele și cel nou creat copilul. Copilul mock-ar putea crea alte bate joc astfel încât să sfârșesc cu o heirarchy de obiecte machete.
punctul de învățare 1: când un obiect MagicMock este numit ca o funcție, în mod implicit creează și returnează un nou obiect MagicMock.
înapoi la exemplul nostru. Până acum ne-au batjocorit simple_function dar nu a făcut nimic cu ea, altele decât imprima MagicMocks care sunt create ca urmare. Spuneți că am vrut să schimb ceea ce a fost returnat de la simple_function(), cum aș face asta?, Mi-ar face asta folosind return_value proprietate de MagicMock ca asa (eu sunt, de asemenea, va elimina primele două printuri ca acestea nu mai sunt necesare):
atunci Când a alerga, de ieșire este:
You have mocked simple_function
după Cum puteți vedea, atunci când simple_function() este acum numit MagicMock return_value este returnat în loc de a crea un al doilea MagicMock obiect.
punctul de învățare 2: pentru a schimba ceea ce este returnat atunci când un obiect MagicMock este numit ca o funcție, setați return_value.
dacă doriți să faceți mai mult decât să modificați valoarea returnată, puteți utiliza MagicMock.caracteristică side_effect., Vă permite să specificați o funcție complet nouă care va fi apelată în locul celei pe care o batjocoriți. Să facem asta. În primul rând, vom defini o nouă funcție, care va fi side_effect (rețineți că trebuie să aibă același set de parametri ca funcția ești batjocoritor):
def side_effect_function():
return "SKABLAM!"
Acum putem defini o nouă funcție care folosește acest efect secundar ca așa:
Totul este la fel ca mock_simple_function mai sus, cu excepția faptului că sunt stabilirea side_effect de mock_simple_func în loc de return_value.,
rezultatul este:
SKABLAM!
un caz bun de utilizare pentru utilizarea efectului secundar este dacă doriți să testați un flux de eroare și, prin urmare, doriți să ridicați o excepție în testul dvs. Voi schimba side_effect_function pentru a ridica o eroare în loc:
def side_effect_function():
raise FloatingPointError("A disastrous floating point error has occurred")
Acum, de ieșire este (am eliminat o parte din traceback pentru claritate):
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
de Învățare punctul 3: Să facă mai mult decât doar schimba return_value de un MagicMock, set side_effect.
clase batjocoritoare
până acum tocmai am batjocorit o funcție, dar ce zici de o clasă?, Să ne definim o clasă în simple.py numit SimpleClass cu o metoda numita exploda:
class SimpleClass(object):
def explode(self):
return "KABOOM!"
Acum, în use_simple.py să se scrie o funcție care folosește SimpleClass și apoi apel care funcționează:
def use_simple_class():
inst = simple.SimpleClass()
print(inst.explode())use_simple_class()
atunci Când a alerga de ieșire este:
KABOOM!
Acum, hai sa facem defini o a doua funcție și alege să își bată joc de toate elementele de simple_class folosind @machete.patch decorator:
@mock.patch("simple.SimpleClass")
def mock_simple_class(mock_class):
ca atunci când bate joc de o funcție, @mock.,patch decorator trece un obiect MagicMock care înlocuiește clasa pe care o batjocorești în funcția pe care o decorează. Obiectul MagicMock în acest caz este atribuit argumentului mock_class.
Pentru moment, voi doar imprima MagicMock obiect și apoi executați funcția:
@mock.patch("simple.SimpleClass")
def mock_simple_class(mock_class):
print(mock_class)mock_simple_class()
atunci Când a alerga de ieșire este:
<MagicMock name='SimpleClass'>
cu batjocorirea o funcție un MagicMock obiect este creat și a trecut în., Acum, vă permite să imprimați SimpleClass astfel încât să puteți vedea că a fost batjocorit și înlocuit cu același MagicMock obiect:
@mock.patch("simple.SimpleClass")
def mock_simple_class(mock_class):
print(mock_class)
print(simple.SimpleClass)
ieșire este:
<MagicMock name='SimpleClass'>
<MagicMock name='SimpleClass'>
Să creați o instanță a SimpleClass adică SimpleClass(), și apoi imprimați-l. Amintiți-vă, de când ne-am bătut joc SimpleClass, ceea ce de fapt se va întâmpla este că va MagicMock obiect ca o funcție:
rezultatul este:
Așa cum era de așteptat, de asteptare SimpleClass() și, prin urmare, de asteptare MagicMock obiect ca o funcție creează un nou MagicMock obiect.,
până acum, batjocorirea unei clase a fost la fel ca batjocorirea unei funcții. Cu toate acestea, în realitate, atunci când definim o clasă, vom crea apoi obiecte folosind acea clasă și, de cele mai multe ori, acele obiecte ne dorim cu adevărat să ne batem joc. Întrebarea este, cum să vă bateți joc de o instanță creată dintr-o clasă? Ei bine, când ai bate joc de o clasă și, în consecință, o MagicMock obiect este creat, dacă te referi la return_value pe care MagicMock obiect se creează un nou MagicMock obiect care reprezintă o instanță a clasei creat. La naiba! A fost o sentință și jumătate! Acesta a fost cu siguranță unul dintre „Aha!,”momente, dar chiar și scrierea nu face clar. Hai să imprimați return_value astfel încât să puteți vedea ce vreau să spun:
rezultatul este:
Cea de-a treia linie este de la imprimarea de instanță de clasa l-a creat, și cea de-a patra linie este de la imprimarea de return_value de MagicMock obiect trecut în funcția. După cum puteți vedea, se referă la același obiect MagicMock.
punctul de învățare 4: când bateți joc de o clasă, este creat un obiect MagicMock. Când creați o instanță a acestei clase este creat un nou obiect MagicMock., Când vă referiți la return_value obiectului MagicMock acelei clase, obțineți același obiect MagicMock care a fost creat atunci când a fost creată instanța acelei clase.
„Ahhhhh, de ce este atât de important?!?!?”Te aud plângând!
„este important dacă doriți să bată joc funcția exploda” i bellow în schimb!
următoarea întrebare pe care trebuie să o puneți este: este metoda pe care vreau să o batjocoresc o metodă de clasă sau o metodă de instanță? Deoarece modul în care schimbați return_value metodei va fi diferit în funcție de răspuns.,
Dacă metoda a fost o metodă de clasă, atunci v-ar seta return_value astfel:
MagicMockObject.ClassMethodName.return_value = "A delightful return value"
Dar dacă a fost un exemplu de metoda de ai face:
MagicMockObject.return_value.InstanceMethodName.return_value = "A daring return value"
Cu asta în minte, să schimbăm return_value a exploda, care este o metodă de instanță (voi elimina cele mai multe dintre imprima declarații pentru claritate) :
În codul de mai sus, mock_class.return_value returnează obiectul MagicMock care reprezintă instanța SimpleClass.
mock_class.return_value.,explode returnează obiectul MagicMock care reprezintă metoda explode a instanței SimpleClass.
prin urmare, setarea return_value de mock_class.return_value.explode setează return_value metodei explode a instanței SimpleClass.
rezultatul este:
BOO!
la fel ca atunci când batjocoritor o funcție, puteți seta side_effect pentru metodele de clase și obiecte.sper că acest articol v-a ajutat să înțelegeți mai mult batjocorirea. Vă rugăm să o recomandați dacă v-a plăcut și nu ezitați să lăsați un răspuns așa cum mi-ar plăcea să aud de la dvs.