python hånar det finns något ointuitivt om dig. Du är inte lätt att komma till rätta med och det tar flera ”Ah, nu får jag det!”ögonblick innan du är bättre förstådd., Du är ett kraftfullt, fantastiskt verktyg för att möjliggöra automatiserade enhetstester, men jag vet att många utvecklare som ”klarar” på den kunskap de har och skriver dessa tester tar längre tid än det borde.
det finns många artiklar om mocking, och ja, jag skriver nu en annan. Men jag vill verkligen gå på att tydligt förklara det, särskilt de ” aha!”ögonblick som drev fram min förståelse.
För det första, vad hånar? Här är ett citat från docs:
mock är ett bibliotek för testning i Python., Det gör att du kan ersätta delar av ditt system under test med mock objekt och göra påståenden om hur de har använts.
så ”det låter dig byta ut delar av dina system”, men vilka delar? Det visar sig att du kan håna ganska mycket vad du kan definiera, till exempel:
- funktioner
- klasser
- objekt
du ersätter dem med ”mock objekt” som är instanser av Mock eller MagicMock klass.,
du gör sedan påståenden om den här Mock-instansen, vilket är ett sätt att kontrollera att Mock-instansen användes på det sätt du förväntade dig.
i den här artikeln kommer jag att täcka grunderna för att ersätta delar av dina system, men jag kommer inte att täcka att göra påståenden.
låt oss börja med ett enkelt exempel. Jag kommer att skapa en ny modul som heter simple.py och i den modulen kommer jag att definiera en funktion som heter simple_function som bara returnerar en sträng som så:
def simple_function():
return "You have called simple_function"
Nu ska jag skapa en andra modul som heter use_simple.py. – herr talman!, I den modulen kommer jag att importera mock-paketet från unittest och importera den enkla modulen:
from unittest import mockimport simple
nästa kommer jag till en skriv två funktioner, en som kallar simple_function normalt och en som hånar samtalet till den. Observera att normalt skulle du göra detta i ett test sammanhang men jag är målmedvetet strippa bort det så jag kan fokusera på hånfulla delen. Den första funktionen:
def use_simple_function():
result = simple.simple_function()
print(result)use_simple_function()
som du kan se det bara skriver ut resultatet av simple_function., Specifikt är utgången:
You have called simple_function
för den andra funktionen, för att håna simple_function kommer jag att använda mock.patch dekoratör. Denna dekoratör kan du ange vad du vill håna genom att skicka den en sträng i formatet ” paket.modul.FunctionName’., I vårt exempel finns det inget paket, men modulen kallas enkel och funktionen kallas simple_function, så dekoratorn kommer att se ut:
@mock.patch('simple.simple_function')
Vi måste sedan skriva vår andra funktion som så:
@mock.patch('simple.simple_function')
def mock_simple_function():
Jag har kallat det mock_simple_function, men det kan kallas någonting. Vi saknar en parameter här för när du dekorerar en funktion med @mock.patch det kommer att passera en instans av MagicMock klass (en MagicMock objekt) som används för att ersätta den funktion du hånar., Så det kommer att se ut så här:
@mock.patch('simple.simple_function')
def mock_simple_function(mock_simple_func):
så genom att ange @mock.patch (’enkelt.simple_function’) jag säger att jag vill ersätta simple_function med MagicMock-objektet som tilldelats parametern mock_simple_func.
låt oss först dra ut funktionen genom att skriva ut mock_simple_func och sedan kalla den:
Vid körning är utmatningen:
<MagicMock name='simple_function'>
vilket är ett MagicMock-objekt som kommer att kallas istället för simple_function., Det är viktigt att notera objektets namnattribut är det som hånas, och id är ett unikt nummer för objektet.
om vi nu också skriver ut simple_function (men inte kallar det):
som producerar utdata:
<MagicMock name='simple_function'>
<MagicMock name='simple_function'>
Du kan se det från matchande ID som mock_simple_func är samma MagicMock-objekt som simple_function.
hittills så bra. Vi har hånat simple_function. För att vara explicit, simple_function har hånats eftersom det har ersatts med en MagicMock objekt.,
låt oss lägga till rader i från den första funktionen:
när körningen, utgången är:
som du kan se från den tredje raden av utdata när simple_function faktiskt kallas det skapar en annan MagicMock objekt. Kom ihåg att eftersom vi har hånat simple_function, det är faktiskt en MagicMock objekt, så när vi kallar simple_function vi faktiskt ringer MagicMock objektet!
för mig är det här förvirringen sätter in.
- Varför skapas ett nytt MagicMock-objekt?
- och hur i hela friden kan du ringa ett objekt ändå?, Du får normalt ”TypeError:’ * * * * ”objektet är inte callable” när du försöker.
låt oss pausa en minut för att titta närmare på MagicMock.
MagicMock har magi i sitt namn eftersom det har standard implementeringar av de flesta av python magiska metoder. Vad är en python magisk metod du frågar? Det är alla speciella python-funktioner som har dubbel understreck i början och slutet av deras namn. Du kan hitta en lista över dem här. Den av intresse här är__call__., Anropsfunktionen beskrivs som:
anropas när ett objekt anropas som en funktion
Så om en klass implementerar denna funktion kan du skapa en instans av klassen och sedan anropa den instansen som en funktion, dvs.:
därför anropas funktionen __call__. Magicmocks genomförande av denna funktion skapar en ny mock!
så när vi kallar simple_function() skapas och returneras en ny mock. Inte bara det, men vi har begreppet förälder och barn hånar., När en mock skapar en annan blir föräldern och den nyskapade barnet. Att barn mock kan skapa andra hånar så att du hamnar med en arvinge av håna objekt.
Learning point 1: när ett MagicMock-objekt kallas som en funktion skapas och returnerar det som standard ett nytt MagicMock-objekt.
tillbaka till vårt exempel. Hittills har vi bara hånat simple_function men inte gjort något med det annat än att skriva ut Magicmocksna som skapas som ett resultat. Säg att jag ville ändra vad som returnerades från simple_function (), hur skulle jag göra det?, Jag skulle använda return_value egendom MagicMock som så (jag kommer också att ta bort de första två bilder som de inte längre behövs):
När du kör, produktionen är:
You have mocked simple_function
Som du kan se när simple_function() är nu kallas MagicMock return_value är tillbaka i stället för att skapa en andra MagicMock objekt.
Learning point 2: för att ändra vad som returneras när ett MagicMock-objekt kallas som en funktion, ställ in return_value.
om du vill göra mer än ändra returvärdet kan du använda MagicMock.side_effect funktion., Det låter dig ange en helt ny funktion som kommer att kallas istället för den du hånar. Nu kör vi. För det första, kommer jag att definiera en ny funktion som kommer att vara side_effect (observera att det måste ha samma parameteruppsättning som funktion du är hånfullt):
def side_effect_function():
return "SKABLAM!"
Nu kan vi definiera en ny funktion som använder denna bieffekt som så att:
Allt är samma som mock_simple_function längre upp utom jag ställer den side_effect av mock_simple_func istället för return_value.,
utgången är:
SKABLAM!
ett bra användningsfall för att använda bieffekt är om du vill testa ett felflöde och därför vill du höja ett undantag i ditt test. Jag kommer att ändra side_effect_function att höja ett fel istället:
def side_effect_function():
raise FloatingPointError("A disastrous floating point error has occurred")
Nu utgången (jag tog bort en del av traceback för tydlighetens skull):
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
att Lära punkt 3: Att göra mer än att bara ändra return_value av en MagicMock, ställa side_effect.
Mocking klasser
hittills har vi bara hånat en funktion, men vad sägs om en klass?, Låt oss definiera en klass i simple.py kallas SimpleClass med en metod som heter explode:
class SimpleClass(object):
def explode(self):
return "KABOOM!"
nu i use_simple.py låt oss skriva en funktion som använder SimpleClass och sedan ringa den funktionen:
def use_simple_class():
inst = simple.SimpleClass()
print(inst.explode())use_simple_class()
När kör utdata är:
KABOOM!
låt oss nu definiera en andra funktion och välja att håna hela simple_class med hjälp av @mock.patch decorator:
@mock.patch("simple.SimpleClass")
def mock_simple_class(mock_class):
som vid mocking en funktion, @mock.,patch dekoratör passerar en MagicMock objekt som ersätter den klass du hånar in i den funktion det dekorerar. Magicmock-objektet i detta fall tilldelas argumentet mock_class.
för tillfället skriver jag bara ut MagicMock-objektet och kör sedan funktionen:
@mock.patch("simple.SimpleClass")
def mock_simple_class(mock_class):
print(mock_class)mock_simple_class()
När du kör utdata är:
<MagicMock name='SimpleClass'>
som med mocking en funktion skapas ett MagicMock-objekt och skickas in., Nu kan skriva ut SimpleClass så att du kan se det har hånats och ersatts med samma MagicMock objekt:
@mock.patch("simple.SimpleClass")
def mock_simple_class(mock_class):
print(mock_class)
print(simple.SimpleClass)
utgången nu är:
<MagicMock name='SimpleClass'>
<MagicMock name='SimpleClass'>
låt oss skapa en instans av SimpleClass dvs Ring SimpleClass(), och sedan skriva ut. Kom ihåg, eftersom vi har hånat SimpleClass, vad som faktiskt kommer att hända är att vi kommer att ringa MagicMock objektet som en funktion:
utgången är:
så som förväntat, ringa SimpleClass() och därför ringa MagicMock objektet som en funktion skapar en ny MagicMock objekt.,
fram till nu, hånar en klass har varit ungefär som hånar en funktion. Men i verkligheten när vi definierar en klass kommer vi då att skapa objekt med den klassen och det är de objekten oftare än inte att vi verkligen vill håna. Frågan är, hur du håna en instans som skapats från en klass? Tja, när du håna en klass och följaktligen en MagicMock objekt skapas, om du hänvisar till return_value på det MagicMock objektet det skapar en ny MagicMock objekt som representerar instansen av klassen skapas. Jösses! Det var en och en halv mening! Detta var definitivt en av mina ” aha!,”stunder, men även skriva ner det gör det inte klart. Låt oss skriva ut return_value så att du kan se vad jag menar:
utgången är:
den tredje raden är från att skriva ut instansen av klassen som skapats, och den fjärde raden är från att skriva ut return_value av MagicMock objektet skickas in till funktionen. Som du kan se hänvisar de till samma MagicMock-objekt.
Learning point 4: När du mockar en klass skapas ett MagicMock-objekt. När du skapar en instans av den klassen skapas ett nytt MagicMock-objekt., När du hänvisar till return_value för klassens MagicMock-objekt får du samma MagicMock-objekt som skapades när instansen av den klassen skapades.
”Ahhhhh, varför är det viktigt?!?!?”Jag hör dig gråta!
”det är viktigt om du vill håna explode-funktionen” i bellow i gengäld!
nästa fråga du behöver ställa är: är metoden jag vill håna en klassmetod eller en instansmetod? Eftersom hur du ändrar return_value av metoden kommer att vara olika beroende på svaret.,
om metoden var en klassmetod skulle du ställa in return_value som så:
MagicMockObject.ClassMethodName.return_value = "A delightful return value"
men om det var en instansmetod skulle du göra:
MagicMockObject.return_value.InstanceMethodName.return_value = "A daring return value"
med det i åtanke låt oss ändra return_value av explodera, vilket är en instansmetod (jag tar bort de flesta av utskriftsdeklarationerna för tydlighet) :
i koden ovan, Mock_class.return_value returnerar objektet MagicMock som representerar instansen av SimpleClass.
mock_class.returvärde.,explode returnerar MagicMock-objektet som representerar exploderingsmetoden för instansen av SimpleClass.
ställer därför in mock_class return_value.returvärde.explodera sätter return_value av explode metoden för förekomsten av SimpleClass.
utgången är:
BOO!
ungefär som när du mockar en funktion kan du ställa in side_effect för metoder för klasser och objekt också.
Jag hoppas att den här artikeln hjälpte dig att förstå mocking lite mer. Rekommendera det om du gillade det och gärna lämna ett svar som jag skulle älska att höra från dig.