pytestのmark.parametrizeでテスト名を付ける

2024.02.13

はじめに

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

Pytestでmark.parametrizeを使って多数のサブテストを行っている際にどのサブテストでエラーが起きたのかを判断したい場合にサブテスト名をつけると便利です。その内容をまとめます。

mark.parametrizeに名前をつける

まず先に結論です。 mark.parametrizeで行うサブテストに名前を付けるにはidパラメータを付けることで実現できます。

Parametrizing tests — pytest documentation

それでは実際に以下の足し算と引き算を行う関数のコードのテストを作成して試してみます。

main.py

def add_sub(x, y):
    return [x + y, x - y]

テスト名を付けないサブテスト

main.pyのadd_sub関数に対して以下のテストを行います。

test_main.py

import pytest

from main import add_sub


@pytest.mark.parametrize(
    ["data_in", "expected_data"],
    [
        pytest.param([1, 2], [3, -1]),
        pytest.param([0, 0], [0, 0]),
        pytest.param([-1, -2], [-3, 1]),
        pytest.param([1, -2], [-1, 3]),
        pytest.param([-1, 2], [1, -3]),
        pytest.param([200, 800], [1000, -600]),
    ],
)
def test_add_sub(data_in, expected_data):
    assert add_sub(data_in[0], data_in[1]) == expected_data
$ pytest -v                                     
platform darwin -- Python 3.11.4, pytest-7.4.3, pluggy-1.3.0 -- /Users/kobayashi.masahiro/CMProject/developers.io/developers.io_22/venv/bin/python
cachedir: .pytest_cache
rootdir: /Users/kobayashi.masahiro/CMProject/developers.io/developers.io_22/devio_22/pytest-param
plugins: xdist-3.5.0, anyio-3.7.0
collected 6 items                                                                                                                                                            

test_main.py::test_add_sub[data_in0-expected_data0] PASSED                                                                                                           [ 16%]
test_main.py::test_add_sub[data_in1-expected_data1] PASSED                                                                                                           [ 33%]
test_main.py::test_add_sub[data_in2-expected_data2] PASSED                                                                                                           [ 50%]
test_main.py::test_add_sub[data_in3-expected_data3] PASSED                                                                                                           [ 66%]
test_main.py::test_add_sub[data_in4-expected_data4] PASSED                                                                                                           [ 83%]
test_main.py::test_add_sub[data_in5-expected_data5] PASSED

idを付けない状態ではpytest -vの結果からはパラメータ値に基づく名前が自動的に付けられます。

この実行方法だと上から順に実行されるのでまだどのテストかは判断できますが、pytest-xdistなどを使って並列実行してみると非常にわかりにくく、parametrizeが増えてくるとさらにわかりにくくなります。

pytest -v -n 4                                
=========================================================================== test session starts ============================================================================
platform darwin -- Python 3.11.4, pytest-7.4.3, pluggy-1.3.0 -- /Users/kobayashi.masahiro/CMProject/developers.io/developers.io_22/venv/bin/python
cachedir: .pytest_cache
rootdir: /Users/kobayashi.masahiro/CMProject/developers.io/developers.io_22/devio_22/pytest-param
plugins: xdist-3.5.0, anyio-3.7.0
4 workers [6 items]     
scheduling tests via LoadScheduling

test_main.py::test_add_sub[data_in1-expected_data1] 
test_main.py::test_add_sub[data_in0-expected_data0] 
[gw1] [ 16%] PASSED test_main.py::test_add_sub[data_in1-expected_data1] 
test_main.py::test_add_sub[data_in5-expected_data5] 
[gw1] [ 33%] PASSED test_main.py::test_add_sub[data_in5-expected_data5] 
[gw0] [ 50%] PASSED test_main.py::test_add_sub[data_in0-expected_data0] 
test_main.py::test_add_sub[data_in4-expected_data4] 
test_main.py::test_add_sub[data_in2-expected_data2] 
[gw2] [ 66%] PASSED test_main.py::test_add_sub[data_in2-expected_data2] 
[gw0] [ 83%] PASSED test_main.py::test_add_sub[data_in4-expected_data4] 
test_main.py::test_add_sub[data_in3-expected_data3] 
[gw3] [100%] PASSED test_main.py::test_add_sub[data_in3-expected_data3]

テスト名を付けたサブテスト

そこでidを付けて再度テストを行ってみたいと思います。

test_main.py

import pytest

from main import add_sub


@pytest.mark.parametrize(
    ["data_in", "expected_data"],
    [
        pytest.param([1, 2], [3, -1], id="positive positive"),
        pytest.param([0, 0], [0, 0], id="zero zero"),
        pytest.param([-1, -2], [-3, 1], id="negative negative"),
        pytest.param([1, -2], [-1, 3], id="positive negative"),
        pytest.param([-1, 2], [1, -3], id="negative positive"),
        pytest.param([200, 800], [1000, -600], id="hundred hundred"),
    ],
)
def test_add_sub_label(data_in, expected_data):
    assert add_sub(data_in[0], data_in[1]) == expected_data

これでテストをを行うと以下のような出力がされます。

$  pytest -v -n 4                                
=========================================================================== test session starts ============================================================================
platform darwin -- Python 3.11.4, pytest-7.4.3, pluggy-1.3.0 -- /Users/kobayashi.masahiro/CMProject/developers.io/developers.io_22/venv/bin/python
cachedir: .pytest_cache
rootdir: /Users/kobayashi.masahiro/CMProject/developers.io/developers.io_22/devio_22/pytest-param
plugins: xdist-3.5.0, anyio-3.7.0
4 workers [6 items]     
scheduling tests via LoadScheduling

test_main.py::test_add_sub_label[zero zero] 
test_main.py::test_add_sub_label[positive positive] 
[gw1] [ 16%] PASSED test_main.py::test_add_sub_label[zero zero] 
test_main.py::test_add_sub_label[hundred hundred] 
[gw1] [ 33%] PASSED test_main.py::test_add_sub_label[hundred hundred] 
[gw0] [ 50%] PASSED test_main.py::test_add_sub_label[positive positive] 
test_main.py::test_add_sub_label[negative positive] 
[gw0] [ 66%] PASSED test_main.py::test_add_sub_label[negative positive] 
test_main.py::test_add_sub_label[negative negative] 
[gw2] [ 83%] PASSED test_main.py::test_add_sub_label[negative negative] 
test_main.py::test_add_sub_label[positive negative] 
[gw3] [100%] PASSED test_main.py::test_add_sub_label[positive negative]

idをつける前と違いpytest -vで出力されるテスト結果にサブテスト名が表示されてどのテストケースの結果なのかがわかりやすくなりました。

またpytestの-kオプションで実行するテストケースを絞ってテストを実行できますが、idに指定した値も-kの条件に含まれるためたとえば以下のように実行するとidpositiveを含んだケースに絞ってテストできます。

$ pytest -v -k positive                         
=========================================================================== test session starts ============================================================================
collected 6 items / 3 deselected / 3 selected                                                                                                                              

test_main.py::test_add_sub_label[positive positive] PASSED                                                                                                           [ 33%]
test_main.py::test_add_sub_label[positive negative] PASSED                                                                                                           [ 66%]
test_main.py::test_add_sub_label[negative positive] PASSED

まとめ

pytestでmark.parametrizeにidをすることでテスト結果にサブテスト名をつけてみました。これによりテスト結果がわかりやすくなったのとテスト実行対象を絞る事ができるので積極的に使っていきたいです。

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