[英]How to instruct a magic mock on how it should treat its arguments
我遇到了以下(边缘?)案例,我不知道如何正确处理。 一般的问题是
这是它在我的代码库中的外观的简化示例:
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
,但它可以做任何事情。
解决方法:
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))
并且您现有的测试仍然会通过(明显的错误只是被嘲笑了)。
要解决这个问题,请使用模拟的side_effect
:
my_mocked_func = mocker.patch('itertools.accumulate', side_effect=list)
当使用 callable 作为 mock 的side_effect
,它使用与 mock 相同的参数被调用,并且这个 callable 的返回值被用作 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")
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.