Sie missverstehen die Dokumentation. Ohne autospec
wird die side_effect
, die aufgerufen wird, buchstäblich wie ohne die ursprüngliche Deklaration zu überprüfen ist. Lassen Sie uns ein besseres Minimalbeispiel erstellen, um dieses Problem zu demonstrieren.
class Book(object):
def __init__(self):
self.valid = False
def save(self):
self.pk = 'saved'
def make_valid(self):
self.valid = True
class BookForm(object):
def __init__(self, book):
self.book = book
def is_valid(self):
return self.book.valid
class Client(object):
def __init__(self, book):
self.form = BookForm(book)
def post(self):
if self.form.is_valid() is True: # to avoid sentinel value
print('Book is valid')
else:
print('Book is invalid')
Jetzt ist Ihre ursprüngliche Test sollte
@mock.patch.object(BookForm, 'is_valid')
def test_edit(mocked_is_valid):
book = Book()
book.save()
client = Client(book)
mocked_is_valid.side_effect = lambda: True
client.post()
Durchführung des Tests mit einigen Anpassungen über die gleiche Arbeit wie verursachen ist Book is valid
zu stdout gedruckt werden, auch wenn wir nicht durch den Tanz zu gegangen sind setze das Book.valid-Flag auf "true", da der self.form.is_valid
, der in Client.post
aufgerufen wird, durch das Lambda ersetzt wird, das aufgerufen wird. Wir können dies durch einen Debugger sehen:
> /usr/lib/python3.4/unittest/mock.py(962)_mock_call()
-> ret_val = effect(*args, **kwargs)
(Pdb) pp effect
<function test_edit.<locals>.<lambda> at 0x7f021dee6730>
(Pdb) bt
...
/tmp/test.py(20)post()
-> if self.form.is_valid():
/usr/lib/python3.4/unittest/mock.py(896)__call__()
-> return _mock_self._mock_call(*args, **kwargs)
/usr/lib/python3.4/unittest/mock.py(962)_mock_call()
-> ret_val = effect(*args, **kwargs)
Auch im Rahmen des Client.post
Methodenaufruf, es ist nicht eine gebundene Methode (wir werden diese später zurück)
(Pdb) self.form.is_valid
<MagicMock name='is_valid' id='140554947029032'>
So hmm, wir könnte hier ein Problem haben: die side_effect
könnte buchstäblich alle aufrufbar sein, die anders sein könnte, was Realität ist, in unserem Fall die is_valid
Funktionssignatur (das ist die Argumentliste) könnte anders sein als die Mock, die wir bieten. Was passiert, wenn die BookForm.is_valid
Verfahren modifiziert wurde, in einem zusätzlichen Argument zu nehmen:
class BookForm(object):
def __init__(self, book):
self.book = book
def is_valid(self, authcode):
return authcode > 0 and self.book.valid
Rerun unserem Test ...und Sie werden sehen, dass unser Test hat bestanden, obwohl Client.post
noch ruft BookForm.is_valid
ohne Argumente. Ihr Produkt wird in der Produktion ausfallen, obwohl Ihr Test bestanden hat. Aus diesem Grund autospec
Argument eingeführt wird, und wir werden das in unserem zweiten Test anzuwenden, ohne das aufrufbar durch side_effect ersetzt:
@mock.patch.object(BookForm, 'is_valid', autospec=True)
def test_edit_autospec(mocked_is_valid):
book = Book()
book.save()
client = Client(book)
client.post()
Dies geschieht nun beim Aufruf der Funktion
Traceback (most recent call last):
...
File "/tmp/test.py", line 49, in test_edit_autospec
client.post()
File "/tmp/test.py", line 20, in post
if self.form.is_valid():
...
File "/usr/lib/python3.4/inspect.py", line 2571, in _bind
raise TypeError(msg) from None
TypeError: 'authcode' parameter lacking default value
Welche ist das, was Sie wollen und welche autospec
beabsichtigt zu liefern - eine Prüfung vor der Mocks genannt werden, und
In addition mocked functions/methods have the same call signature as the original so they raise a TypeError if they are called incorrectly.
So haben wir diebehebenMethode mit einem Authcode größer als 0
.
def post(self):
if self.form.is_valid(123) is True:
print('Book is valid')
else:
print('Book is invalid')
Da unser Test die Methode die is_valid
Funktion über die side_effect
aufrufbar, werden am Ende Druck Book is invalid
nicht verspotten hat.
Nun, wenn wir die side_effect
zur Verfügung stellen möchten, wird es die gleiche Signatur
@mock.patch.object(BookForm, 'is_valid', autospec=True)
def test_edit_autospec(mocked_is_valid):
book = Book()
book.save()
client = Client(book)
mocked_is_valid.side_effect = lambda self, authcode: True
client.post()
Book is valid
wird nun gedruckt werden wieder übereinstimmen müssen. durch den Debugger gehen, dass autospec
‚d und verspotteten is_valid
Objekt innerhalb des Rahmens des Client.post
Methodenaufruf ist
(Pdb) self.form.is_valid
<bound method BookForm.is_valid of <__main__.BookForm object at 0x7fd57f43dc88>>
Ah, irgendwie die Methodensignatur ein einfaches MagicMock
Objekt nicht zu inspizieren (man erinnere sich der <MagicMock name='is_valid' id='140554947029032'>
zuvor erwähnt) und ein richtig gebunden Methode, die die self
Argument bedeutet, wird nun in die Mock übergeben, die Lösung dieses:
side_effect: A function to be called whenever the Mock is called. See the side_effect
attribute. Useful for raising exceptions or dynamically changing return values. The function is called with the same arguments as the mock...
die „gleichen Argumente wie der Schein“ bedeutet in diesem Fall die gleiche wie was auch immer das in den Mock übergeben wurde. Um es zu wiederholen, der erste Fall der self.form.is_valid
wurde durch eine nackte, unbegrenzte aufrufbar ersetzt, so dass self
nie überschritten wird; und im zweiten Fall ist das Callable nun an self
gebunden, sowohl die self
als auch die authcode
werden in die side_effect
aufrufbar - genau so, was bei dem echten Anruf passieren würde. Dies sollte das wahrgenommene Fehlverhalten von Interaktionen mit autospec=True
für mock.patch.object
und manuell definierten side_effect
für eine Scheinprobe in Einklang bringen.