Python spottende, at der er noget ulogisk om dig. Du er ikke let at få fat i, og det tager flere ” Ah, nu får jeg det!”øjeblikke før du bliver bedre forstået., Du er et kraftfuldt, fantastisk værktøj til aktivering af automatiserede enhedstest, men jeg kender mange udviklere, der “klarer sig” på den viden, de har, og at skrive disse test tager længere tid, end det burde.
Der er mange artikler om mocking, og ja, jeg skriver nu en anden. Imidlertid, jeg virkelig ønsker at have en gå på klart at forklare det, især dem “Aha!”øjeblikke, der skubbede frem min forståelse.
for det første, hvad er mocking? Her er et citat fra docs:
mock er et bibliotek til test i Python., Det giver dig mulighed for at udskifte dele af dit system under test med mock objekter og fremsætte påstande om, hvordan de er blevet brugt.
så “det giver dig mulighed for at udskifte dele af dine systemer”, men hvilke dele? Det viser sig, at du kan håne stort set alt, hvad du kan definere, sådan som:
- funktioner
- klasser
- objects
Du udskifte dem med “mock-objekter”, der er forekomster af Mock eller MagicMock klasse.,
du “gør påstande” om den Mock-instans, hvilket er en måde at kontrollere, at Mock-forekomsten blev brugt på den måde, du forventede.
i denne artikel vil jeg dække det grundlæggende ved udskiftning af dele af dine systemer, men jeg vil ikke dække at fremsætte påstande.
lad os starte med et simpelt eksempel. Jeg vil oprette et nyt modul kaldet simple.py og i det modul vil jeg definere en funktion kaldet simple_function, der bare returnerer en streng som sådan:
def simple_function():
return "You have called simple_function"
nu skal jeg oprette et andet modul kaldet use_simple.py., I dette modul vil jeg importere mock-pakken fra unittest og importere det enkle modul:
from unittest import mockimport simple
næste vil jeg skrive to funktioner, en der kalder simple_function normalt og en der håner opkaldet til det. Bemærk, at du normalt ville gøre dette i en testkontekst, men jeg fjerner målrettet det væk, så jeg kan fokusere på den spottende del. Den første funktion:
def use_simple_function():
result = simple.simple_function()
print(result)use_simple_function()
som du kan se, udskriver det bare resultatet af simple_function., Specifikt er output:
You have called simple_function
For den anden funktion, for at mock simple_function vil jeg bruge mock.patch dekoratør. Denne dekoratør kan du angive, hvad du ønsker at håne ved at sende det en streng i formatet ‘pakke.modul.FunctionName’., I vores eksempel er der ingen pakke, men det modul, der kaldes simpel og funktionen kaldes simple_function, så dekoratør vil se ud som dette:
@mock.patch('simple.simple_function')
så er Vi nødt til at skrive vores anden funktion som så:
@mock.patch('simple.simple_function')
def mock_simple_function():
jeg har kaldt det mock_simple_function, men det kan kaldes noget. Vi mangler en parameter her, fordi når du dekorere en funktion med @mock.patch det vil passere en forekomst af MagicMock-klassen (et MagicMock-objekt), der bruges til at erstatte den funktion, du håner., Så det vil se sådan ud:
@mock.patch('simple.simple_function')
def mock_simple_function(mock_simple_func):
så ved at angive @mock.patch (‘simpelt.simple_function’) jeg siger, at jeg vil erstatte simple_function med MagicMock-objektet tildelt parameteren kaldet mock_simple_func.
lad os først udfolde funktionen ved at udskrive mock_simple_func og derefter kalde den:
Når du kører, er output:
<MagicMock name='simple_function'>
hvilket er et MagicMock-objekt, der kaldes i stedet for simple_function., Det er vigtigt at bemærke, at objektets navnattribut er den ting, der bliver hånet, og id ‘ et er et unikt nummer for objektet.
Hvis vi nu også udskrive simple_function (men ikke kalde det):
Der producerer output:
<MagicMock name='simple_function'>
<MagicMock name='simple_function'>
Du kan se, at fra den matchende id ‘ er, der mock_simple_func er den samme MagicMock objekt som simple_function.
indtil videre så godt. Vi har hånet simple_function. For at være eksplicit er simple_function blevet hånet, fordi den er blevet erstattet med et MagicMock-objekt.,
Lad os tilføje linjerne fra den første funktion:
Når køre, output er:
Som du kan se fra den tredje linje af output, når simple_function er faktisk hedder det skaber et anderledes MagicMock objekt. Husk, at da vi har hånet simple_function, er det faktisk et MagicMock-objekt, så når vi kalder simple_function, kalder vi faktisk MagicMock-objektet!
for mig er det her forvirringen sætter ind.
- hvorfor oprettes et nyt MagicMock-objekt?
- og hvordan I alverden kan du alligevel kalde et objekt?, Du får normalt” TypeError: ‘****’ object is not callable”, når du prøver.
lad os holde pause i et minut for at se nærmere på MagicMock.
MagicMock har magi i sit navn, fordi det har standard implementeringer af de fleste af python magiske metoder. Hvad er en python magisk metode, du spørger? Det er alle specielle python-funktioner, der har dobbelt understregning i starten og slutningen af deres navn. Du kan finde en liste over dem her. Den af interesse her er __call__., Call funktion er beskrevet som:
Kaldt, når et objekt er indkaldt som en funktion
Så hvis en klasse implementerer denne funktion kan du oprette en instans af klassen, og derefter kalder denne instans som en funktion, dvs :
Derfor er det, når MagicMock er indkaldt som en funktion, de __opkald__ funktionen kaldes. Magicmocks implementering af denne funktion skaber en ny mock!
så når vi kalder simple_function() oprettes og returneres en ny mock. Ikke kun det, men vi har begrebet forældre og barn mocks., Når en mock skaber en anden, bliver det forælderen og den nyoprettede barnet. At barnet mock kunne skabe andre mocks, så du ender med et heirarchy af mock objekter.læringspunkt 1: Når et MagicMock-objekt kaldes som en funktion, opretter og returnerer det som standard et nyt MagicMock-objekt.
Tilbage til vores eksempel. Indtil videre har vi lige hånet simple_function, men ikke gjort noget med det andet end at udskrive de MagicMocks, der er oprettet som et resultat. Sig, at jeg ville ændre, hvad der blev returneret fra simple_function(), hvordan ville jeg gøre det?, Jeg vil gøre det ved hjælp af den return_value ejendom MagicMock som så (jeg er også kommer til at fjerne de to første tryk som de ikke længere er nødvendige):
Når køre, output er:
You have mocked simple_function
Som du kan se, når simple_function (), der nu kaldes MagicMock return_value er tilbage i stedet for at skabe en anden MagicMock objekt.læringspunkt 2: for at ændre, hvad der returneres, når et MagicMock-objekt kaldes som en funktion, skal du indstille return_value.
Hvis du vil gøre mere end at ændre returværdien, kan du bruge MagicMock.side_effect funktion., Det giver dig mulighed for at angive en helt ny funktion, der vil blive kaldt i stedet for den, du mocker. Lad os gøre det. For det første vil jeg definere en ny funktion, der vil være side_effect (bemærk, det skal have den samme parameter, der som den funktion, du er spottende):
def side_effect_function():
return "SKABLAM!"
Nu kan vi definere en ny funktion, der bruger denne bivirkning som så:
Alt er det samme som mock_simple_function yderligere op, bortset fra at jeg er indstilling af side_effect af mock_simple_func i stedet for return_value.,
udgangen er:
SKABLAM!
en god brugssag til brug af bivirkning er, hvis du vil teste en fejlstrøm, og derfor vil du hæve en undtagelse i din test. Jeg vil ændre side_effect_function til at udløse en fejl, i stedet for:
def side_effect_function():
raise FloatingPointError("A disastrous floating point error has occurred")
Nu er output (jeg har fjernet en del af tilbagesporing til klarhed):
Traceback (most recent call last):
File "use_simple.py”, line 18, in side_effect_function
raise FloatingPointError("A disastrous floating point error has occurred”)
FloatingPointError: A disastrous floating point error has occurred
Læring punkt 3: At gøre mere end blot at ændre return_value af en MagicMock, sæt side_effect.
spottende klasser
indtil videre har vi lige hånet en funktion, men hvad med en klasse?, Lad os definere en klasse, der i simple.py kaldes SimpleClass med en metode kaldet eksplodere:
class SimpleClass(object):
def explode(self):
return "KABOOM!"
Nu er i use_simple.py lad os skrive en funktion, der bruger SimpleClass og derefter kalde denne funktion:
def use_simple_class():
inst = simple.SimpleClass()
print(inst.explode())use_simple_class()
Når du kører output er:
KABOOM!
lad os Nu gøre definere en anden funktion og vælger at håne hele simple_class hjælp @mock.patch dekoratør:
@mock.patch("simple.SimpleClass")
def mock_simple_class(mock_class):
som når spottende en funktion, @mock.,patch dekoratør passerer en MagicMock objekt, der erstatter den klasse, du spotter ind i den funktion, det er udsmykning. MagicMock-objektet i dette tilfælde er tildelt argumentet mock_class.
jeg vil bare udskrive MagicMock objekt og derefter køre den funktion:
@mock.patch("simple.SimpleClass")
def mock_simple_class(mock_class):
print(mock_class)mock_simple_class()
Når du kører output er:
<MagicMock name='SimpleClass'>
Som med spottende, en funktion, en MagicMock objektet er oprettet, og gik i., Nu giver mulighed for at udskrive SimpleClass, så du kan se det er blevet hånet og erstattet med det samme MagicMock objekt:
@mock.patch("simple.SimpleClass")
def mock_simple_class(mock_class):
print(mock_class)
print(simple.SimpleClass)
output nu er:
<MagicMock name='SimpleClass'>
<MagicMock name='SimpleClass'>
Lad os oprette en instans af SimpleClass dvs opkald SimpleClass(), og derefter printe den ud. Husk, da vi har spottet SimpleClass, hvad der faktisk vil ske, er, at vi vil være at kalde MagicMock objekt, som en funktion:
output er:
Så som forventet, ringer SimpleClass (), og derfor ringer de MagicMock objekt som en funktion opretter en ny MagicMock objekt.,indtil nu har spottende en klasse været meget som at spotte en funktion. Imidlertid, i virkeligheden, når vi definerer en klasse, vil vi derefter oprette objekter ved hjælp af denne klasse, og det er disse objekter oftere end ikke, at vi virkelig ønsker at håne. Spørgsmålet er, hvordan du mocker en instans oprettet fra en klasse? Nå, når du mocker en klasse, og derfor oprettes et MagicMock-objekt, hvis du henviser til return_value på det MagicMock-objekt, opretter det et nyt MagicMock-objekt, der repræsenterer forekomsten af den oprettede klasse. Hold da kæft! Det var halvanden sætning! Dette var bestemt en af mine ” Aha!,”øjeblikke, men selv at skrive det ned gør det ikke klart. Lad os printe ud return_value, så du kan se hvad jeg mener:
output er:
Den tredje linje er fra udskrivning instans af klassen skabt, og den fjerde linje er fra udskrivning af return_value af MagicMock objekt bestået i at fungere. Som du kan se, henviser de til det samme MagicMock-objekt.
læringspunkt 4: Når du håner en klasse, oprettes et MagicMock-objekt. Når du opretter en forekomst af denne klasse, oprettes et nyt MagicMock-objekt., Når du henviser til returværdien af klassens MagicMock-objekt, får du det samme MagicMock-objekt, der blev oprettet, da forekomsten af den klasse blev oprettet.
“Ahhhhh, hvorfor er det vigtigt?!?!?”Jeg hører dig græde!
“det er vigtigt, hvis du vil mocke eksploderings funktionen” jeg Bello!til gengæld!det næste spørgsmål, du skal stille, er: er den metode, jeg vil mocke en klassemetode eller en instansmetode? Fordi hvordan du ændrer return_value af metoden vil være anderledes afhængigt af svaret.,
Hvis den metode, der var en klasse-metode, så du vil indstille return_value som så:
MagicMockObject.ClassMethodName.return_value = "A delightful return value"
Men hvis det var et eksempel på metode ville du gøre:
MagicMockObject.return_value.InstanceMethodName.return_value = "A daring return value"
Med det i tankerne, lad os ændre return_value af eksplodere, der er et eksempel på metode (jeg vil fjerne de fleste af de trykte redegørelser for klarhedens skyld) :
I koden ovenfor, mock_class.return_value returnerer MagicMock-objektet, der repræsenterer forekomsten af SimpleClass.
mock_class.returværdi.,eksplodere returnerer MagicMock-objektet, der repræsenterer eksploderingsmetoden for forekomsten af SimpleClass.
indstiller derfor returværdien af mock_class.returværdi.e explodeplode angiver returværdien af e explodeplode-metoden for forekomsten af SimpleClass.
udgangen er:
BOO!
ligesom når du håner en funktion, kan du også indstille side_effect for metoder til klasser og objekter.
Jeg håber, at denne artikel hjalp dig med at forstå mocking lidt mere. Venligst anbefale det, hvis du kunne lide det og velkommen til at efterlade et svar, som jeg ville elske at høre fra dig.