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.mock
sau 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 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 mymodule
are 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:
- Mock out metoda
RemovalService.rm
în sine., - 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 faptrm
metoda tuturor instanțelorRemovalService
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 înUploadService
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
șimock.MagicMock
Cursurimock
library, de asemenea, include două clase importante pe care cele mai multe de interne funcționalitate este construit pe:mock.Mock
șimock.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
șimock.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 șiautospec
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 folosindmock
î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.,