我有一个用Python编写的控制台程序。它使用以下命令询问用户问题:
some_input = input('Answer the question:', ...)
如何使用pytest
测试包含对input
的调用的函数?
我不想强迫测试人员多次输入文本才能完成一次测试。
答案 0 :(得分:26)
正如编译器建议的那样,pytest有一个新的monkeypatch夹具。 monkeypatch对象可以更改类中的属性或字典中的值,然后在测试结束时恢复其原始值。
在这种情况下,内置的-XX:+PrintCompilation
函数是python的input
字典的值,所以我们可以像这样改变它:
__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 x: "Mark")
# go about using input() like you normally would:
i = input("What is your name?")
assert i == "Mark"
更改为lambda: "Mark"
答案 1 :(得分:14)
你应该嘲笑内置的input
函数,你可以使用teardown
提供的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
模块与with statement
一起使用。这样您就不需要使用拆卸,修补后的方法只能在with
范围内使用。
import mock
import module
def test_function():
with mock.patch.object(__builtins__, 'input', lambda: 'some_input'):
assert module.function() == 'expected_output'
答案 2 :(得分:14)
您可以将sys.stdin
替换为某个自定义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()
答案 3 :(得分:3)
您可以使用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
答案 4 :(得分:1)
这可以通过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
,您应该能够使此测试运行。
答案 5 :(得分:0)
由于我需要input()调用来暂停并检查我的硬件状态LED,因此我不得不在没有嘲笑的情况下处理这种情况。我使用了 -s标志。
python -m pytest -s test_LEDs.py
-s标志实质上意味着:-capture = no 的快捷方式。
答案 6 :(得分:0)
您还可以在测试代码中使用环境变量。例如,如果您想将路径作为参数,您可以读取 env 变量并设置默认值(如果缺少)。
import os
...
input = os.getenv('INPUT', default='inputDefault/')
然后从默认参数开始
pytest ./mytest.py
或使用自定义参数
INPUT=newInput/ pytest ./mytest.py