2016-03-07 161 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