首页 > 解决方案 > 如何使用生成器参数验证 Python 模拟调用

问题描述

我有一个以 aIterable作为参数的函数:

def print_me(vals: Iterable[str]) -> None:
    ...

它被一个生成器调用:

def run():
    print_me((str(i) for i in range(10)))

我想修补和模拟print_me()并验证它是否被特定参数调用。使用普通的旧列表,这很容易:

mock.assert_called_with(["0", "1", ...])

但是对于生成器,因为它不能相等或重放,所以这更棘手。

标签: pythonunit-testingmockinggenerator

解决方案


我找到了一些解决方案。我可以手动捕获参数,但如果多次调用该方法,它会变得更加混乱:

@patch('module.print_me')
def test(self, mock):
    captured = None
    mock.side_effect = lambda vals: captured = vals
    run()    
    mock.assert_called_once_with(mock.ANY)
    self.assertEqual(list(captured), ["0", "1", ...])

我也可以代理模拟。这使得断言干净,但想想有点奇怪,并且proxy()每次真正函数的参数发生变化时都需要更新函数。

@patch('module.print_me')
def test(self, proxy_mock):
    mock = mock.Mock()
    def proxy(vals):
        mock(list(vals))

    proxy_mock.side_effect = proxy

    run()    
    mock.assert_called_once_with(["0", "1"])
    mock.assert_called_once_with(["2", "3"])

还有 PyHamcrest。Hamcrest 在 Java 测试中确实很受欢迎,但在 Python 中似乎没有那么受欢迎。也就是说,它做了我想要的:

@patch('module.print_me')
def test(self, mock):
    run()
    mock.assert_called_once_with(match_equality(contains_exactly(["2", "3"])))

推荐阅读