Het is gewoon een feit van het leven, als code groeit uiteindelijk moet je beginnen met het toevoegen van mocks aan je test suite. Wat begon als een schattig klein twee klasse project is nu in gesprek met externe diensten en je kunt het niet comfortabel meer testen.

daarom wordt Python geleverd met unittest.mock, een krachtig onderdeel van de standaardbibliotheek voor Stubben afhankelijkheden en spottende bijwerkingen.

echter, unittest.mock is niet bijzonder intuïtief.,

Ik heb mezelf vaak afgevraagd waarom mijn go-to Recept niet werkt voor een bepaald geval, Dus ik heb dit cheatsheet samengesteld om mezelf en anderen te helpen om spotters snel aan het werk te krijgen.

je kunt de code voorbeelden vinden in de GitHub repository van het artikel. Ik zal Python 3.6 gebruiken, als je 3.2 of lager gebruikt moet je het mock PyPI pakket gebruiken.,

De voorbeelden zijn geschreven met unittest.TestCase klassen voor het gemak bij het uitvoeren ervan zonder afhankelijkheden, maar je kunt ze schrijven als functies met pytest bijna direct,unittest.mock werkt prima. Als u een pytest gebruiker bent, moedig ik u echter aan om eens te kijken naar de uitstekende pytest-mock bibliotheek.

het middelpunt van de unittest.mock module is natuurlijk de Mock klasse., Het belangrijkste kenmerk van een Mock object is dat het zal terugkeren andere Mock bijvoorbeeld wanneer:

  • toegang tot één van de kenmerken
  • het aanroepen van het object zelf

Dit is het standaard gedrag, maar het kan overschreven worden op verschillende manieren. U kunt bijvoorbeeld een waarde toewijzen aan een attribuut in de Mock door:

  • direct toewijzen, zoals u zou doen met elk Python-object.,
  • gebruik deconfigure_mock methode op een instantie.
  • of geef sleutelwoordargumenten door aan de Mock klasse bij het aanmaken.

om aanroepen naar de mock te overschrijven moet u de eigenschapreturn_valueconfigureren, ook beschikbaar als sleutelwoordargument in deMockinitializer., De Mock geeft altijd dezelfde waarde op alle aanroepen, dit kan ook worden geconfigureerd met behulp van het side_effect attribuut:

  • Als u verschillende waarden wilt retourneren bij elke oproep kunt u een iterable toewijzen aan side_effect.
  • Als u een uitzondering wilt maken bij het aanroepen van de Mock, kunt u het exception object gewoon toewijzen aan side_effect.,

met al deze tools kunnen we nu stubs maken voor vrijwel elk Python-object, wat geweldig zal werken voor ingangen naar ons systeem. Maar hoe zit het met outputs?

als u een CLI-programma maakt om het hele Internet te downloaden, Wilt u waarschijnlijk niet bij elke test het hele Internet downloaden. In plaats daarvan zou het voldoende zijn om te beweren dat requests.download_internet (geen echte methode) correct werd genoemd. Mock geeft u handige methoden om dit te doen.,

opmerking in ons voorbeeldassert_called_oncemislukt, dit toont een ander belangrijk aspect vanMockobjecten, ze registreren alle interacties met hen en vervolgens kunt u deze interacties inspecteren.

u kunt bijvoorbeeld call_count gebruiken om het aantal aanroepen naar de Mock op te halen, en call_args of call_args_list gebruiken om de argumenten van de laatste of alle aanroepen te inspecteren.,

als dit op enig moment lastig is, kunt u de reset_mock methode gebruiken om de opgenomen interacties te wissen, let op: de configuratie wordt niet gereset, alleen de interacties.

tot slot introduceer ikMagicMock, een subklasse vanMockdie standaard magic-of dunder-methoden implementeert. Dit maakt MagicMock ideaal om klassengedrag te mockeren, daarom is het de standaardklasse bij patchen.,

We zijn nu klaar om te beginnen met het bespotten en isoleren van de eenheid die wordt getest. Hier zijn een paar recepten om in gedachten te houden:

Patch on import

de belangrijkste manier om unittest.mock te gebruiken is om de import te patchen in de module die wordt getest met behulp van de patch functie.

patch onderschept import statements geà dentificeerd door een string (meer daarover later), en geeft een Mock instance u kunt vooraf configureren met behulp van de technieken die we hierboven besproken.,

stel je voor dat we deze zeer eenvoudige functie willen testen:

Note we importerenosen roepengetcwdom de huidige werkmap te verkrijgen. We willen het niet echt noemen op onze tests, omdat het niet belangrijk is voor onze code en de retourwaarde kan verschillen tussen omgevingen waarop het draait.

zoals hierboven vermeld moeten we patch voorzien van een string die onze specifieke import vertegenwoordigt., We willen niet simpelweg os.getcwd leveren omdat dat het voor alle modules zou patchen, in plaats daarvan willen we de module onder test import van os leveren , d.w.z. work.os . Wanneer de module wordt geïmporteerd zal patch zijn magie gebruiken en in plaats daarvan een Mock teruggeven.,

alternatief kunnen we de decorator versie vanpatchgebruiken, merk op dat deze keer de test een extra parameter heeft:mocked_oswaarbij deMockin de test wordt geïnjecteerd.,

patch zendt een trefwoord argumenten om de Mock class, zodat u een return_value we gewoon toevoegen als een:

Spottende klassen

Het is heel gebruikelijk om patch klassen geheel of gedeeltelijk., Helper met "db"

  • Worker geeft als resultaat het verwachte pad geleverd met behulp van de Helper
  • om de test Worker in volledig isolement we moeten patch de hele Helper klasse:

    Let op de dubbele return_value in het voorbeeld, gewoon met behulp van de MockHelper.get_path.return_value zou niet werken, omdat in de code noemen we get_path op een exemplaar, niet in de klas zelf.,

    De chaining syntaxis is enigszins verwarrend, maar onthoud MagicMock geeft een andere MagicMock bij aanroepen __init__. Hier configureren we elke fake Helper instances aangemaakt door MockHelper om te retourneren wat we verwachten bij oproepen naar get_path wat de enige methode is waar we om geven in onze test.,

    Class speccing

    een gevolg van de flexibiliteit van Mock is dat als we eenmaal een klasse Python bespot hebben, AttributeError niet zal verhogen, omdat het eenvoudig nieuwe instanties van MagicMock zal teruggeven voor in principe alles. Dit is meestal een goede zaak, maar kan leiden tot wat verwarrend gedrag en potentieel bugs. Bijvoorbeeld bij het schrijven van de volgende test, zal

    geruisloos passeren zonder dat er een waarschuwing ontbreekt die het typefoutje inassrt_called_oncemist .,

    Bovendien, als we hernoemen Helper.get_path tot Helper.get_folder, maar vergeet voor het bijwerken van de oproep in Worker onze tests zullen nog doorgeven:

    Gelukkig Mock wordt geleverd met een instrument om te voorkomen dat deze fouten, speccing.

    simpel gezegd, het configureert mocks om alleen te reageren op methoden die daadwerkelijk bestaan in de spec klasse., Er zijn verschillende manieren om specs te definiëren, maar het makkelijkst is om gewoon autospec=True door te geven aan de patch aanroep, die de Mock configureert om zich te gedragen als het object dat bespot wordt, waarbij uitzonderingen worden gemaakt voor ontbrekende attributen en onjuiste handtekeningen zoals vereist., Bijvoorbeeld:

    Gedeeltelijke klasse spottende

    Als je bent minder geneigd om te testen in volledige isolatie kunt u ook gedeeltelijk patch van een klasse met patch.object:

    Hier patch.object laat ons voor het configureren van een bespotten versie van get_path terwijl de rest van het gedrag onaangeroerd., Natuurlijk betekent dit dat de test is niet langer wat je strikt zou overwegen een eenheid test, maar je kan ok zijn met dat.

    het bespotten van ingebouwde functies en omgevingsvariabelen

    in de vorige voorbeelden hebben we nagelaten een bepaald aspect van onze eenvoudige klasse te testen, de print aanroep. Als dit in het kader van uw applicatie belangrijk is, zoals een CLI commando bijvoorbeeld, moeten we beweringen doen tegen deze call.,

    print is natuurlijk een ingebouwde functie in Python, wat betekent dat we het niet hoeven te importeren in onze module, wat in tegenspraak is met wat we hierboven hebben besproken over patchen bij import., Nog altijd denken dat we hadden deze iets uitgebreidere versie van onze functie:

    Wij kunnen het schrijven van een test, zoals de volgende:

    Let op een paar dingen:

    1. wij kunnen mock print zonder probleem en het opkomen was er een oproep door het volgen van de “patch op importeren” – regel. Dit was echter een wijziging die in 3 werd ingevoerd.,5, voorheen moest u create=True toevoegen aan depatch aanroep naar signaalunittest.mockom eenMock aan te maken, ook al komt er geen import overeen met de identifier.
    2. we gebruiken patch.dict om een tijdelijke omgevingsvariabele te injecteren in os.environ, dit is uitbreidbaar naar elk ander woordenboek dat we willen bespotten.,
    3. we nesten verschillende patch contextmanager-aanroepen, maar gebruiken alleen as in de eerste omdat het degene is die we moeten gebruiken om assert_called_once_with aan te roepen .,

    Als u niet dol bent op contextmanagers, kunt u ook de patch aanroepen in de vorm van decorator schrijven:

    merk echter op dat de volgorde van de argumenten aan de test komt overeen met de stapelvolgorde van de decorateurs, en ook datpatch.dictgeen argument injecteert.,dit zeer vaak voorkomende scenario:

    Je kon natuurlijk, het toevoegen van een werkelijke armatuur bestand, maar in de echte wereld gevallen is het misschien niet een optie, in plaats daarvan kunnen we mock het kader van de manager uitgang om een StringIO object:

    Er is niets bijzonders hier, behalve de magie __enter__ methode, maar vergeet niet de onderliggende mechanismen van context managers en een aantal slimme gebruik van onze trouwe MagicMock .,

    spottende klasse attributen

    Er zijn vele manieren om dit te bereiken, maar sommige zijn meer fool-proof dan andere. Stel dat u de volgende code hebt geschreven:

    u kunt de code zonder mocks op twee manieren testen:

    1. als de code in de test het attribuut opent via self.ATTRIBUTE, wat in dit voorbeeld het geval is, kunt u het attribuut eenvoudig direct in de instantie instellen. Dit is vrij veilig als de wijziging is beperkt tot deze enkele instantie.,
    2. u kunt ook het attribuut in de geïmporteerde klasse in de test instellen voordat u een instantie maakt. Dit verandert echter het class-attribuut dat u in uw test hebt geïmporteerd, wat van invloed kan zijn op de volgende tests, dus u moet onthouden om het opnieuw in te stellen.

    het belangrijkste nadeel van deze niet-Mock benaderingen is dat als je op enig moment de naam van het attribuut te hernoemen uw tests zullen mislukken en de fout zal niet direct wijzen op deze naamgeving mismatch.,

    om op te lossen dat we gebruik kunnen maken van patch.object op de geïmporteerde klasse die zal klagen als de klasse niet het opgegeven attribuut heeft.

    Hier zijn enkele tests die elke techniek gebruiken:

    spottende klasse helpers

    het volgende voorbeeld is de oorzaak van veel problemen met monkeypatching met Mock. Het verschijnt meestal op meer volwassen codebases die beginnen gebruik te maken van frameworks en helpers op klasse definitie., Stel je bijvoorbeeld deze hypothetische Field class helper:

    Het doel is om een attribuut in een andere klasse te wikkelen en te verbeteren, een redelijk patroon dat je vaak in ORMs of bibliotheken ziet. Maak je niet te veel zorgen over de details ervan, maar merk op dat er een type en default waarde wordt doorgegeven.,

    neem Nu een andere voorbeeld van de productie code:

    Deze klasse maakt gebruik van de Field klasse door het definiëren van de country – kenmerk als één waarvan het type is str en een default als het eerste element van het COUNTRIES constante. De logica die wordt getest is dat afhankelijk van het land een korting wordt toegepast.,

    waarvoor we de volgende test zouden kunnen schrijven:

    maar dat zou niet slagen.,

    Laten lopen door middel van de test:

    1. Eerste patches van de standaard landen in pricer om een lijst met een single entry GB ,
    2. Dit moet de CountryPricer.country – kenmerk standaard die vermelding, omdat het de definitie omvat default=COUNTRIES ,
    3. Het dan instanciates de CountryPricer en vraagt voor de gereduceerde prijs voor de GB !

    dus wat is er aan de hand?,

    de oorzaak hiervan ligt in het gedrag van Python tijdens het importeren, het best beschreven in Luciano Ramalho ‘ s uitstekende vloeiende Python in hoofdstuk 21:

    voor klassen is het verhaal anders: tijdens het importeren voert de interpreter de body van elke klasse uit, zelfs de body van klassen die in andere klassen zijn genest. Uitvoering van een klasse-lichaam betekent dat de attributen en methoden van de klasse worden gedefinieerd, en dan wordt het klasse-object zelf gebouwd.,

    wanneer we dit Toepassen op ons voorbeeld:

    1. de country attribuut Field exemplaar is gebouwd voor de test liep op tijd voor importeren,
    2. Python leest het lichaam van de klas, het doorgeven van de COUNTRIES die is gedefinieerd op dat moment aan het Field bijvoorbeeld
    3. Onze test code wordt uitgevoerd, maar het is te laat voor ons om de patch COUNTRIES en een juiste bewering.,

    uit de bovenstaande beschrijving kunt u proberen het importeren van de module uit te stellen tot binnen de tests, iets als:

    Dit zal echter nog steeds niet doorgaan alsmock.patchzal importeren de module en dan monkeypatch het, wat resulteert in dezelfde situatie als voorheen.,

    om dit Te omzeilen moeten we omarmen de staat van de CountryPricer klasse op het moment van de test en patch default op de reeds geïnitialiseerd Field bijvoorbeeld:

    Deze oplossing is niet ideaal, omdat het vereist kennis van het inwendige van de Field die je niet hebt of wilt gebruiken als u gebruikmaakt van een externe bibliotheek, maar het werkt op dit weliswaar vereenvoudigd voorbeeld.,

    Dit importtijdprobleem is een van de belangrijkste problemen die ik heb ondervonden tijdens het gebruik van unittest.mock. U moet tijdens het gebruik onthouden dat bij het importeren van de tijd code op het hoogste niveau van modules wordt uitgevoerd, met inbegrip van klasse lichamen.

    als de logica die u test afhankelijk is van een van deze logica, moet u mogelijk heroverwegen hoe u patch gebruikt.,

    het afronden van

    deze inleiding en cheatsheet zou je moeten laten spotten metunittest.mock, maar ik raad je aan om de documentatie grondig te lezen, er zijn nog veel meer interessante trucs om te leren.

    Het is vermeldenswaard dat er alternatieven zijn voor unittest.mock, in het bijzonder Alex Gaynor’s pretend bibliotheek in combinatie met pytest ‘ s monkeypatch fixture. Alex wijst erop dat je met behulp van deze twee bibliotheken een andere aanpak kunt kiezen om spot te drijven strenger en voorspelbaarder te maken., Zeker een benadering die het verkennen waard is, maar buiten het bereik van dit artikel.

    unittest.mock is momenteel de standaard voor spot in Python en je zult het in vrijwel elke codebase vinden. Het hebben van een solide begrip van het zal u helpen betere tests sneller te schrijven.

    Articles

    Geef een reactie

    Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *