Python的unittest.模拟'断言对多个方法调用与变异的参数?



今天我花了很多时间在一些棘手的单元测试问题上,在那里我试图正确地断言对同一个方法的两个调用,并从unittest.mockassert_has_calls方法得到非常奇怪的行为。

这里有一个非常简单的例子,我试图断言一些调用:

class Foo():
def __init__(self):
pass
# Method that I testing!
def bar(self, d):
# doing something with dictionary
print(d)

def baz():
f = Foo()
d = {1: 2}
# first call
f.bar(d)
# updated dictionary
d[3] = 4
# second call, after dictionary mutation
f.bar(d)

@mock.patch('foo.Foo')
def test_baz(foo_mock):
baz()
foo_mock.return_value.bar.assert_has_calls(
[
mock.call({1: 2}),
mock.call({1: 2, 3: 4})
]
)

上面非常简单的测试(例如test_baz)失败与错误:

E               AssertionError: Calls not found.
E               Expected: [call({1: 2}), call({1: 2, 3: 4})]
E               Actual: [call({1: 2, 3: 4}), call({1: 2, 3: 4})]

原因是d字典在两次调用之间的被测试方法中的突变,assert_has_calls不知怎么地没有正确捕获调用历史,即它只是捕获所有调用的最后字典状态!

这看起来像一个bug在unittest.mock,但也许我错过了一些东西在这里(即使用测试框架不当或左右)?

这是相当琐碎的单元测试,但我没有其他方法来正确断言测试方法的输出(否则测试将是无用的)。有没有人遇到过类似的情况,有没有什么解决办法?

我在这里看到的唯一解决方案是更改测试代码(即baz函数)并在传递给方法之前创建变异字典(d)的副本,但我想避免这种情况,因为它可能相当大。

回答我自己(也许它也会对其他人有所帮助)-我找到了解决方案,感谢我在评论中得到的想法(感谢@MrBeanBremen)。

所以,这不是unittest.mock中的错误(如预期的那样),但是所描述的行为是通过设计的。关于这个问题的更多信息可以在mock入门指南中找到。

根据具体的用例,有多种变通方法(如链接指南中所述),对于我的用例,我决定使用辅助函数和副作用:

import mock
from copy import deepcopy
from pylib.pytest_examples.foo import baz

def copy_call_args(original_mock):
new_mock = mock.Mock()
def side_effect(*args, **kwargs):
args = deepcopy(args)
kwargs = deepcopy(kwargs)
new_mock(*args, **kwargs)
return mock.DEFAULT
original_mock.side_effect = side_effect
return new_mock

@mock.patch('foo.Foo')
def test_baz(foo_mock):
bar_mock = copy_call_args(foo_mock.return_value.bar)
baz()
bar_mock.assert_has_calls(
[
mock.call({1: 2}),
mock.call({1: 2, 3: 4})
]
)

这样assert_has_calls断言才能正常工作。

最新更新