如何使用 pytest 测试 python 中的 void 函数?

问题描述 投票:0回答:3

我刚刚开始使用 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% 的代码覆盖率?

我发现测试此函数的一种方法是返回值(而不是打印)并稍后打印它,然后使用断言。但我想知道是否可以避免这种情况并直接在打印语句上进行测试。

python unit-testing pytest
3个回答
1
投票

您可以将

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'
)。


0
投票

我建议看看插件 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

另外两个小评论,与帖子没有直接关系:

  • 高代码覆盖率(80% - 90%)是一个很好的目标。我个人尝试保持在 90-95% 左右。然而,100% 的覆盖率通常是不必要的。简单(!)的数据项和日志语句通常可以被忽略。
  • 使用记录器而不是
    print
    是一个很好的做法。例如,请参阅问题 6918493

0
投票

我最喜欢的,内置的 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 ') 

© www.soinside.com 2019 - 2024. All rights reserved.