如何从Mock实例方法访问TestCase实例方法?

时间:2015-10-24 17:40:06

标签: python python-3.x python-unittest python-unittest.mock

TL; DR

我是否可以在模拟类的方法中访问当前的unittest.TestCase实例而无需显式传入该实例?如果没有,从那里访问TestCase的断言辅助函数(它们是实例方法)的正确方法是什么? (有正确的方法吗?)

Szenario:使用我自己的断言方法扩充MagicMock,该方法基于TestCase方法

我想测试一个函数(handle_multiple_foobars())是否正确使用了另一个函数(handle_one_foobar()),因此我嘲笑handle_one_foobar()。 '正确'这意味着handle_multiple_foobars()将分别为每个参数调用handle_one_foobar()。 (每个handle_one_foobar()参数handle_multiple_foobars()次调用。)我不关心电话的顺序。

检查是否所有预期的模拟调用

因此,我从这开始:

import unittest
from unittest import TestCase
from unittest.mock import patch, call

def handle_one_foobar(foobar):
    raise NotImplementedError()

def handle_multiple_foobars(foobars):
    for foobar in reversed(foobars):
        handle_one_foobar(foobar)


class FoobarHandlingTest(TestCase):
    @patch('__main__.handle_one_foobar')
    def test_handle_multiple_foobars_calls_handle_one_foobar_for_each_foobar(
            self, handle_one_foobars_mock):
        foobars = ['foo', 'bar', 'foo', 'baz']

        handle_multiple_foobars(foobars)

        expected_calls = [call(fb) for fb in foobars]
        handle_one_foobars_mock.assert_has_calls(
                expected_calls,
                any_order=True)

if __name__ == '__main__':
    unittest.main()

检查是否已对模拟进行预期调用

但是,如果有更多对模拟函数的调用,例如

,这也会通过
def handle_multiple_foobars(foobars):
    handle_one_foobar('begin')
    for foobar in reversed(foobars):
        handle_one_foobar(foobar)
    handle_one_foobar('end')

我不想要那个。

我可以轻松编写一个额外的断言(或额外的测试)来检查呼叫的总数。但我希望将此作为一个单独的条件进行测试。所以我构建了一个不同的断言:

class FoobarHandlingTest(TestCase):
    @patch('__main__.handle_one_foobar')
    def test_handle_multiple_foobars_calls_handle_one_foobar_for_each_foobar(
            self, handle_one_foobar_mock):
        foobars = ['foo', 'bar', 'foo', 'baz']

        handle_multiple_foobars(foobars)

        expected_calls = [call(fb) for fb in foobars]
        self.assertCountEqual(
                handle_one_foobar_mock.mock_calls,
                expected_calls)

这将很好地捕获额外的调用:

F
======================================================================
FAIL: test_handle_multiple_foobars_calls_handle_one_foobar_for_each_foobar (__main__.FoobarHandlingTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/lib/python3.4/unittest/mock.py", line 1136, in patched
    return func(*args, **keywargs)
  File "convenientmock.py", line 23, in test_handle_multiple_foobars_calls_handle_one_foobar_for_each_foobar
    expected_calls)
AssertionError: Element counts were not equal:
First has 1, Second has 0:  call('begin')
First has 1, Second has 0:  call('end')

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (failures=1)

将新断言移动到mock

的辅助方法中

但是根据我的口味,这里的断言并不明显。因此,我决定将断言提取到一个新函数中。因为这个函数与assert_has_calls()的用途非常相似,所以我觉得它应该是mock类的一个方法。扩展MagicMock并不难,我们甚至可以使提取的方法更通用,以便指定调用顺序是否重要:

from unittest.mock import MagicMock

class MyMock(MagicMock):
    def assert_has_exactly_calls(_mock_self, calls, any_order=False):
        tc = TestCase()
        asserter = tc.assertCountEqual if any_order else tc.assertEqual
        asserter(_mock_self.mock_calls, list(calls))
当我将测试方法修饰更改为

时,

@patch将使用此类而不是unittest.mock.MagicMock来创建模拟

    @patch('__main__.handle_one_foobar', new_callable=MyMock)
    def test_handle_multiple_foobars_calls_handle_one_foobar_for_each_foobar( # ...

然后我可以将我的断言写为

        handle_one_foobar_mock.assert_has_exactly_calls(
                expected_calls,
                any_order=True)

......和丑陋的

但您可能已经注意到一些非常丑陋的事情:为了能够使用TestCase实例方法assertCountEqual()assertEqual(),我创建了一个虚拟TestCase实例这不是运行测试的真实FoobarHandlingTest实例。

我该如何避免这种情况?

显然,我可以将测试的self传递给断言方法,但这会导致非直观的签名。 (为什么我必须告诉关于我的测试用例的断言?)

0 个答案:

没有答案