如何指导一个神奇的mock如何对待它的论点



我遇到了以下(edge?(情况,我不知道如何正确处理。一般的问题是

  • 我有一个要测试的函数
  • 在该函数中,我调用了一个以生成器理解为自变量的外部函数
  • 在我的测试中,我模拟掉了外部函数
  • 现在,prod代码和测试的代码不同了:在prod中,生成器被消耗掉了,mock不这样做

下面是我的代码库中的一个简化示例:

import itertools
import random

def my_side_effects():
# imaginge itertools.accumulate was some expensive strange function
# that consumes an iterable
itertools.accumulate(random.randint(1, 5) for _ in range(10))

def test_my_side_effects(mocker):
my_mocked_func = mocker.patch('itertools.accumulate')
my_side_effects()
# make sure that side-effects took place. can't do much else.
assert my_mocked_func.call_count == 1

测试运行得很好,对我来说已经足够好了。但是,当我在代码上运行coverage时,我在摘要中描述的情况变得显而易见:

----------- coverage: platform linux, python 3.8.0-final-0 -----------
Name                                   Stmts   Miss Branch BrPart  Cover   Missing
----------------------------------------------------------------------------------
[...]
my_test_case.py                            5      0      2      1    86%   6->exit
[...]
----------------------------------------------------------------------------------
# something like this, the ->exit part on the external call is the relevant part

coverage.py中->exit语法的解释。考虑到理解可以执行我实际想要运行的相关业务逻辑,因此遗漏的覆盖范围是相关的。它只是在这里调用random.randint,但它可以做任何事情。


解决方法:

  1. 我可以用列表理解代替。代码被调用了,每个人都很高兴。除了我,他必须修改他们的后端以缓和测试
  2. 我可以在测试期间进入mock,抓住调用arg,然后用手展开它。这看起来可能太可怕了
  3. 我可以对函数进行monkeypatch,而不是使用magicmock,像monkeypatch.setattr('itertools.accumulate', lambda x: [*x])这样的东西会很有描述性。但是,我将失去像在我的示例中那样进行调用断言的能力

我认为一个好的解决方案是这样的,但遗憾的是,它并不存在:

def test_my_side_effects(mocker):
my_mocked_func = mocker.patch('itertools.accumulate')
# could also take "await", and assign treatments by keyword
my_mocked_func.arg_treatment('unroll')  
my_side_effects()
# make sure that side-effects took place. can't do much else.
assert my_mocked_func.call_count == 1

这里缺少覆盖范围是正确的:事实上,由于累积从未被消耗,您甚至可以有:

itertools.accumulate(ERRORERRORERROR for _ in range(10))

你现有的测试仍然可以通过(明显的错误被嘲笑掉了(。

要解决此问题,请使用mock:的side_effect

my_mocked_func = mocker.patch('itertools.accumulate', side_effect=list)

当使用可调用函数作为mock的side_effect时,它会使用与mock相同的参数进行调用,并且此可调用函数的返回值将用作mock的返回值(注意:这意味着您也可以在此处对返回值进行断言,而不仅仅是直接的call_count断言(。

这将允许您消耗发电机,并在这里获得100%的覆盖率。

按旧方法进行:

import itertools
def func():
return list(itertools.izip(["a", "b", "c"], [1, 2, 3]))
def test_mock():
callargs = []
def mock_zip(*args):
callargs.append(args)
for arg in args:
list(arg)
yield ("a", 1)
yield ("b", 2)
old_izip = itertools.izip
itertools.izip = mock_zip
result = func()
itertools.izip = old_izip
assert 1 == len(callargs), "oops, not called once"
assert result == [("a", 1), ("b", 2)], "oops, wrong result"
print("success")

相关内容

  • 没有找到相关文章

最新更新