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 替换的最终效果不依赖于这些调用的顺序。)

换句话说,我希望我的测试能够

所以,我必须检查 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))