hamcrest.contains_inanyorder 的几乎无用的断言输出应用于 unittest.mock.Mock.mock_calls
near-useless assertion output from hamcrest.contains_inanyorder applied to unittest.mock.Mock.mock_calls
通常,我关心被测系统对软件的另一部分(我在测试中模拟)的确切调用,但不关心这些调用发生的顺序。 (例如,因为 real 其他部分被 mock 替换的最终效果不依赖于这些调用的顺序。)
换句话说,我希望我的测试能够
- 如果没有完成所有预期的调用,则失败
- 如果进行了意外调用则失败(因此
unittest.mock.Mock.assert_has_calls
不够)
- not 如果只有调用的 order 改变了
- 如果调用次数少于或多于预期,则失败
所以,我必须检查 mock_calls
property of the mock object. I can do that in a generic and reasonably comprehensible way with PyHamcrest's contains_inanyorder
:
#!/usr/bin/env python3
from unittest import TestCase, main
from unittest.mock import Mock, call
from hamcrest import assert_that, contains_inanyorder as contains_in_any_order
class TestMockCalls(TestCase):
def test_multiple_calls(self):
m = Mock()
m('foo')
m.bar('baz')
m('foo')
assert_that(
m.mock_calls, contains_in_any_order(
call('foo'),
call('foo'),
call.bar('baz'),
)
)
if __name__ == '__main__':
main()
这对于通过测试非常有效,就像上面的测试一样:
$> ./test_mock_calls.py
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
它在应该失败时也会失败(如上所述,例如,当您将 m('foo')
之一更改为 m('F00')
时),但在这种情况下的输出并不像它应该的那样有用是:
$> ./test_mock_calls.py
F
======================================================================
FAIL: test_multiple_calls (__main__.TestMockCalls)
----------------------------------------------------------------------
Traceback (most recent call last):
File "./test_mock_calls.py", line 16, in test_multiple_calls
call.bar('bay'),
AssertionError:
Expected: a sequence over [, , ] in any order
but: not matched:
----------------------------------------------------------------------
Ran 1 test in 0.001s
FAILED (failures=1)
我可以从中收集到的唯一信息(除了哪个测试和哪个断言失败)是总共预期对模拟的调用次数(通过计算方括号之间的逗号),但不是什么调用是预期的,更重要的是,实际观察到的调用是什么和有多少。
这是 unittest.mock
或 PyHamcrest 中的错误还是我用错了它们?
问题是 call
(_Call
) 本身是一种模拟,并覆盖了 __getattr__
。当 hamcrest 开始检查它是否具有 decribe_to
属性时,事情开始出错。
我认为由于两个模块都在做内省的事情,所以没有一个是应该受到指责的,并且应该在任何一方实现特殊情况以与另一方很好地配合(可能在 hamcrest 中,因为 mock
是标准模块)。
user-side 解决方法是:
from unittest.mock import _Call
_Call.describe_to = lambda c, d: d.append(str(c))
通常,我关心被测系统对软件的另一部分(我在测试中模拟)的确切调用,但不关心这些调用发生的顺序。 (例如,因为 real 其他部分被 mock 替换的最终效果不依赖于这些调用的顺序。)
换句话说,我希望我的测试能够
- 如果没有完成所有预期的调用,则失败
- 如果进行了意外调用则失败(因此
unittest.mock.Mock.assert_has_calls
不够) - not 如果只有调用的 order 改变了
- 如果调用次数少于或多于预期,则失败
所以,我必须检查 mock_calls
property of the mock object. I can do that in a generic and reasonably comprehensible way with PyHamcrest's contains_inanyorder
:
#!/usr/bin/env python3
from unittest import TestCase, main
from unittest.mock import Mock, call
from hamcrest import assert_that, contains_inanyorder as contains_in_any_order
class TestMockCalls(TestCase):
def test_multiple_calls(self):
m = Mock()
m('foo')
m.bar('baz')
m('foo')
assert_that(
m.mock_calls, contains_in_any_order(
call('foo'),
call('foo'),
call.bar('baz'),
)
)
if __name__ == '__main__':
main()
这对于通过测试非常有效,就像上面的测试一样:
$> ./test_mock_calls.py
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
它在应该失败时也会失败(如上所述,例如,当您将 m('foo')
之一更改为 m('F00')
时),但在这种情况下的输出并不像它应该的那样有用是:
$> ./test_mock_calls.py
F
======================================================================
FAIL: test_multiple_calls (__main__.TestMockCalls)
----------------------------------------------------------------------
Traceback (most recent call last):
File "./test_mock_calls.py", line 16, in test_multiple_calls
call.bar('bay'),
AssertionError:
Expected: a sequence over [, , ] in any order
but: not matched:
----------------------------------------------------------------------
Ran 1 test in 0.001s
FAILED (failures=1)
我可以从中收集到的唯一信息(除了哪个测试和哪个断言失败)是总共预期对模拟的调用次数(通过计算方括号之间的逗号),但不是什么调用是预期的,更重要的是,实际观察到的调用是什么和有多少。
这是 unittest.mock
或 PyHamcrest 中的错误还是我用错了它们?
问题是 call
(_Call
) 本身是一种模拟,并覆盖了 __getattr__
。当 hamcrest 开始检查它是否具有 decribe_to
属性时,事情开始出错。
我认为由于两个模块都在做内省的事情,所以没有一个是应该受到指责的,并且应该在任何一方实现特殊情况以与另一方很好地配合(可能在 hamcrest 中,因为 mock
是标准模块)。
user-side 解决方法是:
from unittest.mock import _Call
_Call.describe_to = lambda c, d: d.append(str(c))