成功しないテストを処理するために skip と xfail を使用する方法

特定のプラットフォームで実行できない、または失敗すると予想されるテスト関数にマークを付けることができます。 これにより、pytest はそれらを適切に処理し、テストスイートを グリーン のままにしながら、テストセッションの概要を表示できます。

skip とは、いくつかの条件が満たされた場合にのみテストが合格すると予想されることを意味します。 そうでなければ、pytest はテストの実行を完全にスキップする必要があります。 一般的な例としては、非 Windows プラットフォームでの Windows 専用テストのスキップや、現在利用できない外部リソース (たとえばデータベース) に依存するテストのスキップがあります。

xfail とは、何らかの理由でテストが失敗すると予想されることを意味します。 一般的な例としては、まだ実装されていない機能のテストや、まだ修正されていないバグのテストがあります。 失敗すると予想されるにもかかわらずテストが合格した場合 (pytest.mark.xfail でマークされている) 、それは xpass であり、テストの概要に報告されます。

pytestskip および xfail テストを個別にカウントおよびリストします。 スキップされた/xfail されたテストの詳細情報は、出力が乱雑になるのを避けるためにデフォルトでは表示されません。 -r オプションを使用して、テストの進行状況に表示される「短い」文字に対応する詳細を表示できます:

pytest -rxXs  # show extra info on xfailed, xpassed, and skipped tests

-r オプションの詳細については、pytest -h を実行して確認できます。

(See 組み込み設定ファイルオプション)

テスト関数のスキップ

テスト関数をスキップする最も簡単な方法は、オプションの reason を渡すことができる skip デコレータでマークすることです:

@pytest.mark.skip(reason="no way of currently testing this")
def test_the_unknown(): ...

または、pytest.skip(reason) 関数を呼び出して、テストの実行中またはセットアップ中に命令的にスキップすることも可能です:

def test_function():
    if not valid_config():
        pytest.skip("unsupported configuration")

命令的な方法は、インポート時にスキップ条件を評価できない場合に便利です。

モジュールレベルで pytest.skip(reason, allow_module_level=True) を使用してモジュール全体をスキップすることも可能です:

import sys

import pytest

if not sys.platform.startswith("win"):
    pytest.skip("skipping windows-only tests", allow_module_level=True)

リファレンス: pytest.mark.skip

skipif

条件付きで何かをスキップしたい場合は、代わりに skipif を使用できます。 次に、Python3.10 より前のインタープリタで実行されたときにスキップされるようにテスト関数にマークを付ける例を示します:

import sys


@pytest.mark.skipif(sys.version_info < (3, 10), reason="requires python3.10 or higher")
def test_function(): ...

コレクション中に条件が True と評価されると、テスト関数はスキップされ、-rs を使用したときに指定された理由が概要に表示されます。

skipif マーカーをモジュール間で共有できます。 このテストモジュールを考えてみましょう:

# content of test_mymodule.py
import mymodule

minversion = pytest.mark.skipif(
    mymodule.__versioninfo__ < (1, 1), reason="at least mymodule-1.1 required"
)


@minversion
def test_function(): ...

マーカーをインポートして別のテストモジュールで再利用できます:

# test_myothermodule.py
from test_mymodule import minversion


@minversion
def test_anotherfunction(): ...

大規模なテストスイートの場合、マーカーを定義し、それをテストスイート全体で一貫して適用するファイルを 1 つ用意するのが通常は良い考えです。

または、ブール値の代わりに condition strings を使用できますが、モジュール間で簡単に共有できないため、主に後方互換性の理由でサポートされています。

リファレンス: pytest.mark.skipif

クラスまたはモジュールのすべてのテスト関数をスキップする

クラスに skipif マーカー (他のマーカーと同様) を使用できます:

@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
class TestPosixCalls:
    def test_function(self):
        "will not be setup or run under 'win32' platform"

条件が True の場合、このマーカーはそのクラスの各テストメソッドに対してスキップ結果を生成します。

モジュールのすべてのテスト関数をスキップする場合は、pytestmark グローバルを使用できます:

# test_module.py
pytestmark = pytest.mark.skipif(...)

複数の skipif デコレータがテスト関数に適用されている場合、スキップ条件のいずれかが true であればスキップされます。

ファイルやディレクトリのスキップ

テストが Python バージョン固有の機能に依存している場合や、pytest に実行させたくないコードが含まれている場合など、ファイルやディレクトリ全体をスキップする必要がある場合があります。 この場合、ファイルとディレクトリをコレクションから除外する必要があります。 詳細については、テスト収集のカスタマイズ を参照してください。

インポート依存関係が欠落している場合のスキップ

モジュールレベル、テスト内、またはテストセットアップ関数で pytest.importorskip を使用して、インポートが欠落しているテストをスキップできます。

docutils = pytest.importorskip("docutils")

ここで docutils をインポートできない場合、これはテストのスキップ結果につながります。 ライブラリのバージョン番号に基づいてスキップすることもできます:

docutils = pytest.importorskip("docutils", minversion="0.3")

バージョンは、指定されたモジュールの __version__ 属性から読み取られます。

概要

さまざまな状況でモジュール内のテストをスキップする方法に関する簡単なガイドを次に示します:

  1. モジュール内のすべてのテストを無条件にスキップする:

pytestmark = pytest.mark.skip("all tests still WIP")
  1. 特定の条件に基づいてモジュール内のすべてのテストをスキップする:

pytestmark = pytest.mark.skipif(sys.platform == "win32", reason="tests for linux only")
  1. インポートが欠落している場合は、モジュール内のすべてのテストをスキップする:

pexpect = pytest.importorskip("pexpect")

XFail: テスト関数が失敗すると予想されることをマークする

テストが失敗すると予想されることを示すために xfail マーカーを使用できます:

@pytest.mark.xfail
def test_function(): ...

このテストは実行されますが、失敗した場合にトレースバックは報告されません。 代わりに、ターミナルレポートには「失敗することが予想される」 (XFAIL) または「予期せず合格する」 (XPASS) セクションにリストされます。

または、テスト内またはそのセットアップ関数から命令的にテストを XFAIL としてマークすることもできます:

def test_function():
    if not valid_config():
        pytest.xfail("failing configuration (but should work)")
def test_function2():
    import slow_module

    if slow_module.slow_function():
        pytest.xfail("slow_module taking too long")

これらの 2 つの例は、モジュールレベルで条件をチェックしたくない状況を示しています。 これは、条件がマークのために評価される場合です。

これにより、test_functionXFAIL になります。 pytest.xfail() 呼び出しの後に他のコードは実行されないことに注意してください。 これは、既知の例外を発生させることによって内部的に実装されているためです。

リファレンス: pytest.mark.xfail

condition パラメータ

テストが特定の条件下でのみ失敗すると予想される場合、その条件を最初のパラメータとして渡すことができます:

@pytest.mark.xfail(sys.platform == "win32", reason="bug in a 3rd party library")
def test_function(): ...

理由も渡す必要があることに注意してください (パラメータの説明については pytest.mark.xfail を参照) 。

reason パラメータ

reason パラメータを使用して、予想される失敗の動機を指定できます:

@pytest.mark.xfail(reason="known parser issue")
def test_function(): ...

raises パラメータ

テストが失敗する理由をより具体的にしたい場合は、raises 引数に単一の例外または例外のタプルを指定できます。

@pytest.mark.xfail(raises=RuntimeError)
def test_function(): ...

次に、raises に記載されていない例外で失敗した場合、テストは通常の失敗として報告されます。

run パラメータ

テストが xfail としてマークされ、そのように報告されるべきだが、実行さえされるべきでない場合は、run パラメータを False として使用します:

@pytest.mark.xfail(run=False)
def test_function(): ...

これは、インタープリタをクラッシュさせる xfail テストに特に役立ち、後で調査する必要があります。

strict パラメータ

XFAILXPASS の両方は、デフォルトではテストスイートに失敗しません。 strict キーワード専用パラメータを True に設定することで、これを変更できます:

@pytest.mark.xfail(strict=True)
def test_function(): ...

これにより、このテストからの XPASS (「予期せず合格」) 結果がテストスイートに失敗するようになります。

xfail_strict ini オプションを使用して、strict パラメータのデフォルト値を変更できます:

[pytest]
xfail_strict=true

xfail を無視する

コマンドラインで指定することにより:

pytest --runxfail

マークされていないかのように、xfail マークされたテストの実行と報告を強制できます。 これにより、pytest.xfail() も効果がなくなります。

いくつかの使用法を示す簡単なテストファイルを次に示します:

from __future__ import annotations

import pytest


xfail = pytest.mark.xfail


@xfail
def test_hello():
    assert 0


@xfail(run=False)
def test_hello2():
    assert 0


@xfail("hasattr(os, 'sep')")
def test_hello3():
    assert 0


@xfail(reason="bug 110")
def test_hello4():
    assert 0


@xfail('pytest.__version__[0] != "17"')
def test_hello5():
    assert 0


def test_hello6():
    pytest.xfail("reason")


@xfail(raises=IndexError)
def test_hello7():
    x = []
    x[1] = 1

report-on-xfail オプションを使用して実行すると、次の出力が得られます:

! pytest -rx xfail_demo.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR/example
collected 7 items

xfail_demo.py xxxxxxx                                                [100%]

========================= short test summary info ==========================
XFAIL xfail_demo.py::test_hello
XFAIL xfail_demo.py::test_hello2
  reason: [NOTRUN]
XFAIL xfail_demo.py::test_hello3
  condition: hasattr(os, 'sep')
XFAIL xfail_demo.py::test_hello4
  bug 110
XFAIL xfail_demo.py::test_hello5
  condition: pytest.__version__[0] != "17"
XFAIL xfail_demo.py::test_hello6
  reason: reason
XFAIL xfail_demo.py::test_hello7
============================ 7 xfailed in 0.12s ============================

parametrize を使用した skip/xfail

parametrize を使用する場合、個々のテストインスタンスに skip や xfail のようなマーカーを適用することが可能です:

import sys

import pytest


@pytest.mark.parametrize(
    ("n", "expected"),
    [
        (1, 2),
        pytest.param(1, 0, marks=pytest.mark.xfail),
        pytest.param(1, 3, marks=pytest.mark.xfail(reason="some bug")),
        (2, 3),
        (3, 4),
        (4, 5),
        pytest.param(
            10, 11, marks=pytest.mark.skipif(sys.version_info >= (3, 0), reason="py2k")
        ),
    ],
)
def test_increment(n, expected):
    assert n + 1 == expected