Pythonで記述されたコンソールプログラムがあります。次のコマンドを使用してユーザーに質問します。
some_input = input('Answer the question:', ...)
input
を使用して、pytest
への呼び出しを含む関数をどのようにテストしますか? 1回のテスト実行を完了するためだけに、テスターが何度もテキストを入力するように強制したくはありません。
おそらく、組み込みの 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'
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"
_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()
_
次のように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
これは、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
を実行することで、このテストを実行できるはずです。