pytest で unittest ベースのテストを使用する方法

pytest は Python の unittest ベースのテストをすぐに実行できます。 これは、既存の unittest ベースのテストスイートを活用して pytest をテストランナーとして使用し、pytest の機能を最大限に活用するためにテストスイートを段階的に適応させることを目的としています。

既存の unittest スタイルのテストスイートを pytest で実行するには、次のように入力します:

pytest tests

pytest は test_*.py または *_test.py ファイル内の unittest.TestCase サブクラスとその test メソッドを自動的に収集します。

ほとんどすべての unittest 機能がサポートされています:

  • @unittest.skip スタイルのデコレータ;

  • setUp/tearDown;

  • setUpClass/tearDownClass;

  • setUpModule/tearDownModule;

さらに、subtestspytest-subtests プラグインによってサポートされています。

現時点では、pytest は次の機能をサポートしていません:

すぐに使える利点

pytest でテストスイートを実行することで、既存のコードをほとんど変更することなく、いくつかの機能を利用できます:

unittest.TestCase サブクラスでの pytest 機能

次の pytest 機能は unittest.TestCase サブクラスで動作します:

次の pytest 機能は動作しませんし、設計哲学の違いによりおそらく今後も動作しないでしょう:

サードパーティのプラグインは、プラグインとテストスイートによってはうまく動作する場合としない場合があります。

マークを使用して unittest.TestCase サブクラスに pytest フィクスチャを混在させる

pytest で unittest を実行すると、unittest.TestCase スタイルのテストで フィクスチャメカニズム を使用できます。 pytest のフィクスチャ機能を少なくともざっと読んだと仮定して、クラスキャッシュされたデータベースオブジェクトを設定し、unittest スタイルのテストからそれを参照する pytest db_class フィクスチャを統合する例を見てみましょう:

# content of conftest.py

# we define a fixture function below and it will be "used" by
# referencing its name from tests

import pytest


@pytest.fixture(scope="class")
def db_class(request):
    class DummyDB:
        pass

    # set a class attribute on the invoking test context
    request.cls.db = DummyDB()

これは、使用されると各テストクラスごとに一度呼び出され、クラスレベルの db 属性を DummyDB インスタンスに設定するフィクスチャ関数 db_class を定義します。 フィクスチャ関数は、cls 属性などの 要求されたテストコンテキスト へのアクセスを提供する特別な request オブジェクトを受け取ることでこれを実現します。 このアーキテクチャは、フィクスチャの記述を実際のテストコードから分離し、フィクスチャ名という最小限の参照によってフィクスチャを再利用できるようにします。 それでは、フィクスチャ定義を使用して実際の unittest.TestCase クラスを書いてみましょう:

# content of test_unittest_db.py

import unittest

import pytest


@pytest.mark.usefixtures("db_class")
class MyTest(unittest.TestCase):
    def test_method1(self):
        assert hasattr(self, "db")
        assert 0, self.db  # fail for demo purposes

    def test_method2(self):
        assert 0, self.db  # fail for demo purposes

@pytest.mark.usefixtures("db_class") クラスデコレータは、pytest フィクスチャ関数 db_class がクラスごとに一度呼び出されることを保証します。 意図的に失敗する assert 文のおかげで、トレースバックで self.db の値を確認できます:

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

test_unittest_db.py FF                                               [100%]

================================= FAILURES =================================
___________________________ MyTest.test_method1 ____________________________

self = <test_unittest_db.MyTest testMethod=test_method1>

    def test_method1(self):
        assert hasattr(self, "db")
>       assert 0, self.db  # fail for demo purposes
E       AssertionError: <conftest.db_class.<locals>.DummyDB object at 0xdeadbeef0001>
E       assert 0

test_unittest_db.py:11: AssertionError
___________________________ MyTest.test_method2 ____________________________

self = <test_unittest_db.MyTest testMethod=test_method2>

    def test_method2(self):
>       assert 0, self.db  # fail for demo purposes
E       AssertionError: <conftest.db_class.<locals>.DummyDB object at 0xdeadbeef0001>
E       assert 0

test_unittest_db.py:14: AssertionError
========================= short test summary info ==========================
FAILED test_unittest_db.py::MyTest::test_method1 - AssertionError: <conft...
FAILED test_unittest_db.py::MyTest::test_method2 - AssertionError: <conft...
============================ 2 failed in 0.12s =============================

このデフォルトの pytest トレースバックは、2つのテストメソッドが同じ self.db インスタンスを共有していることを示しており、これは上記のクラススコープのフィクスチャ関数を記述したときの意図でした。

autouse フィクスチャを使用して他のフィクスチャにアクセスする

特定のテストに必要なフィクスチャの使用を明示的に宣言する方が通常は良いですが、特定のコンテキストで自動的に使用されるフィクスチャが欲しい場合もあります。 結局のところ、従来の unittest セットアップスタイルでは、この暗黙のフィクスチャ記述の使用が義務付けられており、慣れているか好きである可能性があります。

フィクスチャ関数に @pytest.fixture(autouse=True) フラグを付けて、使用したいコンテキストでフィクスチャ関数を定義できます。 事前に初期化された samplefile.ini を持つ一時ディレクトリで TestCase クラスのすべてのテストメソッドを実行する initdir フィクスチャを見てみましょう。 私たちの initdir フィクスチャ自体は、pytest 組み込みの tmp_path フィクスチャを使用して、テストごとの一時ディレクトリの作成を委任します:

# content of test_unittest_cleandir.py
import unittest

import pytest


class MyTest(unittest.TestCase):
    @pytest.fixture(autouse=True)
    def initdir(self, tmp_path, monkeypatch):
        monkeypatch.chdir(tmp_path)  # change to pytest-provided temporary directory
        tmp_path.joinpath("samplefile.ini").write_text("# testdata", encoding="utf-8")

    def test_method(self):
        with open("samplefile.ini", encoding="utf-8") as f:
            s = f.read()
        assert "testdata" in s

autouse フラグのおかげで、initdir フィクスチャ関数は定義されているクラスのすべてのメソッドに対して使用されます。 これは、前の例のようにクラスに @pytest.mark.usefixtures("initdir") マーカーを使用するためのショートカットです。

このテストモジュールを実行すると ...:

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

... initdir フィクスチャ関数が test_method の前に実行されたため、1つのテストが合格しました。

注釈

unittest.TestCase メソッドは、一般的な unittest.TestCase テストスイートを実行する能力に影響を与える可能性があるため、フィクスチャ引数を直接受け取ることはできません。

上記の usefixtures および autouse の例は、pytest フィクスチャを unittest スイートに混在させるのに役立つはずです。

また、unittest.TestCase のサブクラス化から徐々に離れて プレーンなアサーション に移行し、段階的に pytest の完全な機能セットの恩恵を受け始めることもできます。

注釈

2つのフレームワークのアーキテクチャの違いにより、unittest ベースのテストのセットアップとティアダウンは、pytest の標準的な setup および teardown ステージではなく、テストの call フェーズ中に実行されます。 これは、特にエラーについて推論する際に、いくつかの状況で理解することが重要です。 たとえば、unittest ベースのスイートがセットアップ中にエラーを示した場合、pytest はその setup フェーズ中にエラーを報告せず、代わりに call 中にエラーを発生させます。