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:

    1. 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 å legge create=True til patch ring for å signal unittest.mockfor å lage en Mock selv om ingen import kamper identifikatoren.
    2. vi bruker patch.dict for å injisere en midlertidig miljø-variabelen i os.environ dette er extensible til andre ordliste vi ønsker å håne.,
    3. vi hekkende flere patch kontekst manager samtaler, men bare ved hjelp av as i den første, siden det er den vi må bruke til å ringe assert_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:

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

    1. Første it-lapper standard land i pricer for å være en liste med en enkelt oppføring GB ,
    2. Dette bør gjøre det CountryPricer.country attributt standard til at oppføringen siden det er definisjonen omfatter default=COUNTRIES ,
    3. Det så instanciates CountryPricer og ber for rabattert pris for GB !

    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:

    1. country attributtet Field eksempel er bygget før testen er løp på import tid,
    2. Python leser kroppen av klassen, passerer COUNTRIES som er definert ved at den peker til Field eksempel
    3. 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 pytests 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.

    Articles

    Legg igjen en kommentar

    Din e-postadresse vil ikke bli publisert. Obligatoriske felt er merket med *