我遇到了以下(边缘?)我不知道如何正确处理的情况。一般的问题是
- 我有一个要测试的功能
- 在那个函数中,我调用一个以生成器理解作为参数的外部函数
- 在我的测试中,我模拟了外部函数
- 现在产品代码和测试代码不同:在产品中,生成器被消耗,模拟不这样做
这是我的代码库中的简化示例:
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
->exit
coverage.py 中的语法解释。
鉴于理解可以执行我确实想要运行的相关业务逻辑,因此错过的覆盖范围是相关的。它只是random.randint
在这里调用,但它可以做任何事情。
解决方法:
- 我可以只使用列表推导。代码被调用,每个人都很高兴。除了我,他必须修改他们的后端才能缓和测试。
- 我可以在测试期间进入模拟,抓住调用 arg,然后手动展开它。这可能看起来很糟糕。
- 我可以对函数进行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