如何使用输入调用测试函数?
How to test a function with input call?
我有一个用 Python 编写的控制台程序。它使用以下命令询问用户问题:
some_input = input('Answer the question:', ...)
我如何测试包含使用 pytest
调用 input
的函数?
我不想强迫测试人员多次输入文本来完成一个测试 运行.
您可能应该模拟内置 input
function, you can use the teardown
functionality provided by pytest
以在每次测试后恢复到原始 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
module together with a with statement
。这样你就不需要使用拆解,修补后的方法只会存在于 with
范围内。
import mock
import module
def test_function():
with mock.patch.object(__builtins__, 'input', lambda: 'some_input'):
assert module.function() == 'expected_output'
正如编译器所建议的那样,pytest 对此有一个新的 monkeypatch 固定装置。 monkeypatch 对象可以更改 class 中的属性或字典中的值,然后在测试结束时恢复其原始值。
在这种情况下,内置的 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"
您可以替换 sys.stdin
with some custom Text IO,例如来自文件或内存中 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()
更可靠,因为如果模块使用任何其他方法从 stdin 使用文本,那是不够的。
这也可以使用自定义上下文管理器非常优雅地完成
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'] 中,您可以测试什么都没有输入不同的值时会发生这种情况。
In this case we assume a SystemExit
is raised when answering 'N':
@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.patch
和 with
块来完成。
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 标志本质上意味着:--capture=no.
的快捷方式
您还可以在测试代码中使用环境变量。例如,如果您想将路径作为参数,您可以读取环境变量并在缺少时设置默认值。
import os
...
input = os.getenv('INPUT', default='inputDefault/')
然后从默认参数开始
pytest ./mytest.py
或使用自定义参数
INPUT=newInput/ pytest ./mytest.py
另一种不需要使用 lambda 函数并在测试期间提供更多控制的替代方法是使用标准 unittest
模块中的 mock
装饰器。
它还有一个额外的好处,就是只在查找对象(即 input
)的地方打补丁,即 the recommended strategy。
# 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
我有一个用 Python 编写的控制台程序。它使用以下命令询问用户问题:
some_input = input('Answer the question:', ...)
我如何测试包含使用 pytest
调用 input
的函数?
我不想强迫测试人员多次输入文本来完成一个测试 运行.
您可能应该模拟内置 input
function, you can use the teardown
functionality provided by pytest
以在每次测试后恢复到原始 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
module together with a with statement
。这样你就不需要使用拆解,修补后的方法只会存在于 with
范围内。
import mock
import module
def test_function():
with mock.patch.object(__builtins__, 'input', lambda: 'some_input'):
assert module.function() == 'expected_output'
正如编译器所建议的那样,pytest 对此有一个新的 monkeypatch 固定装置。 monkeypatch 对象可以更改 class 中的属性或字典中的值,然后在测试结束时恢复其原始值。
在这种情况下,内置的 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"
您可以替换 sys.stdin
with some custom Text IO,例如来自文件或内存中 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()
更可靠,因为如果模块使用任何其他方法从 stdin 使用文本,那是不够的。
这也可以使用自定义上下文管理器非常优雅地完成
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'] 中,您可以测试什么都没有输入不同的值时会发生这种情况。
In this case we assume a
SystemExit
is raised when answering 'N':
@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.patch
和 with
块来完成。
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 标志本质上意味着:--capture=no.
的快捷方式您还可以在测试代码中使用环境变量。例如,如果您想将路径作为参数,您可以读取环境变量并在缺少时设置默认值。
import os
...
input = os.getenv('INPUT', default='inputDefault/')
然后从默认参数开始
pytest ./mytest.py
或使用自定义参数
INPUT=newInput/ pytest ./mytest.py
另一种不需要使用 lambda 函数并在测试期间提供更多控制的替代方法是使用标准 unittest
模块中的 mock
装饰器。
它还有一个额外的好处,就是只在查找对象(即 input
)的地方打补丁,即 the recommended strategy。
# 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