web-dev-qa-db-ja.com

stdin、ファイル、環境変数の入力をPythonユニットテストに提供する方法は?

次のような条件が発生した場合のテストの作成方法:

  1. ユーザー入力をテストします。
  2. ファイルから読み取られた入力をテストします。
  3. 環境変数から読み取られたテスト入力。

誰かが上記のシナリオにアプローチする方法を教えてくれたら素晴らしいと思います。私が読むことができるいくつかのドキュメント/記事/ブログの投稿を私に指摘していただければ、それでも素晴らしいでしょう。

29
user277465

説明した3つの状況はすべて、設計で疎結合を使用していることを確認するために、特に邪魔にならないようにする必要がある場合です。

本当に Pythonのraw_inputメソッドをユニットテストする必要がありますか? openメソッド? os.environ.get?番号。

その入力を取得する他の方法に置き換えることができるように、デザインを設定する必要があります。次に、単体テスト中に、実際にはraw_inputまたはopenを呼び出さない種類のスタブをスローします。

たとえば、通常のコードは次のようになります。

import os
def say_hello(input_func):
    name = input_func()
    return "Hello " + name

def Prompt_for_name():
    return raw_input("What is your name? ")

print say_hello(Prompt_for_name)
# Normally would pass in methods, but lambdas can be used for brevity
print say_hello(lambda: open("a.txt").readline())
print say_hello(lambda: os.environ.get("USER"))

セッションは次のようになります。

お名前は何ですか?誰か
こんにちは誰か
こんにちは[いくつかのテキスト] 
 
こんにちはマーク

次に、テストは次のようになります。

def test_say_hello():
    output = say_hello(lambda: "test")
    assert(output == "Hello test")

言語のIO機能をテストする必要はないことに注意してください(言語を設計しているのがあなたである場合を除きます。これはまったく別の状況です)。

32
Mark Rushakoff

Raw_input(または他の特定の入力ソース)の使用に縛られている場合、私は モックライブラリ の大きな支持者です。 Mark Rushakoffが彼の例で使用したコードを考えると:

def say_hello():
    name = raw_input("What is your name? ")
    return "Hello " + name

テストコードでモックを使用できます。

import mock

def test_say_hello():
     with mock.patch('__builtin__.raw_input', return_value='dbw'):
         assert say_hello() == 'Hello dbw'

     with mock.patch('__builtin__.raw_input', side_effect=['dbw', 'uki']):
         assert say_hello() == 'Hello dbw'
         assert say_hello() == 'Hello uki'

これらのアサーションは合格します。 side_effectは、リストの要素を順番に返すことに注意してください。それはもっとたくさんのことができます!ドキュメントをチェックすることをお勧めします。

29
dbn

外部プロセスを使用せずに逃げることができる場合は、そうしてください。

ただし、これが複雑で、実際にプロセスを使用したい場合があります。たとえば、C実行可能ファイルのコマンドラインインターフェイスをテストしたい場合などです。

ユーザー入力

次のように_subprocess.Popen_を使用します。

_process = subprocess.Popen(
    command,
    Shell  = False,
    stdin  = subprocess.PIPE,
    stdout = subprocess.PIPE,
    stderr = subprocess.PIPE,
    universal_newlines = True
)
stdout, stderr = process.communicate("the user input\nline 2")
exit_status = process.wait()
_

_raw_input_やsys.stdin.read()などのメソッドを使用して、ユーザーから入力を取得することと、パイプから入力を取得することに違いはありません。

ファイル

  • 一時ディレクトリを作成し、そこから読み取りたいファイルをテストsetUpメソッドで作成します。

    _tdir = tempfile.mkdtemp(
        prefix = 'filetest_', 
    )
    fpath = os.path.join(tdir,'filename')
    fp = open(fpath, 'w')
    fp.write("contents")
    fp.close()
    _
  • テストでファイルの読み取りを行います。

  • その後、一時ディレクトリを削除します。

    _shutil.rmtree(tdir)
    _
  • ファイルからの読み取りは非常に複雑であり、ほとんどのプログラムはファイルまたはSTDINから読み取ることができます(例:fileinput)。したがって、テストしたいのが特定のコンテンツが入力されたときに何が起こるかであり、プログラムがSTDINを受け入れる場合は、Popenを使用してプログラムをテストします。

環境変数

  • _os.environ["THE_VAR"] = "the_val"_で環境変数を設定します
  • _del os.environ["THE_VAR"]_で設定を解除します
  • _os.environ = {'a':'b'}_が機能しない
  • 次に、_subprocess.Popen_を呼び出します。環境は呼び出しプロセスから継承されます。

テンプレートコード

my github に、STDOUTSTDERR、およびSTDINで指定された終了ステータス、コマンドライン引数、および環境をテストするモジュールがあります。また、「tests」ディレクトリでそのモジュールのテストを確認してください。これにはもっと良いモジュールがあるはずなので、学習目的のためだけに私のものを持っていってください。

pytest を使用する:

import os


def test_user_input(monkeypatch):
    inputs = [10, 'y']
    input_generator = (i for i in inputs)
    monkeypatch.setattr('__builtin__.raw_input', lambda Prompt: next(input_generator))
    assert raw_input('how many?') == 10
    assert raw_input('you sure?') == 'y'


def test_file_input(tmpdir):
    fixture = tmpdir.join('fixture.txt')
    fixture.write(os.linesep.join(['1', '2', '3']))
    fixture_path = str(fixture.realpath())
    with open(fixture_path) as f:
        assert f.readline() == '1' + os.linesep


def test_environment_input(monkeypatch):
    monkeypatch.setenv('STAGING', 1)
    assert os.environ['STAGING'] == '1'
3
famousgarkin