はじめに

pytest のインストール

pytest の要件: Python 3.8+ または PyPy3。

  1. コマンドラインで次のコマンドを実行します:

pip install -U pytest
  1. 正しいバージョンがインストールされたことを確認します:

$ pytest --version
pytest 8.3.4

最初のテストを作成する

test_sample.py という新しいファイルを作成し、関数とテストを含めます:

# content of test_sample.py
def func(x):
    return x + 1


def test_answer():
    assert func(3) == 5

テスト

$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 1 item

test_sample.py F                                                     [100%]

================================= FAILURES =================================
_______________________________ test_answer ________________________________

    def test_answer():
>       assert func(3) == 5
E       assert 4 == 5
E        +  where 4 = func(3)

test_sample.py:6: AssertionError
========================= short test summary info ==========================
FAILED test_sample.py::test_answer - assert 4 == 5
============================ 1 failed in 0.12s =============================

[100%] は、すべてのテストケースの実行全体の進行状況を示しています。 終了後、pytest は func(3)5 を返さないため、失敗レポートを表示します。

注釈

assert ステートメントを使用してテストの期待値を検証できます。 pytest の 高度なアサーションイントロスペクション は、アサーション式の中間値をインテリジェントに報告するため、JUnit のレガシーメソッド の多くの名前を避けることができます。

複数のテストを実行する

pytest は、現在のディレクトリおよびそのサブディレクトリ内の test_*.py または *_test.py 形式のすべてのファイルを実行します。 より一般的には、標準のテスト検出ルール に従います。

特定の例外が発生することをアサートする

raises ヘルパーを使用して、コードが例外を発生させることをアサートします:

# content of test_sysexit.py
import pytest


def f():
    raise SystemExit(1)


def test_mytest():
    with pytest.raises(SystemExit):
        f()

raises によって提供されるコンテキストを使用して、予期される例外が発生した ExceptionGroup の一部であることをアサートすることもできます:

# content of test_exceptiongroup.py
import pytest


def f():
    raise ExceptionGroup(
        "Group message",
        [
            RuntimeError(),
        ],
    )


def test_exception_in_group():
    with pytest.raises(ExceptionGroup) as excinfo:
        f()
    assert excinfo.group_contains(RuntimeError)
    assert not excinfo.group_contains(TypeError)

“quiet” レポートモードでテスト関数を実行します:

$ pytest -q test_sysexit.py
.                                                                    [100%]
1 passed in 0.12s

注釈

-q/--quiet フラグは、この例および次の例で出力を簡潔に保ちます。

複数のテストをクラスにグループ化する

複数のテストを開発したら、それらをクラスにグループ化することをお勧めします。 pytest を使用すると、複数のテストを含むクラスを簡単に作成できます:

# content of test_class.py
class TestClass:
    def test_one(self):
        x = "this"
        assert "h" in x

    def test_two(self):
        x = "hello"
        assert hasattr(x, "check")

pytest は、Python テスト検出の規約 に従ってすべてのテストを検出するため、test_ プレフィックス付きの関数の両方を見つけます。 何もサブクラス化する必要はありませんが、クラスに Test プレフィックスを付けることを確認してください。 そうしないと、クラスがスキップされます。 ファイル名を渡してモジュールを実行するだけです:

$ pytest -q test_class.py
.F                                                                   [100%]
================================= FAILURES =================================
____________________________ TestClass.test_two ____________________________

self = <test_class.TestClass object at 0xdeadbeef0001>

    def test_two(self):
        x = "hello"
>       assert hasattr(x, "check")
E       AssertionError: assert False
E        +  where False = hasattr('hello', 'check')

test_class.py:8: AssertionError
========================= short test summary info ==========================
FAILED test_class.py::TestClass::test_two - AssertionError: assert False
1 failed, 1 passed in 0.12s

最初のテストは成功し、2 番目のテストは失敗しました。 アサーションの中間値を簡単に確認できるため、失敗の理由を理解するのに役立ちます。

テストをクラスにグループ化することには、次のような利点があります:

  • テストの整理

  • 特定のクラス内のテストのみのフィクスチャの共有

  • クラスレベルでマークを適用し、それらをすべてのテストに暗黙的に適用する

クラス内でテストをグループ化する際に注意すべき点は、各テストがクラスの一意のインスタンスを持つことです。 各テストが同じクラスインスタンスを共有することは、テストの分離に非常に有害であり、悪いテストプラクティスを助長します。 以下に概説します:

# content of test_class_demo.py
class TestClassDemoInstance:
    value = 0

    def test_one(self):
        self.value = 1
        assert self.value == 1

    def test_two(self):
        assert self.value == 1
$ pytest -k TestClassDemoInstance -q
.F                                                                   [100%]
================================= FAILURES =================================
______________________ TestClassDemoInstance.test_two ______________________

self = <test_class_demo.TestClassDemoInstance object at 0xdeadbeef0002>

    def test_two(self):
>       assert self.value == 1
E       assert 0 == 1
E        +  where 0 = <test_class_demo.TestClassDemoInstance object at 0xdeadbeef0002>.value

test_class_demo.py:9: AssertionError
========================= short test summary info ==========================
FAILED test_class_demo.py::TestClassDemoInstance::test_two - assert 0 == 1
1 failed, 1 passed in 0.12s

クラスレベルで追加された属性は クラス属性 であるため、テスト間で共有されることに注意してください。

機能テストのために一意の一時ディレクトリを要求する

pytest は、一意の一時ディレクトリなどの任意のリソースを要求するための 組み込みフィクスチャ/関数引数 を提供します:

# content of test_tmp_path.py
def test_needsfiles(tmp_path):
    print(tmp_path)
    assert 0

テスト関数のシグネチャに tmp_path の名前をリストし、pytest はテスト関数の呼び出しを実行する前にリソースを作成するためにフィクスチャファクトリを検索して呼び出します。 テストが実行される前に、pytest はテスト呼び出しごとに一意の一時ディレクトリを作成します:

$ pytest -q test_tmp_path.py
F                                                                    [100%]
================================= FAILURES =================================
_____________________________ test_needsfiles ______________________________

tmp_path = PosixPath('PYTEST_TMPDIR/test_needsfiles0')

    def test_needsfiles(tmp_path):
        print(tmp_path)
>       assert 0
E       assert 0

test_tmp_path.py:3: AssertionError
--------------------------- Captured stdout call ---------------------------
PYTEST_TMPDIR/test_needsfiles0
========================= short test summary info ==========================
FAILED test_tmp_path.py::test_needsfiles - assert 0
1 failed in 0.12s

一時ディレクトリの処理に関する詳細情報は、一時ディレクトリとファイル で確認できます。

次のコマンドを使用して、どのような組み込みの pytest フィクスチャ が存在するかを確認します:

pytest --fixtures   # shows builtin and custom fixtures

このコマンドは、-v オプションが追加されない限り、先頭に _ が付いたフィクスチャを省略します。

読み続ける

独自のワークフローに合わせてテストをカスタマイズするのに役立つ追加の pytest リソースを確認してください: