繁体   English   中英

在自动指定时模拟side_effect给函数附加参数

[英]Mocking side_effect while autospeccing gives the function additional argument

因此,示例代码非常基础:

@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]), {})

这很好。 但是在模拟中添加autospec关键字:

@mock.patch.object(BookForm, 'is_valid', autospec=True)

导致将其他参数传递给side_effect可调用对象,这显然会导致错误:

TypeError: <lambda>() takes 0 positional arguments but 1 was given

我不理解的是为什么自动指定提供了其他参数。 我已经阅读了文档 ,但仍然找不到这种行为的解释。

从理论上讲,

另外,模拟的函数/方法具有与原始函数相同的调用签名,因此如果调用不正确,则会引发TypeError。

这样就可以了( is_validself参数,这可能是在此处传递的参数),但另一方面,它也写了有关side_effect

该函数使用与模拟相同的参数调用,除非返回DEFAULT,否则将此函数的返回值用作返回值。

据我了解,即使没有selfside_effect应使用self参数调用side_effect。 但事实并非如此。

用与模拟相同的参数调用

if form.is_valid():  # the mock is_valid is called with the self argument, isn't it?

因此,如果有人可以向我解释,最好是引用文档,我将不胜感激。

您误解了文档。 如果没有autospecside_effect调用的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')

现在,您的原始测试应该可以进行一些调整,大致相同

@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()

按原样运行测试将导致Book is valid打印到stdout,即使我们没有经历过将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)

同样在Client.post方法调用的框架内,它也不是绑定方法(我们稍后会Client.post

(Pdb) self.form.is_valid
<MagicMock name='is_valid' id='140554947029032'>

因此,在这里,我们可能会遇到一个问题: side_effect实际上可以是与实际情况不同的任何可调用对象,在本例中, is_valid函数签名(即参数列表)可能与我们提供的模拟不同。 如果修改BookForm.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

重新运行我们的测试...,即使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()

现在,在调用函数时会发生这种情况

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

您想要的是什么以及autospec打算提供的内容-在调用autospec之前进行检查,以及

另外,模拟的函数/方法具有与原始函数相同的调用签名,因此如果调用不正确,则会引发TypeError。

因此,我们必须通过提供大于0的authcode来修复Client.post方法。

    def post(self):
        if self.form.is_valid(123) is True:
            print('Book is valid')
        else:
            print('Book is invalid')

由于我们的测试没有通过side_effect调用来模拟is_valid函数,因此该方法将最终打印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()

Book is valid现在将再次打印。 通过调试器将检查该autospec “d和嘲笑is_valid对象的的框架内Client.post方法调用

(Pdb) self.form.is_valid
<bound method BookForm.is_valid of <__main__.BookForm object at 0x7fd57f43dc88>>

嗯,以某种方式,方法签名不是一个简单的MagicMock对象(记得<MagicMock name='is_valid' id='140554947029032'><MagicMock name='is_valid' id='140554947029032'> ),并且是一个正确绑定的方法,这意味着self参数现在已传递给模拟,解决这个问题:

side_effect :每当调用Mock时都要调用的函数。 请参阅side_effect属性。 对于引发异常或动态更改返回值很有用。 使用与模拟相同的参数调用该函数。

在这种情况下,“与模拟参数相同”的含义与传递给模拟的参数相同。 重申一下,第一种情况将self.form.is_valid替换为self.form.is_valid的裸露可调用对象,因此self不会被传递; 在第二种情况下,可调用对象现在已绑定到selfselfauthcode都将传递到side_effect可调用对象中-就像在实际调用中会发生的那样。 这应该可以解决对autospec=True的交互行为的不当行为,其中对于mock.patch.object ,使用autospec=True进行交互;对于人工调用,可以手动定义side_effect

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM