私が歌川です

@utgwkk が書いている

pytestプラグインだってテストされたい

はじめに

これはPython Advent Calendar (3) 17日目の記事です。

pytestプラグインのテストについてお話しします。プラグインの実装方法については今回は触れないので、気になった人はWriting plugins — pytest documentationを読んでみてください。

pytestについて

pytestは、Pythonのテストフレームワークです。Python標準ライブラリのunittestに比べて、assert 文を使って簡潔にテストケースを記述できる*1、fixtureなどを再利用する仕組みがある、たくさんのプラグインが作られているのが特徴です。

プラグインだってテストされたい

みなさまも便利なプラグインを使ってテストを快適に書きたいと思っているところだとは思いますが、ここで1つ疑問が出てきます。プラグインの実装が意図した通りであることは、どうやって確かめればよいでしょうか? プラグインが実は壊れていた、ということになると良くないですね。

一番手っ取り早いのはテストでしょう。pytestには、実装したプラグインに対するテストを実装する仕組みが用意されています。この記事では、拙作のpytestプラグインであるpytest-github-actions-annotate-failuresを例に、pytestプラグインのテストの書き方について少しだけ解説します。

pytest-github-actions-annotate-failuresについては以前別の記事で取り上げたので、気になる方はそちらをご覧ください。

blog.utgw.net

pytestプラグインをテストする

以下のテストケースを例に解説します。

def test_annotation_fail(pytester, monkeypatch):
    pytester.makepyfile(
        """
        import pytest
        pytest_plugins = 'pytest_github_actions_annotate_failures'

        def test_fail():
            assert 0
        """
    )
    with monkeypatch.context() as m:
        m.setenv("GITHUB_ACTIONS", "true")
        result = pytester.runpytest_subprocess()

    result.stderr.fnmatch_lines(
        ["::error file=test_annotation_fail.py,line=5::test_fail*assert 0*",]
    )

テストファイルを生成する

pytester.makepyfile メソッドで、pytestプラグインを有効にしてテストを走らせる (=このテストファイルをもとにプラグインの挙動を確かめる) 対象のファイルを生成します。

先述した例だと、以下のようなファイルが test_annotation_fail.py という名前で一時ディレクトリに作成されます。解説のためにコメントを入れています。複数行の文字列リテラルを使ったぶん、先頭に空行が入っていることに注意しましょう。

# ここに空行が入る
import pytest
pytest_plugins = 'pytest_github_actions_annotate_failures'

def test_fail():
    assert 0

pytest_plugins は読み込むpytestプラグインを指定するための変数です。

環境変数を設定する

pytest-github-actions-annotate-failuresプラグインは、GITHUB_ACTIONS 環境変数が true という値であるときだけ動作します。この挙動を確かめるために、monkeypatch.setenv メソッドを使って、生成したテストファイル内でだけ GITHUB_ACTIONS 環境変数の値を書き換えます。

コンテキストマネージャを使うことで、書き換えた環境変数がテストファイルを実行するプロセスでのみ有効であるようにします。

テストファイルを走らせる

pytester.runpytest_subprocess メソッドを呼ぶことで、生成したテストファイルを走らせます。ここで走らせたテストの結果は、元のプラグインのテストには影響しません。このテストの結果を使って、プラグインが意図通りに動いているかどうかを確かめていきます。

アサーションを書く

pytest-github-actions-annotate-failuresプラグインは、テストが失敗したとき、失敗内容を特定の書式で標準エラー出力にprintします。したがって、標準エラー出力に特定のパターンに一致する文字列が書き出されていることを確認できればよさそうです。

result.stderr で標準エラー出力を表すオブジェクトが取得できるので、その fnmatch_lines メソッドを呼ぶことで確認できます。

できました

これでpytestプラグインの挙動を確認するテストが書けました。

pytestには他にもさまざまなfixtureが用意されています。興味がある人は公式ドキュメントをあたってみてください。

testdir fixtureについて

ここまで例として使ったテストコードは、pytest-github-actions-annotate-failuresプラグインのリポジトリにあります。

github.com

ところで、リポジトリのテストコードを見ると testdir というfixtureが使われていることに気づくと思います。testdir は古いfixtureで、2021/12/13現在では testdir ではなく pytester などのfixtureを使うことが推奨されています。pytesterpytest 6.2.0で追加されたfixtureです。

pytest-github-actions-annotate-failuresプラグインはPython 2.7もサポートしています。Python 2.7ではPytest 4.6.Xまでしか使うことができません*2。互換性のために古いfixtureを使い続けている状態です。

おわりに

Test2もそうであるように、テストフレームワークの側がテストプラグインのテスト (メタなテスト?) を書きやすいように設計されていると、プラグインを実装する側としてもありがたいです。

きちんとテストされたプラグインを使って、テストを安全かつ快適に書きたいですね。

*1:unittestの記法を使うこともできます

*2:Python 2.7 and 3.4 support — pytest documentation