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 tilMock 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:

    1. 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øje create=True til patch opkald til signal, unittest.mockfor at oprette en Mock selvom der ikke er import kampe identifikator.
    2. Vi bruger patch.dict til at injicere en midlertidig miljøvariabel i os.environ, dette kan udvides til enhver anden ordbog, vi gerne vil mocke.,
    3. vi nesting flere patch forbindelse manager opkald, men kun ved hjælp af as i den første, da det er den vi skal bruge til at ringe assert_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:

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

    1. Første er det patches standard lande i pricer for at være en liste med et enkelt indlæg GB
    2. Dette skulle gøre CountryPricer.country attribut standard til dette indlæg, da det er definitionen indeholder default=COUNTRIES
    3. Det så instanciates CountryPricer og anmoder om, at den nedsatte pris for GB !

    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:

    1. country attribut s Field eksempel er bygget, før testen er kørt på import tid,
    2. Python læser organ i den klasse, passerer COUNTRIES, der er defineret ved at pege på det Field eksempel
    3. 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.

    Articles

    Skriv et svar

    Din e-mailadresse vil ikke blive publiceret. Krævede felter er markeret med *