c29*_*301 6 python unit-testing mocking
所以示例代码非常基本:
@mock.patch.object(BookForm, 'is_valid')
def test_edit(self, mocked_is_valid):
create_superuser()
self.client.login(username="test", password="test")
book = Book()
book.save()
mocked_is_valid.side_effect = lambda: True
self.client.post(reverse('edit', args=[book.pk]), {})
Run Code Online (Sandbox Code Playgroud)
这很好用.但是在mock中添加autospec关键字:
@mock.patch.object(BookForm, 'is_valid', autospec=True)
Run Code Online (Sandbox Code Playgroud)
导致附加参数传递给side_effectcallable,这显然会导致错误:
TypeError: <lambda>() takes 0 positional arguments but 1 was given
Run Code Online (Sandbox Code Playgroud)
我不理解的是,为什么自动指定提供了额外的论据.我已经阅读了文档,但仍然找不到这种行为的解释.
从理论上讲,它是写的
此外,模拟的函数/方法与原始函数/方法具有相同的调用签名,因此如果它们被错误地调用,它们会引发TypeError.
所以它没关系(is_valid有self争论,这可能是这里传递的东西),但另一方面它也写了关于side_effect那个
使用与mock相同的参数调用该函数,除非它返回DEFAULT,否则此函数的返回值将用作返回值.
据我所知,即使没有自动指定,也side_effect应该使用self参数调用.但事实并非如此.
使用与mock相同的参数调用
if form.is_valid(): # the mock is_valid is called with the self argument, isn't it?
Run Code Online (Sandbox Code Playgroud)
因此,如果有人能够向我解释,最好引用文档,我会感激不尽.
您误解了文档。如果没有autospec,side_effect被调用的实际上就是原样,无需检查原始声明。让我们创建一个更好的最小示例来演示这个问题。
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')
Run Code Online (Sandbox Code Playgroud)
现在,您的原始测试应该与进行一些调整大致相同
@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()
Run Code Online (Sandbox Code Playgroud)
按原样运行测试将导致Book is valid打印到标准输出,即使我们还没有完成将 Book.valid 标志设置为 true 的过程,因为被self.form.is_valid调用的内容Client.post将被调用的 lambda 替换。我们可以通过调试器看到这一点:
> /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)
Run Code Online (Sandbox Code Playgroud)
同样在方法调用的框架内Client.post,它不是绑定方法(我们稍后会回到这一点)
(Pdb) self.form.is_valid
<MagicMock name='is_valid' id='140554947029032'>
Run Code Online (Sandbox Code Playgroud)
所以嗯,我们可能在这里遇到一个问题:side_effect字面上可能是任何可调用的,可能与现实不同,在我们的例子中,函数is_valid签名(即参数列表)可能与我们提供的模拟不同。如果BookForm.is_valid修改该方法以接受附加参数会怎样:
class BookForm(object):
def __init__(self, book):
self.book = book
def is_valid(self, authcode):
return authcode > 0 and self.book.valid
Run Code Online (Sandbox Code Playgroud)
重新运行我们的测试...您将看到我们的测试已经通过,尽管Client.post仍在BookForm.is_valid 没有任何参数的情况下调用。即使您的测试已通过,您的产品也会在生产中失败。这就是autospec引入参数的原因,我们将在第二个测试中应用它,而不通过 side_effect 替换可调用:
@mock.patch.object(BookForm, 'is_valid', autospec=True)
def test_edit_autospec(mocked_is_valid):
book = Book()
book.save()
client = Client(book)
client.post()
Run Code Online (Sandbox Code Playgroud)
现在调用函数时会发生这种情况
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
Run Code Online (Sandbox Code Playgroud)
这就是您想要的以及autospec打算提供的内容 - 在调用模拟之前进行检查,以及
此外,模拟的函数/方法具有与原始函数/方法相同的调用签名,因此如果调用不正确,它们会引发 TypeError 。
因此,我们必须Client.post通过提供大于的授权码来修复该方法0。
def post(self):
if self.form.is_valid(123) is True:
print('Book is valid')
else:
print('Book is invalid')
Run Code Online (Sandbox Code Playgroud)
由于我们的测试没有is_valid通过side_effect可调用函数模拟该函数,因此该方法最终将打印Book is invalid。
现在,如果我们想提供side_effect,它必须匹配相同的签名
@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()
Run Code Online (Sandbox Code Playgroud)
Book is valid现在将再次打印。通过调试器检查方法调用框架内的autospec模拟对象is_validClient.post
(Pdb) self.form.is_valid
<bound method BookForm.is_valid of <__main__.BookForm object at 0x7fd57f43dc88>>
Run Code Online (Sandbox Code Playgroud)
啊,不知何故,方法签名不是一个简单的MagicMock对象(回想一下<MagicMock name='is_valid' id='140554947029032'>前面提到的),而是一个正确绑定的方法,这意味着self参数现在被传递到模拟中,解决了这个问题:
side_effect:每当调用 Mock 时都会调用的函数。查看
side_effect属性。对于引发异常或动态更改返回值很有用。使用与模拟相同的参数调用该函数...
在这种情况下,“与模拟相同的参数”意味着与传递到模拟中的任何内容相同。重申一下,第一个案例被self.form.is_valid替换为裸露的、无界的可调用,因此self永远不会被通过;在第二种情况下,可调用对象现在绑定到self,两个selfANDauthcode都将传递到可side_effect调用对象中 - 就像实际调用中会发生的情况一样。autospec=True这应该可以协调与for交互的感知不当行为mock.patch.object以及side_effect为模拟手动定义的可调用项。