speg03の雑記帳

主に未来の自分のために試したことなどを記録しています

pytestでコンソールアプリケーションのテストを書く

テスト対象のアプリケーション

コンソールアプリケーションの題材として、ここでは以下のようなアプリケーションを考えます。

  • プレースホルダーが1つ含まれたフォーマット文字列を標準入力から受け取る
  • コマンドライン引数としてnameを受け取る
  • フォーマット文字列にnameの値を埋め込んだ結果を標準出力へ出力する

次のように実装しました。

import argparse
import sys


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--name")
    args = parser.parse_args()

    template = sys.stdin.read()
    print(template.format(args.name))


if __name__ == "__main__":
    main()

実行結果は以下のようになります。

echo "hello, {}" | python3 application.py --name=world
hello, world

テスト

pytestを使ってこのアプリケーションのテストを書いてみます。ポイントとなるのはコマンドライン引数、標準入力、標準出力をどのように扱えばよいかという点です。

import io

from application import main


def test_main(monkeypatch):
    argv = ["application.py", "--name=world"]
    stdin = io.StringIO("hello, {}")
    stdout = io.StringIO()

    with monkeypatch.context() as m:
        m.setattr("sys.argv", argv)
        m.setattr("sys.stdin", stdin)
        m.setattr("sys.stdout", stdout)
        main()

    assert stdout.getvalue() == "hello, world\n"

テストが成功することを確認します。

pytest test_application.py

このテストコードについて、もう少し見ていきましょう。

コマンドライン引数

    args = parser.parse_args()

この部分で内部的にsys.argvの値を参照しています。sys.argvコマンドライン引数の配列です。最初の要素はスクリプトの名前になっており、その後ろに実際のコマンドライン引数が続きます。

標準入力

    template = sys.stdin.read()

sys.stdinから標準入力の文字列を取得しています。sys.stdinはファイルオブジェクトです。

標準出力

    print(template.format(args.name))

printが実行されるとsys.stdoutに対して文字列の書き込みが発生します。sys.stdoutsys.stdinと同様にファイルオブジェクトです。