Pytestでカスタムマークを使って特定のテストのみを実行する

2024.03.27

はじめに

データアナリティクス事業本部のkobayashiです。

Pytestでテストケースが増えてきた場合にすべてのテストケースをテスをするのではなく、新しく実装した機能だけとか特定のリソースを使ったテストケースのみをテストしたいといった場面があるかと思います。そのような場合はカスタムマーカーを使うことで解決できるのでその内容をまとめます。

Pytestのカスタムマーカー

pytestではカスタムマーカーを使うことでテストケースにカスタムメタデータを付与することでテストケースをpytest.markでマークできます。マークしたテストはpytestの-mオプションを使うことで実行するテストを指定してしてテストすることができます。

Pytestのカスタムマーカーを使ってみる

環境

  • Python: 3.11.4
  • pytest: 7.4.3

カスタムマークはモジュール・クラス・関数に付けることができます。さらにparametrizeを使った際にもparametrizeの個々のテストケースにマークを付けることができるので大変便利です。

モジュール、クラス、関数にカスタムマークを付ける

でははじめにモジュール、クラス、関数にカスタムマークを付けてテストを実行してみたいと思います。

test_main.py

import pytest

from main import calc_pi

# モジュールにカスタムマークを付ける
pytestmark = pytest.mark.custom_mark_module

# クラスにカスタムマークを付ける
@pytest.mark.custom_mark_class
class TestPi_1:
    @pytest.mark.parametrize(
        ["data_in"],
        [
            pytest.param(1000000),
        ],
    )
    # 関数にカスタムマークを付ける
    @pytest.mark.custom_mark_func
    def test_calc_pi_1(self, data_in):
        ret = calc_pi(data_in)
        assert ret > 3.14

    @pytest.mark.parametrize(
        ["data_in"],
        [
            pytest.param(1000000),
        ],
    )
    def test_calc_pi_2(self, data_in):
        ret = calc_pi(data_in)
        assert ret > 3.14


class TestPi_2:
    @pytest.mark.parametrize(
        ["data_in"],
        [
            pytest.param(1000000),
        ],
    )
    def test_calc_pi_3(self, data_in):
        ret = calc_pi(data_in)
        assert ret > 3.14

    @pytest.mark.parametrize(
        ["data_in"],
        [
            pytest.param(1000000),
        ],
    )
    def test_calc_pi_4(self, data_in):
        ret = calc_pi(data_in)
        assert ret > 3.14

モジュール全体にカスタムマークを付ける場合はGlobal変数としてカスタムマークを付けます。クラス、関数の場合はデコレータでカスタムマークを指定します。

カスタムマークを付けたテストを実行する

はじめにカスタムマークを指定しない状態でテストを実行してみます。

$ pytest test_main.py
============================= test session starts ==============================
collecting ... collected 4 items

test_main.py::TestPi_1::test_calc_pi_1[1000000] 
test_main.py::TestPi_1::test_calc_pi_2[1000000] 
test_main.py::TestPi_2::test_calc_pi_3[1000000]
test_main.py::TestPi_2::test_calc_pi_4[1000000] 

======================== 4 passed, 3 warnings in 2.22s ========================= 
PASSED                   [ 25%]PASSED                   [ 50%]
PASSED                   [ 75%]PASSED                   [100%]

すべてのテストケースがテストされています。

次にカスタムマーカーcustom_mark_moduleを付けてテストを実行してみます。

$ pytest -m custom_mark_module test_main.py
============================= test session starts ==============================
collecting ... collected 4 items

test_main.py::TestPi_1::test_calc_pi_1[1000000] 
test_main.py::TestPi_1::test_calc_pi_2[1000000] 
test_main.py::TestPi_2::test_calc_pi_3[1000000]
test_main.py::TestPi_2::test_calc_pi_4[1000000] 

======================== 4 passed, 3 warnings in 2.22s ========================= 
PASSED                   [ 25%]PASSED                   [ 50%]
PASSED                   [ 75%]PASSED                   [100%]

このケースでは1つのファイルでしかテストを行っていないのでマークを指定してない状態と同じテスト結果となります。

次にカスタムマーカーcustom_mark_classを付けてテストを実行してみます。

$ pytest -m custom_mark_class test_main.py
============================= test session starts ==============================
collecting ... collected 4 items / 2 deselected / 2 selected

test_main.py::TestPi_1::test_calc_pi_1[1000000] 
test_main.py::TestPi_1::test_calc_pi_2[1000000] 

================= 2 passed, 2 deselected, 3 warnings in 0.96s ==================
PASSED                   [ 50%]PASSED                   [100%]

このケースでは先ほどと違いcustom_mark_classをつけたクラスのみテストが行われています。

次にカスタムマーカーcustom_mark_funcを付けてテストを実行してみます。

$ pytest -m custom_mark_func test_main.py
============================= test session starts ==============================
collecting ... collected 4 items / 3 deselected / 1 selected

test_main.py::TestPi_1::test_calc_pi_1[1000000] 

================= 1 passed, 3 deselected, 3 warnings in 0.47s ==================
PASSED                   [100%]

このケースではcustom_mark_funcをつけた関数のみテストが行われています。

parametrizeにカスタムマークを付ける

さらにparametrizeの個々のテストケースにマークを付けてテストを実行してみたいと思います。

test_main2.py

import pytest

from main import calc_pi

@pytest.mark.parametrize(
    ["data_in"],
    [
        pytest.param(1000001, marks=pytest.mark.custom_mark_odd),
        pytest.param(1000002),
        pytest.param(1000003, marks=pytest.mark.custom_mark_odd),
        pytest.param(1000004),
    ],
)
def test_calc_pi(data_in):
    ret = calc_pi(data_in)
    assert ret > 3.14

parametrizeの個々のテストケースにマークを付ける場合にはpytest.parammarksパラメータでカスタムマークを指定します。

parametrizeにカスタムマークを付けたテストを実行する

はじめにカスタムマークを指定しない状態でテストを実行してみます。

$ pytest test_main2.py
============================= test session starts ==============================
collecting ... collected 4 items

test_main.py::test_calc_pi[1000001] 
test_main.py::test_calc_pi[1000002] 
test_main.py::test_calc_pi[1000003] 
test_main.py::test_calc_pi[1000004] 

======================== 4 passed, 2 warnings in 1.86s =========================
PASSED                               [ 25%]PASSED                               [ 50%]PASSED                               [ 75%]PASSED                               [100%]

カスタムマーカーcustom_mark_oddを付けてテストを実行してみます。custom_mark_oddをつけたparametrize内のテストケースのみテストが行われていることがわかります。

$ pytest -m custom_mark_odd test_main2.py
============================= test session starts ==============================
collecting ... collected 4 items / 2 deselected / 2 selected

test_main.py::test_calc_pi[1000001] 
test_main.py::test_calc_pi[1000003] 

================= 2 passed, 2 deselected, 2 warnings in 0.91s ==================
PASSED                               [ 50%]PASSED                               [100%]

まとめ

Pytestでカスタムマークを使ってテストを実行してみました。Pytestでテストケースが増えてきた場合にはカスタムマークをテストケースに付けることで新しく実装した機能だけとか特定のリソースを使ったテストケースのみをテストしたいといった場合にはとても役に立つ機能だと思います。なお、カスタムマークを使わない場合は-kオプションを使うことでテスト名の部分一致で実行するテストを選択することができます。ただ個人的にはカスタムマークを使った完全一致でのテスト選択のほうが目的のテストをピンポイントで実行できるので使い勝手が良いのではと思います。

最後まで読んで頂いてありがとうございました。