Python spottende er is iets unintuitive over u. Je bent niet gemakkelijk om grip op te krijgen en het duurt een aantal ” Ah, nu snap ik het!”momenten voordat je beter wordt begrepen., Je bent een krachtige, fantastische tool voor het inschakelen van geautomatiseerde unit tests, maar ik ken veel ontwikkelaars die “krijgen door” op de kennis die ze hebben en het schrijven van deze tests duurt langer dan het zou moeten.
er zijn veel artikelen over bespotten, en ja, ik schrijf er nu nog een. Echter, Ik wil echt een poging doen om het duidelijk uit te leggen, vooral die ” Aha!”momenten die mijn begrip naar voren brachten.
ten eerste, wat is spot? Hier is een citaat uit de docs:
mock is een bibliotheek voor het testen in Python., Hiermee kunt u delen van uw systeem te testen vervangen door mock-objecten en beweringen maken over hoe ze zijn gebruikt.
dus “het staat je toe om delen van je systemen te vervangen” , maar welke delen? Het blijkt dat je vrijwel alles wat je kunt definiëren kunt mockeren, zoals:
- functies
- klassen
- objecten
je vervangt ze door “mock objecten” die instanties zijn van de Mock of MagicMock klasse.,
je “assertions” maakt over die Mock instantie, wat een manier is om te controleren of de Mock instantie werd gebruikt op de manier die je verwachtte.
in dit artikel ga ik de basisprincipes behandelen van het vervangen van onderdelen van uw systemen, maar Ik zal niet ingaan op het maken van beweringen.
laten we beginnen met een eenvoudig voorbeeld. Ik ga een nieuwe module maken genaamd simple.py en in die module ga ik een functie definiëren genaamd simple_function die gewoon een tekenreeks als volgt retourneert:
def simple_function():
return "You have called simple_function"
nu ga ik een tweede module aanmaken genaamd use_simple.py., In die module ga ik het mock pakket importeren van unittest en de eenvoudige module importeren:
from unittest import mockimport simple
Vervolgens ga ik twee functies schrijven, één die simple_function normaal aanroept en één die de aanroep ernaar Spot. Merk op dat je dit normaal gesproken in een testcontext zou doen, maar ik ben dat doelbewust aan het verwijderen, zodat ik me kan concentreren op het spotgedeelte. De eerste functie:
def use_simple_function():
result = simple.simple_function()
print(result)use_simple_function()
zoals je kunt zien wordt het resultaat van simple_function afgedrukt., Specifiek is de uitvoer:
You have called simple_function
voor de tweede functie, om simple_function te mockeren, ga ik de mock gebruiken.patch decorateur. Deze decorateur kunt u aangeven wat u wilt bespotten door het passeren van een string in het formaat ‘pakket.module.Functienaam”., In ons voorbeeld is er geen pakket, maar de module heet simple en de functie heet simple_function, dus de decorator zal eruit zien als:
@mock.patch('simple.simple_function')
We moeten dan onze tweede functie als volgt schrijven:
@mock.patch('simple.simple_function')
def mock_simple_function():
Ik heb het mock_simple_function genoemd, maar het kan alles worden genoemd. We missen hier een parameter omdat wanneer u een functie versieren met @mock.patch het zal een instantie van de magicmock klasse (een magicmock object) die wordt gebruikt om de functie die u bespotten vervangen., Dus het zal er zo uitzien:
@mock.patch('simple.simple_function')
def mock_simple_function(mock_simple_func):
dus door @mock te specificeren.patch (‘eenvoudig.simple_function’) Ik zeg dat ik simple_function wil vervangen door het magicmock object toegewezen aan de parameter genaamd mock_simple_func.
laten we eerst de functie uitbreiden door mock_simple_func uit te printen en het vervolgens aan te roepen:
als het wordt uitgevoerd, is de uitvoer:
<MagicMock name='simple_function'>
wat een MagicMock-object is dat zal worden aangeroepen in plaats van simple_function., Het is belangrijk om op te merken dat de naam attribuut van het object is het ding dat wordt bespot, en de id is een uniek nummer voor het object.
als we nu ook simple_function uitprinten (maar het niet noemen):
die de uitvoer produceert:
<MagicMock name='simple_function'>
<MagicMock name='simple_function'>
kunt u zien dat aan de overeenkomende ID ‘ s dat mock_simple_func hetzelfde magicmock object is als simple_function.
So far so good. We hebben simple_function bespot. Om expliciet te zijn, simple_function is bespot omdat het is vervangen door een magicmock object.,
laten we de regels toevoegen uit de eerste functie:
wanneer uitgevoerd, is de uitvoer:
zoals u kunt zien aan de derde regel van de uitvoer wanneer simple_function daadwerkelijk wordt genoemd, maakt het een ander MagicMock object aan. Onthoud dat aangezien we simple_function bespotten, het eigenlijk een MagicMock object is, dus als we simple_function noemen, roepen we eigenlijk het magicmock object!
voor mij is dit waar de verwarring begint.
- Waarom is een nieuw magicmock-object aangemaakt?
- en hoe kun je in hemelsnaam een object aanroepen?, Normaal krijg je” TypeError: ‘****’ object is not callable ” wanneer je het probeert.
laten we even pauzeren om nader te kijken naar MagicMock.
MagicMock heeft magie in zijn naam omdat het standaard implementaties heeft van de meeste python magische methoden. Wat is een python magische methode die je vraagt? Het zijn allemaal speciale python functies die dubbele underscore hebben aan het begin en einde van hun naam. U kunt een lijst van hen hier vinden. Degene van belang hier is__call__., De oproep functie wordt beschreven als:
Aangeroepen wanneer een object wordt genoemd als een functie
Dus als een klasse implementeert deze functie kunt u een instantie van de klasse, en dan roepen dat bijvoorbeeld als een functie d.w.z. dat :
Daarom, als MagicMock wordt genoemd als een functie, de __oproep__ functie wordt aangeroepen. MagicMock ‘ s implementatie van deze functie creëert een nieuwe mock!
dus als we simple_function() aanroepen wordt een nieuwe mock aangemaakt en geretourneerd. Niet alleen dat, maar we hebben het concept van ouder en kind bespot., Als de ene spot een andere creëert wordt het de ouder en de nieuw gecreëerde het kind. Die kinderspot kan andere spotjes creëren zodat je eindigt met een erforde van spotobjecten.
leerpunt 1: wanneer een magicmock-object als een functie wordt aangeroepen, maakt en retourneert het standaard een nieuw MagicMock-object.
terug naar ons voorbeeld. Tot nu toe hebben we net bespot simple_function maar niets anders gedaan dan het afdrukken van de MagicMocks die als resultaat zijn gemaakt. Stel dat ik wilde veranderen wat werd geretourneerd uit simple_function (), hoe zou ik dat doen?, Ik zou dat doen met behulp van de return_value eigenschap van MagicMock als zodanig (ik ga ook de eerste twee afdrukken verwijderen omdat ze niet langer nodig zijn):
wanneer uitgevoerd wordt, is de uitvoer:
You have mocked simple_function
zoals je kunt zien wanneer simple_function() nu de MagicMock return_value wordt geretourneerd in plaats van een tweede magicmock object te maken.
leerpunt 2: Stel return_value in om te veranderen wat er wordt geretourneerd wanneer een magicmock object als een functie wordt aangeroepen.
Als u meer wilt doen dan de retourwaarde wijzigen, kunt u de MagicMock gebruiken.side_effect functie., Hiermee kunt u een geheel nieuwe functie opgeven die zal worden aangeroepen in plaats van degene die u bespot. Laten we dit doen. Ten eerste zal ik een nieuwe functie definiëren die het side_effect zal zijn (merk op dat het dezelfde parameter moet hebben ingesteld als de functie die u bespot):
def side_effect_function():
return "SKABLAM!"
nu kunnen we een nieuwe functie definiëren die deze bijwerking als zodanig gebruikt:
alles is hetzelfde als de mock_simple_function verderop behalve dat ik het side_effect van mock_simple_func in plaats van return_value stel.,
De uitvoer is:
SKABLAM!
een goed gebruik voor het gebruik van neveneffect is als u een foutstroom wilt testen en daarom een uitzondering wilt maken in uw test. Ik zal de functie side_effect_ wijzigen om in plaats daarvan een fout te maken:
def side_effect_function():
raise FloatingPointError("A disastrous floating point error has occurred")
nu is de uitvoer (ik heb een deel van de traceback verwijderd voor duidelijkheid):
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
leerpunt 3: om meer te doen dan alleen de return_value van een MagicMock te veranderen, stel side_effect in.
spottende klassen
tot nu toe hebben we zojuist een functie bespot, maar hoe zit het met een klasse?, Laten we definiëren een klasse in de simple.py genoemd SimpleClass met een methode genaamd exploderen:
class SimpleClass(object):
def explode(self):
return "KABOOM!"
Nu in use_simple.py laten we schrijven een functie die gebruik maakt van SimpleClass en dan die functie aanroepen:
def use_simple_class():
inst = simple.SimpleClass()
print(inst.explode())use_simple_class()
Wanneer het uitvoeren van de uitvoer is:
KABOOM!
laten we Nu doen definieer een tweede functie en kiezen te bespotten het geheel van simple_class met behulp van de @mock.patch decorator:
@mock.patch("simple.SimpleClass")
def mock_simple_class(mock_class):
als bij het bespotten van een functie, de @mock.,patch decorator passeert een MagicMock object dat de klasse die u bespotten vervangt in de functie die het is versieren. Het magicmock object wordt in dit geval toegewezen aan het argument mock_class.
Op dit moment zal ik gewoon het magicmock object printen en dan de functie uitvoeren:
@mock.patch("simple.SimpleClass")
def mock_simple_class(mock_class):
print(mock_class)mock_simple_class()
wanneer de uitvoer is:
<MagicMock name='SimpleClass'>
zoals met de spotfunctie een magicmock object wordt gemaakt en doorgegeven., Laten we nu SimpleClass uitprinten zodat je kunt zien dat het is bespot en vervangen door hetzelfde MagicMock object:
@mock.patch("simple.SimpleClass")
def mock_simple_class(mock_class):
print(mock_class)
print(simple.SimpleClass)
De uitvoer is nu:
<MagicMock name='SimpleClass'>
<MagicMock name='SimpleClass'>
laten we een instantie van SimpleClass maken, dat wil zeggen SimpleClass () aanroepen, en het dan uitprinten. Onthoud, aangezien we SimpleClass bespotten, wat er eigenlijk gebeurt is dat we het magicmock object aanroepen als een functie:
De uitvoer is:
dus zoals verwacht, creëert het aanroepen van SimpleClass() en dus het aanroepen van het magicmock object als een functie een nieuw MagicMock object.,
tot nu toe leek het bespotten van een klasse op het bespotten van een functie. Echter, in werkelijkheid als we een klasse definiëren zullen we dan objecten maken met behulp van die klasse en het zijn die objecten vaker wel dan niet die we echt willen bespotten. De vraag is, hoe bespot u een instantie gemaakt van een klasse? Nou, als je een klasse bespot en als gevolg daarvan een MagicMock object wordt gemaakt, als je refereert naar return_value op dat magicmock object maakt het een nieuw MagicMock object dat de instantie van de gemaakte klasse vertegenwoordigt. Verdorie! Dat was anderhalve zin! Dit was zeker een van mijn ” Aha!,”momenten, maar zelfs het opschrijven maakt het niet duidelijk. Laten we return_value uitprinten zodat je kunt zien wat ik bedoel:
De uitvoer is:
de derde regel is van het afdrukken van de instantie van de gemaakte klasse, en de vierde regel is van het afdrukken van de return_value van het magicmock object doorgegeven in de functie. Zoals je kunt zien verwijzen ze naar hetzelfde MagicMock object.
leerpunt 4: Wanneer u een klasse bespot, wordt een magicmock-object aangemaakt. Wanneer u een instantie van die klasse maakt, wordt een nieuw magicmock object aangemaakt., Als je refereert naar de return_value van het magicmock object van die klasse krijg je hetzelfde magicmock object dat werd gemaakt toen de instantie van die klasse werd gemaakt.
” Ahhhhh, waarom is dat belangrijk?!?!?”Ik hoor je huilen!
“It is important if you want to mock the explode function” I bellow in return!
de volgende vraag die u moet stellen is: is de methode die ik een class-methode of een instance-methode wil bespotten? Want hoe je de return_value van de methode verandert zal verschillen afhankelijk van het antwoord.,
als de methode een klasse methode was, dan zou je de return_value als volgt instellen:
MagicMockObject.ClassMethodName.return_value = "A delightful return value"
maar als het een instantie methode was, zou je doen:
MagicMockObject.return_value.InstanceMethodName.return_value = "A daring return value"
met dat in gedachten laten we de return_value van explode veranderen, wat een instantie methode is (Ik zal de meeste van de print statements verwijderen voor de duidelijkheid) :
in de bovenstaande code, mock_class.return_value geeft het magicmock object terug dat de instantie van SimpleClass vertegenwoordigt.
mock_class.retourwaarde.,explode geeft het MagicMock object dat de explode methode van de instantie van SimpleClass vertegenwoordigt.
stelt daarom de return_value van mock_class in.retourwaarde.explode stelt de return_value in van de explode methode van de instantie van SimpleClass.
De uitvoer is:
BOO!
net als bij het bespotten van een functie, kunt u ook het side_effect instellen voor methoden van klassen en objecten.
Ik hoop dat dit artikel je geholpen heeft om de spot te begrijpen. Beveel het aan als je het leuk vond en voel je vrij om een reactie achter te laten, omdat ik graag van je zou horen.