det er bare en kendsgerning, da koden vokser til sidst, skal du begynde at tilføje mocks til din testpakke. Det, der startede som et sødt lille to-klasses projekt, taler nu med eksterne tjenester, og du kan ikke teste det komfortabelt længere.
derfor leveres Python med unittest.mock
, en stærk del af standardbiblioteket til stubbe afhængigheder og spottende bivirkninger.
unittest.mock
er dog ikke særlig intuitiv.,
jeg har fundet mig selv mange gange undrende, hvorfor min go-to-opskrift ikke fungerer i en bestemt sag, så jeg har sammensat dette snydeark for at hjælpe mig selv og andre med at få mocks til at arbejde hurtigt.
Du kan finde kodeeksemplerne i artiklens Github-arkiv. Jeg bruger Python 3.6, hvis du bruger 3.2 eller under, skal du bruge mock
PyPI-pakken.,
De eksempler, der er skrevet ved hjælp af unittest.TestCase
klasser for enkelhed i udfører dem uden afhængigheder, men du kan skrive dem som funktioner ved hjælp af pytest
næsten direkte,unittest.mock
vil fungere fint. Hvis du er en pytest
bruger, selvom jeg opfordrer dig til at se på det fremragende pytest-mock
bibliotek.
midtpunktet for unittest.mock
– modulet er selvfølgelig Mock
– klassen., Den vigtigste egenskab ved en Mock
objektet er, at det vil vende tilbage en anden Mock
eksempel når:
- adgang til en af dens egenskaber
- at kalde selve objektet
Dette er standard opførsel, men det kan ændres på forskellige måder. For eksempel kan du tildele en værdi til en attribut i Mock
ved at:
- tildele det direkte, som du ville gøre med ethvert Python-objekt.,
- brug
configure_mock
metode på en forekomst. - eller send søgeordsargumenter til
Mock
klassen ved oprettelse.
for At tilsidesætte opkald til mock du bliver nødt til at konfigurere sin return_value
ejendom, også tilgængelig som et søgeord argument i Mock
startværdi., Mock
vil altid vende tilbage til den samme værdi på alle opkald, dette, igen, kan også være konfigureret ved hjælp af side_effect
attribut:
- hvis du ønsker at returnere forskellige værdier for hvert opkald du kan tildele et iterable til
side_effect
. - hvis du gerne vil rejse en undtagelse, når du kalder Mock, kan du blot tildele undtagelsesobjektet til
side_effect
.,
Med alle disse værktøjer, kan vi nu oprette artikler for stort set alle Python-objekt, som vil arbejde meget for input til vores system. Men hvad med udgange?
Hvis du laver et CLI-program til at do .nloade hele internettet, vil du sandsynligvis ikke Do .nloade hele internettet på hver test. I stedet ville det være nok at hævde, at requests.download_internet
(ikke en reel metode) blev kaldt korrekt. Mock
giver dig praktiske metoder til at gøre det.,
Bemærk i vores eksempel assert_called_once
mislykkedes, dette viser et andet centralt aspekt af Mock
objekter, de kan optage alle interaktioner med dem, og du kan derefter kontrollere disse interaktioner.
For eksempel kan du bruge call_count
for at hente antallet af opkald til den Mock
, og brug call_args
eller call_args_list
for at inspicere de argumenter, til den sidste eller alle opkald, der er hhv.,
Hvis dette på noget tidspunkt er ubelejligt, kan du bruge reset_mock
– metoden til at rydde de optagede interaktioner, bemærk, at konfigurationen ikke nulstilles, bare interaktionerne.
Endelig, lad mig introducere MagicMock
, en underklasse af Mock
, der implementerer standard magi eller dunder metoder. Dette gør MagicMock
ideel til at mocke klasseadfærd, hvorfor det er standardklassen, når du lapper.,
Vi er nu klar til at begynde at spotte og isolere enheden under test. Her er et par opskrifter til at holde i tankerne:
Plaster på import
Den vigtigste måde at bruge unittest.mock
er til at lappe import i modulet under test ved brug af patch
funktion.
patch
vil aflytte import
erklæringer, der er identificeret af en string (mere om det senere), og return en Mock
eksempel du kan forudkonfigurere at bruge de teknikker, som vi diskuterede ovenfor.,
Forestil dig, at vi ønsker at teste denne meget simple funktion:
Bemærk vi importerer os
og ringer til getcwd
for at få den aktuelle arbejdsmappe. Vi ønsker faktisk ikke at kalde det på vores test, da det ikke er vigtigt for vores kode, og returværdien kan variere mellem miljøer, den kører på.
Som nævnt ovenfor, er vi nødt til at levere patch
med en streng, der repræsenterer vores specifikke import., Vi ønsker ikke at levere simpelthen os.getcwd
siden at ville lappe det, for alle de moduler, vi ønsker i stedet at levere det modul under test import
af os
, dvs work.os
. Når modulet importeres patch
vil arbejde sin magi og returnere en Mock
i stedet.,
Alternativt, kan vi bruge dekoratør version af patch
bemærk, denne gang testen har en ekstra parameter: mocked_os
som Mock
injiceres ind i den test.,
patch
vil frem søgeord argumenter til Mock
class, så for at konfigurere return_value
skal vi blot tilføje det som en:
Spottende klasser
Det er helt almindeligt at lappe klasser, helt eller delvist., Helper
med "db"
Worker
returnerer den forventede udvikling, der leveres af Helper
for at teste Worker
i fuldstændig isolation, vi har brug for at lappe det hele Helper
class:
Bemærk dobbelt return_value
i eksemplet, blot ved hjælp af MockHelper.get_path.return_value
vil ikke virke, da der i den kode, vi kalder get_path
om en instans, der ikke klassen selv.,
kæde syntaks er lidt forvirrende, men husk MagicMock
returnerer en anden MagicMock
opkald __init__
. Her konfigurerer vi enhver falsk Helper
forekomster oprettet af MockHelper
for at returnere det, vi forventer ved opkald til get_path
, som er den eneste metode, vi er interesseret i i vores test.,
Class speccing
En konsekvens af den fleksibilitet Mock
er, at når vi har spottet en klasse Python vil ikke hæve AttributeError
, da det blot vil vende tilbage med nye forekomster af MagicMock
for stort set alt. Dette er normalt en god ting, men kan føre til nogle forvirrende adfærd og potentielt bugs. For eksempel skriver følgende test,
vil stille passere uden advarsel helt mangler tastefejl i assrt_called_once
.,
Derudover, hvis vi skulle omdøbe Helper.get_path
til Helper.get_folder
, men glemmer at opdatere call i Worker
vores tests vil stadig bestå:
Heldigvis Mock
kommer med et værktøj til at undgå disse fejl, speccing.
kort sagt forudkonfigurerer det mocks for kun at reagere på metoder, der faktisk findes i spec-klassen., Der er flere måder at definere specs, men det nemmeste er simpelthen at gå autospec=True
til patch
opkald, som vil konfigurere Mock
til at opføre sig som objekt, bliver hånet, hæve undtagelser for manglende attributter og forkerte signaturer, som kræves., For eksempel:
Delvis klasse spottende
Hvis du er mindre tilbøjelige til at teste i fuldstændig isolation kan du også delvist patch en klasse ved hjælp af patch.object
:
Her patch.object
giver os mulighed for at konfigurere en hånede version af get_path
, mens resten af adfærd uberørt., Selvfølgelig betyder det, at testen ikke længere er, hvad du strengt ville overveje en enhedstest, men du kan være ok med det.
spottende indbyggede funktioner og miljøvariabler
i de foregående eksempler forsømte vi at teste et bestemt aspekt af vores enkle klasse, print
opkald. Hvis det i forbindelse med din ansøgning er vigtigt, som en CLI-kommando for eksempel, er vi nødt til at fremsætte påstande mod dette opkald.,
print
er selvfølgelig en indbygget funktion i Python, hvilket betyder, at vi ikke behøver at importere det i vores modul, hvilket går imod det, vi diskuterede ovenfor om patching ved import., Stadig forestille mig, at vi havde lidt mere kompliceret udgave af vores funktion:
man kan skrive en test som følgende:
Bemærk et par ting:
- vi kan håne
print
med noget problem, og at hævde, at der var et opkald ved at følge “plaster på import” – reglen. Dette var dog en ændring indført i 3.,5, tidligere har du behov for at tilføjecreate=True
tilpatch
opkald til signal,unittest.mock
for at oprette enMock
selvom der ikke er import kampe identifikator. - Vi bruger
patch.dict
til at injicere en midlertidig miljøvariabel ios.environ
, dette kan udvides til enhver anden ordbog, vi gerne vil mocke., - vi nesting flere
patch
forbindelse manager opkald, men kun ved hjælp afas
i den første, da det er den vi skal bruge til at ringeassert_called_once_with
.,
Hvis du ikke er glad for redebygning forbindelse ledere du kan også skrive patch
opkald i dekoratør form:
Bemærk dog rækkefølgen af argumenterne for at testkampe stabling rækkefølgen af dekoratører, og også, at patch.dict
ikke injicere et argument.,denne meget almindeligt scenarie:
Du kan naturligvis tilføje en faktiske armatur fil, men i den virkelige verden tilfælde kan det ikke være en mulighed, i stedet kan vi spotte den forbindelse manager ‘ s produktion til at være en StringIO
objekt:
Der er ikke noget særligt her, bortset fra den magiske __enter__
metode, vi bare nødt til at huske den underliggende mekanik i forbindelse ledere og nogle klog brug af vores trofaste MagicMock
.,
spottende klasseattributter
Der er mange måder at opnå dette på, men nogle er mere idiotsikre over for andre. Sige, at du har skrevet følgende kode:
Du kan teste kode uden nogen håner på to måder:
- Hvis koden under test giver adgang til attribut via
self.ATTRIBUTE
som det er tilfældet i dette eksempel, kan du blot indstille attributten direkte i de tilfælde. Dette er ret sikkert, da ændringen er begrænset til denne enkelt forekomst., - Alternativt kan du også indstille attributten i den importerede klasse i testen, før du opretter en instans. Dette ændrer dog den klasseattribut, du har importeret i din test, hvilket kan påvirke følgende test, så du skal huske at nulstille den.
den største ulempe ved denne ikke-Mock tilgange er, at hvis du på noget tidspunkt omdøber attributten, vil dine test mislykkes, og fejlen vil ikke direkte påpege denne navngivningsfejl.,
for At løse, at vi kan gøre brug af patch.object
på importerede klasse, som vil klage over, hvis klassen ikke har den angivne egenskab.
Her er nogle tests med hver teknik:
Spottende klasse hjælpere
følgende eksempel er roden til mange problemer i forbindelse monkeypatching ved hjælp af Mock. Det vises normalt på mere modne kodebaser, der begynder at gøre brug af rammer og hjælpere ved klassedefinition., For eksempel, forestil dig at denne hypotetiske Field
class helper:
Dens formål er at pakke og forbedre en egenskab i en anden klasse, en temmelig mønster, du normalt kan se i Ormer eller form biblioteker. Må ikke bekymre dig for meget med detaljerne i det bare bemærke, at der er en type
og default
værdi bestået.,
Nu tage denne anden prøve af produktion kode:
Denne klasse, gør brug af Field
class ved at definere sin country
attribut som en hvis type er str
og default
som det første element i COUNTRIES
konstant. Logikken under test er, at afhængigt af landet anvendes en rabat.,
som vi måske skriver følgende test For:
men det ville ikke passere.,
Lad os gå gennem test:
- Første er det patches standard lande i
pricer
for at være en liste med et enkelt indlægGB
- Dette skulle gøre
CountryPricer.country
attribut standard til dette indlæg, da det er definitionen indeholderdefault=COUNTRIES
- Det så instanciates
CountryPricer
og anmoder om, at den nedsatte pris forGB
!
Så hvad sker der?,
Den egentlige årsag til dette ligger i Python ‘ s adfærd i forbindelse med import, bedste, der er beskrevet i Luciano Ramalho er fremragende Flydende Python på kapitel 21:
For klasser, historien er forskellige: ved import tid, tolken udfører organ i hver klasse, selv kroppen af klasser, der er indlejret i andre klasser. Udførelse af en klassekrop betyder, at klassens attributter og metoder er defineret, og derefter er klasseobjektet selv bygget.,
Anvende denne til vores eksempel:
-
country
attribut sField
eksempel er bygget, før testen er kørt på import tid, - Python læser organ i den klasse, passerer
COUNTRIES
, der er defineret ved at pege på detField
eksempel - i Vores test-kode, der kører, men det er for sent for os at lappe
COUNTRIES
og få en ordentlig påstand.,
ud Fra ovenstående beskrivelse, kan du prøve at forsinke import af modulet, indtil inde i de tests, noget i retning af:
Denne, men vil stadig ikke passere som mock.patch
vil importere modulet og derefter monkeypatch det, hvilket resulterer i den samme situation som før.,
for At arbejde rundt omkring, det er vi nødt til at omfavne den tilstand af CountryPricer
class på det tidspunkt, testen og patch default
på allerede initialiseret Field
eksempel:
Denne løsning er ikke ideel, da det kræver kendskab til de interne dele i Field
som du måske ikke har eller ønsker at bruge, hvis du bruger et eksternt bibliotek, men det virker på denne ganske vist forenklet sagen.,
dette importtidsproblem er et af de største problemer, jeg har stødt på, mens jeg bruger unittest.mock
. Du skal huske, mens du bruger det, at ved import tid kode på øverste niveau af moduler udføres, herunder klasse organer.
Hvis den logik, du tester, er afhængig af nogen af denne logik, skal du muligvis overveje, hvordan du bruger patch
i overensstemmelse hermed.,
Indpakning op
Denne introduktion og cheatsheet bør få dig, der håner med unittest.mock
, men jeg vil opfordre dig til at læse dokumentationen grundigt igennem, der er masser af flere spændende tricks at lære.
Det er værd at nævne, at der er alternativer til unittest.mock
, især Alex Sørensen ‘ s pretend
bibliotek i kombination med pytest
‘s monkeypatch
armatur. Som ale.påpeger, ved hjælp af disse to biblioteker kan du tage en anden tilgang til at gøre mocking strengere endnu mere forudsigelig., Absolut en tilgang værd at udforske, men uden for rammerne af denne artikel.
unittest.mock
er i øjeblikket standarden for mocking i Python, og du finder det i stort set alle kodebaser. At have en solid forståelse af det vil hjælpe dig med at skrive bedre test hurtigere.