2016-03-07 159 views
15

我有一个用Python编写的控制台程序。它要求使用该命令的用户的问题:Pytest:如何通过输入调用来测试函数?

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

我将如何测试中,含有使用pytestinput通话功能? 我不想强迫测试人员多次输入文本来完成一次测试运行。

+0

你去翻关于如何使用'pytest'看你可以尝试什么教程?这是一个相当宽泛的问题。 – idjaw

+1

@idjaw不是最近。我之前使用过pytest,但是当考虑在我的项目中为TDD做TDD时,我想到了这一点,但我不知道如何解决它。我会再看看那些馅饼。 – Zelphir

+0

在您的测试函数中,您可以将'input()'函数重新分配给别的东西(也称为“猴子修补”或“阴影”)。 –

回答

8

您应该嘲笑内置的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 

更好的解决方案将是为与with statement一起使用mock模块。这样你就不需要使用拆卸,修补后的方法只能在with范围内生存。

import mock 
import module 

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

这将改变整个测试'输入'背后的功能会议,还是只为这一个测试? – Zelphir

+3

不,这也会为测试后运行的任何东西修补“输入”。您应该使用pytest的[monkeypatch fixture](http://pytest.org/latest/monkeypatch.html)在测试结束时自动反转修补。 –

+0

谢谢@TheCompiler,我编辑了我的问题。 – Forge

12

正如编译器建议的,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.setitem('builtins.input', lambda x: "Mark") 

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

编辑:改变lambda: "Mark"lambda x: "Mark"

+0

这应该是'setattr',而不是'setitem'。 – Matt

8

您可以更换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()更稳健,因为如果模块使用任何其他从标准输入消耗文本的方法,这将不够。

这也可以很优雅地完成了一个自定义的上下文管理

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

可以按如下方式与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'],您可以测试在输入不同的值时没有任何反应。

在这种情况下,我们假设一个SystemExit引发回答“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