当自动指定时,模拟副作用会给函数提供额外的参数

2024-05-29 09:50:27 发布

您现在位置:Python中文网/ 问答频道 /正文

因此示例代码非常基本:

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

这很有效。但是在mock中添加autospec关键字:

^{pr2}$

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

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

我不明白的是,为什么自动选择会给出额外的论证。我读过docs,但仍然找不到这种行为的解释。在

理论上是这样写的

In addition mocked functions / methods have the same call signature as the original so they raise a TypeError if they are called incorrectly.

所以可以(is_validself参数,这可能就是这里传递的),但另一方面,它也写了side_effect的内容

The function is called with the same arguments as the mock, and unless it returns DEFAULT, the return value of this function is used as the return value.

据我所知,即使没有自动选择,也应该使用self参数调用side_effect。但事实并非如此。在

is called with the same arguments as the mock

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

所以,如果有人能给我解释,最好引用文件,我会很感激的。在


Tags: thetestselfisaswithmockarguments
1条回答
网友
1楼 · 发布于 2024-05-29 09:50:27

你误解了文件。如果没有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')

现在,您的原始测试应该可以通过一些调整来工作

^{pr2}$

按原样运行测试将导致Book is valid被打印到stdout,即使我们还没有完成设置书籍有效标志为true,因为在Client.post中被调用的self.form.is_valid被替换为被调用的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方法调用的框架内,它不是一个绑定方法(我们稍后将讨论这个问题)

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

嗯,我们这里可能有个问题: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

重新运行我们的测试。。。您将看到,我们的测试已经通过了,即使Client.post仍然在调用BookForm.is_valid,而没有任何参数。即使你的测试通过了,你的产品也会在生产中失败。这就是引入autospec参数的原因,我们将在第二个测试中应用该参数,而不替换callable through side\u效果:

@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打算提供的-调用mock之前的检查,以及

In addition mocked functions / methods have the same call signature as the original so they raise a TypeError if they are called incorrectly.

因此,我们必须通过提供大于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方法调用的框架内的autospec'并模拟了is_valid对象

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

啊,不知何故,方法签名不是一个简单的MagicMock对象(回想一下前面提到的<MagicMock name='is_valid' id='140554947029032'>),而是一个正确绑定的方法,这意味着self参数现在被传递到mock中,解决了这个问题:

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...

在本例中,“与mock相同的参数”的含义与传入mock的参数相同。重申一下,第一种情况下,self.form.is_valid被替换为一个裸的、无界的可调用的,因此self永远不会被传递;在第二种情况下,可调用的现在绑定到selfself和{}都将被传递到side_effect可调用对象中,就像在实际调用中发生的一样。这应该可以调和与autospec=True交互的不当行为,以及为模拟调用的手动定义的{}的错误行为。在

相关问题 更多 >

    热门问题