web-dev-qa-db-ja.com

入力呼び出しで関数をテストする方法は?

Pythonで記述されたコンソールプログラムがあります。次のコマンドを使用してユーザーに質問します。

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

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

35

おそらく、組み込みの input 関数をモックする必要があります。 teardown が提供する pytest 機能を使用できますvar] _)==は、各テストの後に元の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'
16
Forge

The Compilerが示唆したように、pytestにはこのための新しいmonkeypatchフィクスチャがあります。 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.setattr('builtins.input', lambda: "Mark")

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

_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
_

これは、モジュールが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()
_
16
Felk

次のように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
3
fernandezcuesta

これは、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'):で、これはinputをラムダ関数でオーバーライドします。ラムダ関数は、inputが引数を取るため、スローアウェイ変数___を取ります。

以下は、user_inputが_sys.exit_を呼び出す失敗ケースをテストする方法です。ここでのコツは、pytest.raises(SystemExit)を使用してpytestにその例外を検索させることです。

_"""
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を実行することで、このテストを実行できるはずです。

2
AlexG