我刚刚开始使用 pytest 在 python 中进行单元测试。好吧,当我有一个带有返回值的函数时,使用“断言”我可以将某个值与函数返回的值进行比较。 但是,如果我有一个 void 函数,它不返回任何内容并在最后进行打印,例如:
def function() -> None:
number = randint(0, 4)
if (number == 0):
print("Number 0")
elif (number == 1):
print("Number 1")
elif (number == 2):
print("Number 2")
elif (number == 3):
print("Number 3")
elif (number == 4):
print("Number 4")
如何测试这个简单的函数以获得 100% 的代码覆盖率?
我发现测试此函数的一种方法是返回值(而不是打印)并稍后打印它,然后使用断言。但我想知道是否可以避免这种情况并直接在打印语句上进行测试。
您可以将
sys.stdout
(print
写入的流)重定向到缓冲区,然后检查或断言缓冲区的内容。
>>> import io
>>> import contextlib
>>>
>>> def f():print('X')
...
>>> buf = io.StringIO()
>>> with contextlib.redirect_stdout(buf):
... f()
...
>>> print(repr(buf.getvalue()))
'X\n'
>>>
>>> buf.close()
(回想一下 print() 将其
end
参数的值附加到该行,默认为 '\n'
)。
我建议看看插件 pytest-mock。它允许您模拟正在测试的代码的协作对象。
考虑下面的测试代码:
# production.py
def say_hello() -> None:
print('Hello World.')
你现在可以轻松地嘲笑这个了
# production_test.py
from production import say_hello
def test_greeting(mocker):
# The "mocker" fixture is auto-magicall inserted by pytest,
# once the extenson 'pytest-mock' is installed
printer = mocker.patch('builtins.print')
say_hello()
assert printer.call_count == 1
您还可以断言调用打印机函数所用的参数等。您将在有用的文档中找到很多详细信息。
现在,考虑一下您不想访问
printer
,但有一个带有一些不良副作用的代码(例如,操作需要很长时间,或者结果是不可预测的(随机)。)让我们再举一个例子,比如
# deep_though.py
class DeepThought:
#: Seven and a half million years in seconds
SEVEN_HALF_MIO_YEARS = 2.366771e14
@staticmethod
def compute_answer() -> int:
time.sleep(DeepThought.SEVEN_HALF_MIO_YEARS)
return 42
是的,我个人不希望我的测试套件运行 7.5 mio 年。那么,我们该怎么办呢?
# deep_thought_test.py
from deep_thought import DeepThought
def test_define_return_value(mocker) -> None:
# We use the internal python lookup path to the method
# as an identifier (from the location it is called)
mocker.patch('deep_thought.DeepThought.compute_answer', return_value=12)
assert DeepThought.compute_answer() == 12
另外两个小评论,与帖子没有直接关系:
print
是一个很好的做法。例如,请参阅问题 6918493。我最喜欢的,内置的 unittest.mock 尚未列出,所以这里是:
from unittest import mock
print_mock = mock.MagicMock()
with mock.patch("builtins.print", print_mock):
function()
# MagicMock records how it was called, which assertions can be made on:
assert print_mock.call_count == 1
args, kwargs = print_mock.call_args
assert args[0].startswith('Number ')