o introducere în batjocură în Python

cum să rulați teste unitare în Python fără a vă testa răbdarea

cel mai adesea, software-ul pe care îl scriem interacționează direct cu ceea ce am eticheta ca servicii „murdare”. În termeni laici: servicii care sunt cruciale pentru aplicația noastră, dar ale căror interacțiuni au intenționat, dar efecte secundare nedorite-adică nedorite în contextul unui test autonom.,

De exemplu: poate scriem o aplicație socială și doriți pentru a testa noul nostru ‘Post pentru a Facebook caracteristică, dar nu vreau să posta la Facebook de fiecare dată când vom rula testul nostru suite.

Python unittest library include un subpachet nume unittest.mocksau daca declara ca o dependență, pur și simplu mock—care oferă extrem de puternic și util mijloacele prin care să batjocorească și stub aceste efectele secundare nedorite.,

Notă: mock este nou incluse în biblioteca standard de Python 3.3; înainte de distribuții va trebui să utilizați Mock biblioteca descărcate prin intermediul PyPI.

apeluri de sistem vs.Python batjocoritor

pentru a vă oferi un alt exemplu și unul cu care vom rula pentru restul articolului, luați în considerare apeluri de sistem., Nu e greu să vedem că acestea sunt primele candidate pentru batjocoritor: dacă scrii un script pentru a ejecta un CD drive, un server web care elimină învechit fișierele cache de /tmp, sau un server socket care se leagă la un port TCP, aceste apeluri toate au efectele secundare nedorite în cadrul unității dumneavoastră-teste.

ca dezvoltator, vă pasă mai mult că biblioteca dvs. a apelat cu succes funcția de sistem pentru ejectarea unui CD, spre deosebire de a vă deschide tava CD de fiecare dată când se execută un test.,

ca dezvoltator, vă pasă mai mult că biblioteca numit cu succes funcția de sistem pentru ejectarea unui CD (cu argumentele corecte, etc.), spre deosebire de a experimenta de fapt tava CD deschis de fiecare dată când un test este rulat. (Sau mai rău, de mai multe ori, ca mai multe teste de referință codul de scoatere în timpul unei singure unități-test run!)

De asemenea, menținerea testelor unitare eficiente și performante înseamnă păstrarea cât mai mult „cod lent” din rulările automate de testare, și anume accesul la sistemul de fișiere și la rețea.,

pentru primul nostru exemplu, vom refactoriza un caz standard de testare Python de la forma originală la una folosind mock. Vom demonstra cum scrierea unui caz de testare cu batjocuri va face testele noastre mai inteligente, mai rapide și capabile să dezvăluie mai multe despre modul în care funcționează software-ul.cu toții trebuie să ștergem din când în când fișiere din sistemul nostru de fișiere, așa că haideți să scriem o funcție în Python care va face un pic mai ușor pentru scripturile noastre să facă acest lucru.,

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

în mod Evident, ne rm metodă în acest moment nu oferă mult mai mult decât de fond al sistemului os.remove metoda, dar codebase va îmbunătăți, permițându-ne pentru a adăuga mai multă funcționalitate aici.

să scriem un caz de test tradițional, adică fără batjocură:

cazul nostru de test este destul de simplu, dar de fiecare dată când este rulat, este creat un fișier temporar și apoi șters., În plus, nu avem nici o modalitate de a testa dacă noștri rm metoda treacă corect argumentul până la os.remove apel. Putem presupune că se bazează pe testul de mai sus, dar rămâne mult de dorit.

Refactoring cu Python își bate joc

Să refactor nostru de test folosind mock:

Cu aceste refactors, ne-au schimbat fundamental modul în care testul își desfășoară activitatea. Acum, avem un insider, un obiect pe care îl putem folosi pentru a verifica funcționalitatea altui.,unul dintre primele lucruri care ar trebui să iasă în evidență este că folosim metodamock.patch decorator pentru a bate joc de un obiect situat lamymodule.os și injectând acea batjocură în metoda noastră de testare. Nu ar face mai mult sens să batjocorească os în sine, mai degrabă decât trimiterea la la mymodule.os?Ei bine, Python este oarecum un șarpe mascate atunci când vine vorba de importuri și gestionarea modulelor., În timpul rulării, modulul mymoduleare propriulos care este importat în propriul domeniu de aplicare local din modul. Astfel, dacă ne-am bate joc os, nu vom vedea efectele bate joc în mymodule module.

mantra să repeți este aceasta:

Mock un articol în care este folosit, nu de unde a venit.,

Dacă aveți nevoie pentru a bate joc tempfile module pentru myproject.app.MyElaborateClass, probabil, ai nevoie pentru a aplica mock să myproject.app.tempfile, ca fiecare modul păstrează propriile importuri.

cu această capcană din drum, să continuăm să ne batem joc.

adăugarea validării la „rm”

metoda rm definită anterior este destul de suprasimplificată. Am dori să-l valideze că există o cale și este un fișier înainte de a încerca doar orbește să-l eliminați., Să refactor rm să fie un pic mai inteligent:

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

Mare. Acum, să ajustăm cazul nostru de testare pentru a menține acoperirea.

paradigma noastră de testare s-a schimbat complet. Acum putem verifica și valida funcționalitatea internă a metodelor fără efecte secundare.

File-Removal as a Service with Mock Patch

până acum, am lucrat doar cu furnizarea de batjocuri pentru funcții, dar nu și pentru metode pe obiecte sau cazuri în care batjocura este necesară pentru trimiterea parametrilor. Să acoperim mai întâi metodele obiectului.,

vom începe cu un refactor al metodei rm într-o clasă de servicii. Nu există într-adevăr o nevoie justificabilă, în sine, de a încapsula o funcție atât de simplă într-un obiect, dar cel puțin ne va ajuta să demonstrăm concepte cheie în mock. Să refactor:

veți observa că nu s-au schimbat prea multe în cazul nostru de testare:

grozav, așa că acum știm că RemovalService funcționează conform planificării., Hai să creăm un alt serviciu care declară ca o dependență:

Deoarece avem deja testul de acoperire pe RemovalService, nu vom valida interne funcționalitatea rm metoda în testele noastre de UploadService. Mai degrabă, vom testa pur și simplu (fără efecte secundare, desigur) că UploadService apeluri RemovalService.rm metoda, despre care știm că „pur și simplu funcționează™” de la ultima noastră încercare caz.

există două moduri de a merge despre acest lucru:

  1. Mock out metoda RemovalService.rm în sine.,
  2. furnizați o instanță batjocorită în constructorul UploadService.deoarece ambele metode sunt adesea importante în testarea unităților, vom examina ambele.

    Opțiunea 1: Batjocoritor Exemplu Metode

    mock biblioteca are o metodă specială de decorator pentru batjocoritor instanță obiect metode și proprietăți, @mock.patch.object decorator:

    Mare! Am validate că UploadService cu succes apelurile noastre instanță rm metoda. Ai observat ceva interesant acolo?, Mecanismul de patch-uri a înlocuit de fapt rm metoda tuturor instanțelor RemovalService din metoda noastră de testare. Asta înseamnă că putem inspecta instanțele în sine. Dacă doriți să vedeți mai multe, încercați să scăpați într-un punct de întrerupere în codul dvs. de batjocură pentru a vă simți bine cum funcționează mecanismul de patch-uri.

    Mock Patch capcană: Decorator ordine

    atunci când se utilizează mai multe decoratori pe metodele de testare, ordinea este importantă, și este un fel de confuz. Practic, atunci când mapați decoratorii la parametrii metodei, lucrați înapoi., Luați în considerare acest exemplu:

    observați cum parametrii noștri sunt potriviți cu ordinea inversă a decoratorilor? Asta se datorează în parte modului în care funcționează Python. Cu mai multe metode decoratori, aici e ordinea de execuție în pseudocod:

    patch_sys(patch_os(patch_os_path(test_something)))

    Deoarece patch-uri pentru sys este ultraperiferice patch-uri, acesta va fi executat ultimul, făcându-l ultimul parametru în testul real argumentele metodei. Luați notă de acest bine și utilizați un depanator atunci când executați testele pentru a vă asigura că parametrii potriviți sunt injectați în ordinea corectă.,

    Opțiunea 2: Crearea de Machete Cazuri

    în Loc de batjocoritor specifice metodă de exemplu, am putea în schimb să furnizeze un râs de exemplu la UploadService cu constructorul acestuia. Prefer opțiunea 1 de mai sus, deoarece este mult mai precisă, dar există multe cazuri în care opțiunea 2 ar putea fi eficientă sau necesară. Să refactor din nou testul nostru:

    În acest exemplu, nu am avut chiar de a patch-uri de funcționalitate, noi pur și simplu a crea un auto-spec pentru RemovalService clasa, și apoi se injectează acest exemplu în UploadService pentru a valida funcționalitatea.,

    metoda mock.create_autospec creează o instanță echivalentă funcțional cu clasa furnizată. Ceea ce înseamnă, practic vorbind, este că atunci când instanța returnată este interacționată, va ridica excepții dacă este folosită în moduri ilegale. Mai exact, dacă o metodă este apelată cu un număr greșit de argumente, se va ridica o excepție. Acest lucru este extrem de important ca refactori se întâmplă. Pe măsură ce o bibliotecă se schimbă, testele se rup și asta este de așteptat. Fără a utiliza un auto-spec, testele noastre vor trece în continuare, chiar dacă implementarea de bază este întreruptă.,

    Capcană: mock.Mock și mock.MagicMock Cursuri

    mock library, de asemenea, include două clase importante pe care cele mai multe de interne funcționalitate este construit pe: mock.Mock și mock.MagicMock. Când i se oferă posibilitatea de a folosi o instanță mock.Mock, o instanță mock.MagicMock sau o auto-spec, favorizează întotdeauna utilizarea unui auto-spec, deoarece ajută la menținerea testelor sănătoase pentru modificările viitoare., Acest lucru este pentru că mock.Mock și mock.MagicMock accepta toate apelurile de metodă și de proprietate misiuni, indiferent de suport API. Luați în considerare următorul caz de utilizare:

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

    putem testa acest lucru cu un mock.Mock exemplu de genul asta:

    Această logică pare normal, dar hai să modificați Target.apply metoda de a lua mai mulți parametri:

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

    Re-rula testul, și veți găsi că încă trece. Asta pentru că nu este construit împotriva API-ului dvs. real., Acest lucru este de ce tu ar trebui să folosiți întotdeauna create_autospec metoda și autospec parametru cu @patch și @patch.object decoratori.

    Python bate joc de Exemplu: Mocking un Facebook API Apel

    Pentru a termina, să scrie o mai aplicabile în lumea reală python bate joc de exemplu, unul pe care am menționat în introducere: de a posta un mesaj de la Facebook. Vom scrie o clasă de înveliș frumos și un caz de testare corespunzător.,

    Iată cazul nostru de testare, care verifică dacă postăm mesajul fără a posta mesajul:

    așa cum am văzut până acum, este foarte simplu să începeți să scrieți teste mai inteligente cu mock în Python.

    Concluzie

    Python mock library, dacă un pic confuz pentru a lucra cu, este un joc-changer pentru unitate de testare. Am demonstrat frecvente cazuri de utilizare pentru a începe folosind mock în unitate de testare, și sperăm că acest articol vă va ajuta dezvoltatorii Python depăși obstacole inițiale și să scrie excelent, testat cod.,

Share

Lasă un răspuns

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