Pytest:如何使用输入调用测试函数?

时间:2016-03-07 18:36:17

标签: python unit-testing testing input pytest

我有一个用Python编写的控制台程序。它使用以下命令询问用户问题:

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

如何使用pytest测试包含对input的调用的函数? 我不想强迫测试人员多次输入文本才能完成一次测试。

7 个答案:

答案 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.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,您应该能够使此测试运行。

答案 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