如何用输入调用测试函数?

我有一个用 Python 编写的控制台程序:

some_input = input('Answer the question:', ...)

如何使用 pytest测试包含对 input调用的函数? 我不想强迫测试人员多次输入文本,只是为了完成一次测试运行。

47407 次浏览

您可能应该模拟内置的 input函数,您可以使用 pytest提供的 teardown功能,在每次测试后恢复到原来的 input函数。

import module  # The module which contains the call to input


class TestClass:


def test_function_1(self):
# Override the Python built-in input method
module.input = lambda: 'some_input'
# Call the function you would like to test (which uses input)
output = module.function()
assert output == 'expected_output'


def test_function_2(self):
module.input = lambda: 'some_other_input'
output = module.function()
assert output == 'another_expected_output'


def teardown_method(self, method):
# This method is being called after each test case, and it will revert input back to original function
module.input = input

一个更优雅的解决方案是将 mock模块与 with statement一起使用。这样,您就不需要使用 teardown,并且补丁方法将只存在于 with范围内。

import mock
import module


def test_function():
with mock.patch.object(__builtins__, 'input', lambda: 'some_input'):
assert module.function() == 'expected_output'

正如编译器所建议的那样,pytest 为此提供了一个新的 monkeypatch 装置。猴子补丁对象可以更改类中的属性或字典中的值,然后在测试结束时恢复其原始值。

在本例中,内置的 input函数是 python 的 __builtins__字典的值,因此我们可以这样修改它:

def test_something_that_involves_user_input(monkeypatch):


# monkeypatch the "input" function, so that it returns "Mark".
# This simulates the user entering "Mark" in the terminal:
monkeypatch.setattr('builtins.input', lambda _: "Mark")


# go about using input() like you normally would:
i = input("What is your name?")
assert i == "Mark"

您可以用一些定制的 文本 IO替换 sys.stdin,比如从文件或内存中的 StringIO 缓冲区输入:

import sys


class Test:
def test_function(self):
sys.stdin = open("preprogrammed_inputs.txt")
module.call_function()


def setup_method(self):
self.orig_stdin = sys.stdin


def teardown_method(self):
sys.stdin = self.orig_stdin

这比仅仅修补 input()更加健壮,因为如果模块使用任何其他从标准输入消耗文本的方法,这是不够的。

这也可以通过自定义上下文管理器非常优雅地完成

import sys
from contextlib import contextmanager


@contextmanager
def replace_stdin(target):
orig = sys.stdin
sys.stdin = target
yield
sys.stdin = orig

然后像这样使用它,例如:

with replace_stdin(StringIO("some preprogrammed input")):
module.call_function()

您可以使用 mock.patch进行如下操作。

首先,在代码中,为对 input的调用创建一个虚函数:

def __get_input(text):
return input(text)

在你的测试功能中:

import my_module
from mock import patch


@patch('my_module.__get_input', return_value='y')
def test_what_happens_when_answering_yes(self, mock):
"""
Test what happens when user input is 'y'
"""
# whatever your test function does

例如,如果您有一个循环,检查唯一有效的答案是在[‘ y’,‘ Y’,‘ n’,‘ N’]中,您可以测试输入一个不同的值时什么也没有发生。

在这种情况下,我们假设在回答“ N”时引发 SystemExit:

@patch('my_module.__get_input')
def test_invalid_answer_remains_in_loop(self, mock):
"""
Test nothing's broken when answer is not ['Y', 'y', 'N', 'n']
"""
with self.assertRaises(SystemExit):
mock.side_effect = ['k', 'l', 'yeah', 'N']
# call to our function asking for input

这可以通过 python3中的 mock.patchwith块来完成。

import pytest
import mock
import builtins


"""
The function to test (would usually be loaded
from a module outside this file).
"""
def user_prompt():
ans = input('Enter a number: ')
try:
float(ans)
except:
import sys
sys.exit('NaN')
return 'Your number is {}'.format(ans)


"""
This test will mock input of '19'
"""
def test_user_prompt_ok():
with mock.patch.object(builtins, 'input', lambda _: '19'):
assert user_prompt() == 'Your number is 19'

要注意的行是 mock.patch.object(builtins, 'input', lambda _: '19'):,它使用 lambda 函数覆盖 input。我们的 lambda 函数接受一个一次性变量 _,因为 input接受一个参数。

下面是如何测试失败案例,其中 user _ input 调用 sys.exit。这里的技巧是让 pytest 使用 pytest.raises(SystemExit)查找该异常。

"""
This test will mock input of 'nineteen'
"""
def test_user_prompt_exit():
with mock.patch.object(builtins, 'input', lambda _: 'nineteen'):
with pytest.raises(SystemExit):
user_prompt()

通过将上面的代码复制粘贴到文件 tests/test_.py中并从父目录运行 pytest,您应该能够运行这个测试。

因为我需要 input ()调用来暂停并检查我的硬件状态 LED,所以我必须在不嘲笑的情况下处理这种情况。我用的是 S 旗。

python -m pytest -s test_LEDs.py

S 标志实际上意味着: ——俘虏 = 不的快捷方式。

还可以在测试代码中使用环境变量。例如,如果你想给路径作为参数,你可以读取 env 变量,并设置缺省值,如果它丢失。

import os
...
input = os.getenv('INPUT', default='inputDefault/')

然后从默认参数开始

pytest ./mytest.py

或与习惯论点

INPUT=newInput/ pytest ./mytest.py

另一种不需要使用 lambda 函数并在测试期间提供更多控制的替代方法是使用标准 unittest模块中的 mock装饰器。

它还有一个额外的优点,就是在查找对象(即 input)的地方进行修补,即 建议的策略

# path/to/test/module.py
def my_func():
some_input = input('Answer the question:')
return some_input
# tests/my_tests.py


from unittest import mock


from path.to.test.module import my_func


@mock.patch("path.to.test.module.input")
def test_something_that_involves_user_input(mock_input):
mock_input.return_value = "This is my answer!"
assert my_func() == "This is my answer!"
mock_input.assert_called_once()  # Optionally check one and only one call

我没有足够的观点来评论,但是这个答案是: https://stackoverflow.com/a/55033710/10420225 如果你只是抄袭[抄袭]意大利面,就不起作用。

第一部分

对于 Python 3,import mock不工作。

你需要 import unittest.mock并称之为 unittest.mock.patch.object(),或者 from unittest import mock mock.patch.object()...

如果使用 Python3.3 + 以上应该“只是工作”。如果使用 Python 3.3-您需要 pip install mock。有关更多信息,请参见以下答案: https://stackoverflow.com/a/11501626/10420225

第二部分

另外,如果您想使这个示例更加真实,比如从文件外部导入函数并使用它,那么需要更多的汇编。

这是我们将使用的通用目录结构

root/
src/prompt_user.py
tests/test_prompt_user.py

如果函数在外部文件

# /root/src/prompt_user.py
def user_prompt():
ans = input("Enter a number: ")
try:
float(ans)
except:
import sys


sys.exit("NaN")
return "Your number is {}".format(ans)
# /root/tests/test_prompt_user.py
import pytest
from unittest import mock
import builtins


from prompt_user import user_prompt


def test_user_prompt_ok():
with mock.patch.object(builtins, "input", lambda _: "19"):
assert user_prompt() == "Your number is 19"

如果函数在外部文件中的类中

# /root/src/prompt_user.py
class Prompt:
def user_prompt(self):
ans = input("Enter a number: ")
try:
float(ans)
except:
import sys


sys.exit("NaN")
return "Your number is {}".format(ans)
# /root/tests/test_prompt_user.py
import pytest
from unittest import mock
import builtins


from mocking_test import Prompt




def test_user_prompt_ok():
with mock.patch.object(builtins, "input", lambda _: "19"):
assert Prompt.user_prompt(Prompt) == "Your number is 19"

希望这对人们有更多的帮助。我发现这些非常简单的示例几乎毫无用处,因为它遗漏了很多实际用例。

编辑: 如果在从外部文件运行时遇到 pytest 导入问题,我建议查看这个答案: PATH 问题与 pytest & # 39; import 错误: 没有模块命名为 YadaYadaYada & # 39;