pytest-skip でテストをスキップする
Skipping | https://docs.pytest.org/en/reorganize-docs/new-docs/user/skipping.html
テストコードが特定の環境でしか動かない場合(例えばWindows環境では動かない場合)や、Pythonのバージョンに依存したテストコードがある場合、実行に時間がかかるので普段のテストでは実行したくないなど、様々な理由によって一部のテストを実行しないようにスキップしたい場面があります。
pytest では一部のテストメソッドやテストクラスをスキップするマーカーが用意されおり、これを使えばテスト実行時にスキップすることが可能です。
skipif で条件を動的に指定してテストをスキップ
@pytest.mark.skipif
で条件を指定することで、テストを実行するかスキップするかを動的に決定できます。
例えばわかりやすく以下のように、スキップするテストとしないテストを実装してみます。
import pytest
@pytest.mark.skipif(True, reason="[スキップする理由]")
def test_function_1():
pass
@pytest.mark.skipif(False, reason="[スキップする理由]")
def test_function_2():
pass
pytest -v
でテスト結果を詳細に確認してみると以下のようになります。
$ pytest -v
============================= test session starts ==============================
platform linux -- Python 3.8.3, pytest-6.2.2, py-1.10.0, pluggy-0.13.1 -- /home/tm/.pyenv/versions/3.8.3/bin/python3.8
cachedir: .pytest_cache
rootdir: /mnt/c/work/pytest-sample
plugins: mock-3.5.1
collected 2 items
tests/test_my_code.py::test_function_1 SKIPPED ([スキップする理由]) [ 50%]
tests/test_my_code.py::test_function_2 PASSED [100%]
========================= 1 passed, 1 skipped in 0.03s =========================
test_function_1
がスキップされていることがわかります。reason
にスキップする理由を書いておけば確認できます。ここでは固定でスキップするかどうかのフラグを指定しましたがもちろん動的に指定してスキップするかどうかを実行時に判断することも可能です。
Python の2系では実行しないようにスキップ
上記ページからの引用ですが、例えば Python3系を必要とするテストコードでは以下のように @pytest.mark.skipif
でバージョン情報を参照した結果を条件として指定します。
import sys
@pytest.mark.skipif(sys.version_info < (3, 0),
reason="requires Python3")
def test_function():
...
このテストを Python2系で実行するとスキップされます。
テストを無条件でスキップする
skipif
ではなく skip
とすることで条件の指定なしで強制的にスキップすることができます。
@pytest.mark.skip(reason="テストがうまくいかないのでこのテストは無条件でスキップします。")
def test_sample():
pass
今はテストできないけど原因が解決できればテストしたい場合などに利用するイメージでしょうか。
テストクラスをまとめてスキップ
import pytest
@pytest.mark.skip(reason="[スキップする理由]")
class TestClass:
def test_function_1():
pass
def test_function_2():
pass
スキップのデコレータをテストクラスに付与すればテストクラス内のテストコード全体がスキップされます。
スキップマーカーを使いまわす
python3_only = pytest.mark.skipif(sys.version_info < (3, 0),
reason="requires Python3")
@python3_only
def test_function1():
pass
スキップデコレータを使いまわす場合は変数として参照することが推奨されるみたいです。
コマンドのオプション引数の指定で動的にスキップ対象を決める
コマンドラインのオプション引数で実行するテストの範囲を指定したいです。例えば実行に時間がかかるテストがあったとして、--runslow
が指定されている場合のみスキップせずに実行するということを実装したいです。
以下のドキュメントページにまさにそのサンプルコードが載っています。
Basic patterns and examples — pytest documentation
# content of conftest.py
import pytest
def pytest_addoption(parser):
parser.addoption(
"--runslow", action="store_true", default=False, help="run slow tests"
)
def pytest_configure(config):
config.addinivalue_line("markers", "slow: mark test as slow to run")
def pytest_collection_modifyitems(config, items):
if config.getoption("--runslow"):
# --runslow given in cli: do not skip slow tests
return
skip_slow = pytest.mark.skip(reason="need --runslow option to run")
for item in items:
if "slow" in item.keywords:
item.add_marker(skip_slow)
# content of test_module.py
import pytest
def test_func_fast():
pass
@pytest.mark.slow # <- 独自のマーカー
def test_func_slow():
pass
pytest ではカスタマイズ用に幾つかのイベントをフックすることが可能です。これらを組み合わせてでやりたいことが実現できます。
- コマンドラインのオプション引数を解析する
- 独自のマーカーをテスト開始前に生成する
- 独自マーカーをテストにデコレータで指定する
手順的には上の通りです。順に見ていきます。
pytest_addoption: コマンドラインのオプション引数を解析する
- #pytest.hookspec.pytest_addoption — pytest documentation
- #pytest.config.argparsing.Parser — pytest documentation
pytest_addoption
というフックを使えば、パーサーを参照してオプション引数を解析できます。
pytest_addoption
はテスト開始前に1度だけ実行されます。引数の parser
は標準ライブラリの argparser
と同じようなインターフェースでコマンドラインのオプション引数をパースできます。上記ドキュメントのURLを参照してください。
def pytest_addoption(parser):
"""
コマンドラインのオプションを解析し
--runslow があれば True, なければ False を
変数として保持する
"""
parser.addoption(
'--runslow',
action='store_true',
default=False,
help='run slow tests'
)
action='store_true'
はオプションの指定があるかないかでフラグ結果を保持します。argperser
と同じ使用感ですね。
ここで解析した結果を後続のイベントで使用します。
pytest_configure: 独自マーカーの説明を追加
pytest_configure
は初期構成を実行できるようにするフックです。コマンドラインオプションの解析(pytest_addoption
)実行後に呼び出されます。
pytest --markers
で使用可能なマーカーを一覧できます。そこに独自マーカーの説明を追加しておくとわかりやすいので追加します。
def pytest_configure(config):
'''
$ pytest --markers
上記コマンドで参照できるマーカーの説明を追加します。
'''
config.addinivalue_line('markers', 'slow: 実行に時間がかかるテストのマークです。')
もちろん無くても動作します。が、以下のようにマーカーの使い方を確認した時に説明があれば利用者側にはわかりやすいので追加しておくとよいでしょう。
$ pytest --markers | grep slow
@pytest.mark.slow: 実行に時間がかかるテストのマークです。
pytest_collection_modifyitems: 独自マーカーがあればスキップする設定
pytest_collection_modifyitems
はテスト対象の収集完了後に呼び出されるフックです。テスト対象を並べ替えたりフィルタリングしたりできます。
ここでは独自マーカーが設定されたテスト対象に対してスキップするような設定を行います。
def pytest_collection_modifyitems(session, config, items):
# --runslow オプションが無ければ無視します。
if config.getoption('--runslow'):
return
# 独自のスキップマーカー
skip_slow = pytest.mark.skip(reason='実行にはオプション --runslow が必要です。')
# 全テスト対象のメソッドを走査
for item in items:
# 'slow'マーカーがあればスキップマーカーを付与
if 'slow' in item.keywords:
item.add_marker(skip_slow)
pytest_collection_modifyitems
は config
という引数でコマンドラインオプションの解析結果を参照できます。ここでは config.getoption
で --runslow
が指定されているかを判定し、指定がなければ処理せずに終了します。
引数の items
は収集されたテスト対象です。これらをすべて走査して、マーカーに @pytest.mark.slow
という独自マーカーが設定されているかチェックし、設定があればスキップマーカーを追加してスキップするようにします。
以上の手順で、コマンドラインオプションの指定結果を使って、動的にスキップ対象のテストを指定できました。
コード全体
これまでのコード内容をまとめたものを掲載します。プロジェクト構成はこんな感じです。
$ tree
.
├── conftest.py
└── test_sample.py
conftest.py
import pytest
def pytest_addoption(parser):
'''
コマンドラインのオプションを解析し
--runslow があれば True, なければ False を
変数として保持する
'''
parser.addoption(
'--runslow',
action='store_true',
default=False,
help='実行時間がかかるテストを実行します。'
)
def pytest_configure(config):
'''
$ pytest --markers
上記コマンドで参照できるマーカーの説明を追加します。
'''
config.addinivalue_line('markers', 'slow: 実行に時間がかかるテストのマークです。')
def pytest_collection_modifyitems(session, config, items):
# --runslow オプションが無ければ無視します。
if config.getoption('--runslow'):
return
# 独自のスキップマーカー
skip_slow = pytest.mark.skip(reason='実行にはオプション --runslow が必要です。')
# 全テスト対象のメソッドを走査
for item in items:
# 'slow'マーカーがあればスキップマーカーを付与
if 'slow' in item.keywords:
item.add_marker(skip_slow)
test_sample.py
import pytest
@pytest.mark.slow
def test_slow_func():
pass
def test_sample():
pass
上記テストを実行すると以下のようになります。
$ pytest -v
==================================== test session starts ====================================
platform linux -- Python 3.8.3, pytest-6.2.2, py-1.10.0, pluggy-0.13.1 -- /home/tm/.pyenv/versions/3.8.3/bin/python3.8
cachedir: .pytest_cache
rootdir: /mnt/c/work/pytest-sample
plugins: mock-3.5.1
collected 2 items
test_sample.py::test_slow_func SKIPPED (実行にはオプション --runslow が必要です。) [ 50%]
test_sample.py::test_sample PASSED [100%]
=============================== 1 passed, 1 skipped in 0.03s ================================
独自のマーカー @pytest.mark.slow
を設定したテストのみスキップされ、その理由が確認できています。
以上。
参考URL
- Basic patterns and examples — pytest documentation
- Collection hooks — pytest documentation
- #pytest.hookspec.pytest_addoption — pytest documentation
- #pytest.config.argparsing.Parser — pytest documentation
- python – pytest skip-if条件でコマンドラインオプションを使用する – ITツールウェブ
- Python + pytestにて、pytestに独自のコマンドラインオプションを追加する – メモ的な思考的な
- #pytest.hookspec.pytest_addoption — pytest documentation
- #pytest.config.argparsing.Parser — pytest documentation
コメントを書く