2016-03-07 6 views
15

私はPythonで書かれたコンソールプログラムを持っています。これは、コマンドを使用して、ユーザーの質問をする:Pytest:入力呼び出しで関数をテストする方法は?

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

どのように私はpytestを使用してinputへの呼び出しを含む関数をテストするのでしょうか? テスターが1回のテストを終了するために何度も何度もテキストを入力することを強制したくありません。

+0

あなたが試すことができるものを見るために 'pytest'の使用方法についてのチュートリアルを見ていましたか?これはかなり広い質問です。 – idjaw

+1

@idjaw最近ではありません。以前はpytestを使っていましたが、ここで私のプロジェクトでTDDをやってみると、これは私の頭に浮かんできました。私はそれらのツイートをもう一度見てみましょう。 – Zelphir

+0

あなたのテスト関数では、 'input()'関数を何か他のもの( "猿のパッチ"や "シャドウイング"とも呼ばれます)に割り当てることができます。 –

答えて

8

おそらく内蔵のinput機能をモックする必要があり、あなたは、各テストの後に元のinput機能に戻すpytestによって提供さteardown機能を使用することができます。

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

これはテスト全体の 'input'の背後にある関数を変更しますか?セッション、またはこの1つのテストのみ? – Zelphir

+3

いいえ、これはまた、そのテストの後に実行されているものに対して 'input'をパッチします。代わりに、pytestの[monkeypatch fixture](http://pytest.org/latest/monkeypatch.html)を使用して、テストの最後にパッチを自動的に取り消す必要があります。 –

+0

ありがとう@TheCompiler、私は私の質問を編集しました。 – Forge

12

コンパイラが示唆したように、pytestにはこのための新しいMonkeypatch Fixtureがあります。 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 x: "Mark"

+0

これは 'setitem'ではなく' setattr'でなければなりません。 – Matt

8

からlambda: "Mark"を変更しますファイルまたはインメモリのStringIOバッファからの入力のような何らかのカスタムText IOsys.stdinを使用します。

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

次のようにあなたが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