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:

    1. 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ăuga create=True la patch suna pentru a semnala unittest.mockpentru a crea un Mock chiar dacă nu importa meciuri de identificare.
    2. folosim patch.dict pentru a injecta o temporară variabilă de mediu în os.environ, asta este extensibil pentru orice alte dicționar ne-ar plăcea să-și bată joc.,
    3. suntem mai multe cuiburi patch contextul manager apeluri, dar numai folosind as în primul deoarece este cel de care avem nevoie pentru a utiliza pentru a numi assert_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:

    1. î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ță.,
    2. 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:

    1. Primul patch-uri implicit țări în pricer să fie o listă cu o singură intrare GB ,
    2. Acest lucru ar trebui să facă CountryPricer.country atribut implicit la intrare, deoarece este definiția include default=COUNTRIES ,
    3. atunci instanciates CountryPricer și cere prețul redus pentru GB !

    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:

    1. country atributului Field exemplu este construit înainte de test este fugit la import de timp,
    2. Python citește corpul de clasa, trecerea COUNTRIES care este definit în acel moment la Field exemplu
    3. 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.

    Articles

    Lasă un răspuns

    Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *