Det er bare et faktum i livet, som kode vokser etter hvert vil du trenger for å begynne å legge spotter til test suite. Det som startet som en søt liten to-klasse-prosjektet er nå snakker til eksterne tjenestene, og du kan teste det komfortabelt lenger.
Det er grunnen til Python skip med unittest.mock
, en kraftig del av standard bibliotek for stubbing avhengigheter og gjør narr av bivirkninger.
Imidlertid unittest.mock
er ikke spesielt intuitivt.,
jeg har funnet meg selv mange ganger lurer på hvorfor min går til oppskriften fungerer ikke for en bestemt sak, så jeg har satt sammen denne cheatsheet å hjelpe meg selv og andre får hengivenhet og jobber raskt.
Du kan finne koden eksemplene i artikkelen er Github depotet. Jeg skal bruke Python 3.6, hvis du bruker 3.2 eller nedenfor trenger du å bruke mock
PyPI pakken.,
eksemplene er skrevet ved hjelp av unittest.TestCase
klasser for enkelhet i å gjennomføre dem uten avhengigheter, men du kan skrive dem som funksjoner ved hjelp av pytest
nesten direkte,unittest.mock
vil fungere helt fint. Hvis du er en pytest
brukeren selv om jeg oppfordrer deg til å ta en titt på den utmerkede pytest-mock
bibliotek.
centerpoint av unittest.mock
modulen er, selvfølgelig, Mock
klasse., De viktigste kjennetegn på et Mock
objektet er at det vil komme tilbake en annen Mock
eksempel når:
- for å få tilgang til en av dens egenskaper
- kall selve objektet
Dette er standard oppførsel, men det kan overstyres på forskjellige måter. For eksempel kan du tilordne en verdi til et attributt i Mock
av:
- Angi det direkte, som du ville gjøre med noen Python-objekt.,
- Bruk
configure_mock
metoden på et eksempel. - Eller passere søkeord argumenter til
Mock
klasse på skapelsen.
for Å overstyre anrop til mock du trenger for å konfigurere return_value
eiendel, også tilgjengelig som et søkeord argument i Mock
initializer., Mock
vil alltid gå tilbake samme verdi på alle anrop, dette, igjen, kan også bli konfigurert ved å bruke side_effect
attributt:
- hvis du ønsker å returnere forskjellige verdier på hver ring kan du tilordne en iterable til
side_effect
. - Dersom du ønsker å heve et unntak når du ringer Mock du kan bare gi unntak objekt til
side_effect
.,
Med alle disse verktøyene kan vi nå lage stubber for egentlig alle Python-objekt, som vil fungere bra for input til systemet vårt. Men hva om utganger?
Hvis du gjør en CLI-programmet til å laste ned hele Internett, har du sannsynligvis ikke ønsker å laste ned hele Internett på hver test. I stedet ville det være nok til å hevde at requests.download_internet
(ikke en ekte metode) ble kalt på riktig måte. Mock
gir deg praktiske metoder til å gjøre det.,
Merk i vårt eksempel assert_called_once
mislyktes, dette viser et annet viktig aspekt av Mock
objekter, de spille alle interaksjoner med dem, og deretter kan du inspisere disse interaksjonene.
For eksempel kan du bruke call_count
for å hente antall anrop til Mock
, og bruk call_args
eller call_args_list
for å inspisere argumenter til siste eller for alle samtaler, henholdsvis.,
Hvis dette er upraktisk på noe punkt kan du bruke reset_mock
metode for å fjerne registrert vekselsvirkningene, merk konfigurasjonen vil ikke bli tilbakestilt, bare interaksjoner.
til Slutt, la meg introdusere MagicMock
, en underklassen av Mock
som implementerer standard magi eller dunder metoder. Dette gjør MagicMock
ideell for å håne klasse atferd, som er hvorfor det er standard klasse når lapp.,
Vi er nå klar til å starte på tentamen og å isolere enhet under tester. Her er et par oppskrifter for å holde i tankene:
Patch på import
Den viktigste måten å bruke unittest.mock
er å patch import i modul under test ved hjelp av patch
funksjon.
patch
vil avskjære import
uttalelser som er identifisert av en streng (mer om det senere), og returnerer en Mock
eksempel kan du preconfigure å bruke teknikkene vi har diskutert ovenfor.,
Tenk deg vi ønsker å teste dette svært enkel funksjon:
Merk vi importerer os
og ringer getcwd
for å få den gjeldende arbeidsmappen. Vi ønsker ikke å faktisk kalle det på våre tester om siden det ikke er viktig for våre kode og returnere verdien kan variere mellom miljøer det kjøres på.
Som nevnt ovenfor må vi leverer patch
med en streng som representerer våre spesifikke import., Vi ønsker ikke å levere rett og slett os.getcwd
siden det ville patch det for alle modulene, i stedet ønsker vi å levere modul under test er import
av os
, dvs. work.os
. Når modulen er importert patch
vil arbeide sin magi, og returnere en Mock
i stedet.,
Alternativt kan vi bruke dekoratør versjon av patch
merk denne tiden testen har en ekstra parameter: mocked_os
som Mock
er sprøytet inn i testen.,
patch
vil fremover søkeord argumenter til Mock
klasse, så for å konfigurere en return_value
vi bare legge det til som en:
Tentamen klasser
Det er ganske vanlig å patch klasser helt eller delvis., Helper
med "db"
Worker
returnerer forventet bane levert av Helper
for å teste Worker
i fullstendig isolasjon vi trenger for å dekke hele Helper
class:
Merk dobbel return_value
i eksempelet, bare ved hjelp av MockHelper.get_path.return_value
ikke ville fungere, siden i koden som vi kaller get_path
på en forekomst, ikke klassen i seg selv.,
kjeding syntaks er litt forvirrende, men husk MagicMock
returnerer en annen MagicMock
på anrop __init__
. Her er vi å konfigurere alle falske Helper
forekomster som er opprettet av MockHelper
for å gå tilbake hva vi kan forvente på anrop til get_path
som er den eneste metoden vi bryr oss om i vår test.,
Klasse speccing
En konsekvens av fleksibilitet Mock
er at når vi har spottet en klasse Python vil ikke heve AttributeError
som det bare vil gå tilbake nye forekomster av MagicMock
for stort sett alt. Dette er vanligvis en god ting, men kan føre til noe forvirrende atferd og potensielt bugs. For eksempel skriver følgende test,
vil stille passere uten advarsel helt mangler skrivefeil i assrt_called_once
.,
i Tillegg, hvis vi skulle endre navn Helper.get_path
til Helper.get_folder
, men glemmer å oppdatere ring i Worker
våre tester vil fremdeles gå:
Heldigvis Mock
kommer med et verktøy for å unngå disse feilene, speccing.
for å si det enkelt, det preconfigures håner å bare svare på metoder som faktisk finnes i spec klasse., Det er flere måter å definere spesifikasjoner, men den enkleste er å bare bestå autospec=True
til patch
ring, som vil konfigurere Mock
for å oppføre seg som et objekt som blir spottet, heve unntak for manglende egenskaper og feil signaturer som kreves., For eksempel:
Delvis klasse tentamen
Hvis du er mindre tilbøyelig til å teste i fullstendig isolasjon du kan også delvis patch en klasse ved hjelp av patch.object
:
Her patch.object
er noe som gir oss mulighet til å konfigurere en spotte versjon av get_path
bare la resten av atferd urørt., Selvfølgelig betyr dette at testen er ikke lenger hva ville du strengt vurdere en unit-test, men du kan være ok med det.
Tentamen innebygde funksjoner, og miljøvariabler
I tidligere eksempler har vi unnlatt å teste en bestemt del av våre enkle klasse, print
anrop. Hvis du er i den sammenheng av søknaden dette er viktig, som en CLI-kommandoen, for eksempel, må vi gjøre påstander mot denne samtalen.,
print
er selvfølgelig en innebygd funksjon i Python, noe som betyr at vi ikke trenger å importere det i vår modul, som går mot det vi har diskutert ovenfor om oppdatering på importer., Fortsatt forestille vi hadde dette litt mer komplisert versjon av vår funksjon:
Vi kan skrive en test som følgende:
Merk et par ting:
- kan vi mock
print
uten problem, og å hevde at det var en samtale ved å følge «patch på import» – regelen. Dette var imidlertid en endring som ble gjennomført i 3.,5, tidligere har du behov for å leggecreate=True
tilpatch
ring for å signalunittest.mock
for å lage enMock
selv om ingen import kamper identifikatoren. - vi bruker
patch.dict
for å injisere en midlertidig miljø-variabelen ios.environ
dette er extensible til andre ordliste vi ønsker å håne., - vi hekkende flere
patch
kontekst manager samtaler, men bare ved hjelp avas
i den første, siden det er den vi må bruke til å ringeassert_called_once_with
.,
Hvis du ikke er glad i nesting sammenheng ledere du kan også skrive patch
anrop i dekoratør skjemaet:
Merk imidlertid rekkefølgen på argumentene til test kamper stablerekkefølgen av dekoratører, og også at patch.dict
ikke injisere et argument.,dette er veldig vanlig scenario:
Du kan selvfølgelig legge til en faktiske armatur-fil, men i den virkelige verden tilfeller kan det ikke være et alternativ, i stedet kan vi mock sammenheng manager utgang å være en StringIO
objektet:
Det er ikke noe spesielt her, bortsett fra den magiske __enter__
metode, vi må bare huske på de underliggende mekanismene i sammenheng ledere og noen smart bruk av våre trofaste MagicMock
.,
Tentamen klasse attributter
Det er mange måter å oppnå dette, men noen er mer utformet som andre. Si at du har skrevet inn følgende kode:
Du kan teste koden uten noen spotter på to måter:
- Hvis koden under test tilgang attributtet via
self.ATTRIBUTE
, som er tilfelle i dette eksemplet, kan du ganske enkelt angi attributtet direkte i forekomsten. Dette er ganske sikker som endringen er begrenset til denne ene forekomsten., - Alternativt kan du også angi attributtet i importerte klasse i testen før du oppretter en forekomst. Dette er imidlertid endringer klasse attributt du har importert i testen som kan påvirke følgende tester, så du trenger å huske på å tilbakestille det.
Den viktigste ulempen fra denne ikke-Mock tilnærminger er at hvis du når som helst gi nytt navn til attributtet tester vil mislykkes, og feilen vil ikke direkte påpeke dette navngi mismatch.,
for Å løse dette kan vi gjøre bruk av patch.object
på importerte klasse som vil klage dersom klassen ikke har angitt attributt.
Her er noen tester med hver teknikk:
Tentamen klasse hjelpere
følgende eksempel er roten til mange problemer angående monkeypatching ved hjelp av Mock. Det viser vanligvis opp på mer modne codebases som begynner å gjøre bruk av rammer og hjelpere på klasse definisjon., For eksempel, tenk deg denne hypotetiske Field
klasse helper:
Dens formål er å pakke og forbedre en egenskap i en annen klasse, en ganske mønster du ofte kan se i ORMs eller form biblioteker. Ikke bekymre deg for mye med detaljer om det bare oppmerksom på at det er en type
og default
verdi bestått i.,
Nå tar denne andre eksempel på produksjon kode:
Denne klassen gjør bruk av Field
klasse ved å definere sin country
attributt som en hvis type er str
og default
som det første elementet i COUNTRIES
konstant. Logikken under test er at avhengig av hvilket land en rabatt er brukt.,
som vi kan skrive følgende test:
Men det ville IKKE gå.,
La oss gå gjennom testen:
- Første it-lapper standard land i
pricer
for å være en liste med en enkelt oppføringGB
, - Dette bør gjøre det
CountryPricer.country
attributt standard til at oppføringen siden det er definisjonen omfatterdefault=COUNTRIES
, - Det så instanciates
CountryPricer
og ber for rabattert pris forGB
!
Så hva er det som skjer?,
roten årsaken til dette ligger i Python ‘ s oppførsel under import, best beskrevet i Luciano Ramalho er utmerket Flytende Python på kapittel 21:
For klasser, historien er annerledes: ved import tid, tolken utfører hoveddelen av hver klasse, selv kroppen av klasser som er nestet i andre klasser. Gjennomføring av en klasse kroppen betyr at attributter og metoder i klassen er definert, og deretter klassen objektet i seg selv er bygget.,
bruke denne til å vårt eksempel:
-
country
attributtetField
eksempel er bygget før testen er løp på import tid, - Python leser kroppen av klassen, passerer
COUNTRIES
som er definert ved at den peker tilField
eksempel - Vår test-kode går, men det er for sent for oss å patch
COUNTRIES
og få en riktig påstand.,
Fra beskrivelsen ovenfor kan du prøve å forsinke import av modulen til inne i testene, noe sånt som:
Dette, imidlertid, vil fortsatt ikke passere som mock.patch
vil importere modulen og deretter monkeypatch det, noe som resulterer i samme situasjon som før.,
Hvis du vil omgå dette må vi omfavne staten av CountryPricer
klasse på tidspunktet for testen og patch default
på allerede initialisert Field
eksempel:
Denne løsningen er ikke ideelt siden det krever kunnskap om det innvendige av Field
som du kanskje ikke har eller ønsker å bruke hvis du bruker et eksternt bibliotek, men det fungerer på denne riktignok forenklet tilfelle.,
Dette importere gang problemet er ett av de største problemene jeg har støtt mens du bruker unittest.mock
. Du må huske på mens du bruker det som på import tid-kode i topp-nivå-moduler som er utført, herunder klasse organer.
Hvis logikken du tester er avhengig av noen av denne logikken du kan få behov for å revurdere hvordan du bruker patch
tilsvarende.,
Innpakning opp
Denne innledningen og cheatsheet bør få deg harselering med unittest.mock
, men jeg oppfordrer deg til å lese dokumentasjonen grundig, det er mye mer interessant triks å lære.
Det er verdt å nevne at det finnes alternativer til unittest.mock
, spesielt Alex Gaynor er pretend
biblioteket i kombinasjon med pytest
s monkeypatch
armatur. Som Alex poeng ut, ved hjelp av disse to bibliotekene kan du ta en annen tilnærming til å gjøre tentamen strengere ennå mer forutsigbar., Definitivt en tilnærming som er verdt å utforske, men utenfor omfanget av denne artikkelen.
unittest.mock
er i dag standard for tentamen i Python, og du finner det i praktisk talt alle codebase. Å ha en solid forståelse av det vil hjelpe deg å skrive bedre tester raskere.