はじめに
データアナリティクス事業本部のkobayashiです。
Pytestでmark.parametrizeを使って多数のサブテストを行っている際にどのサブテストでエラーが起きたのかを判断したい場合にサブテスト名をつけると便利です。その内容をまとめます。
- How to parametrize fixtures and test functions — pytest documentation
- Parametrizing tests — pytest documentation
-
参考
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
の条件に含まれるためたとえば以下のように実行するとid
にpositive
を含んだケースに絞ってテストできます。
$ 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をすることでテスト結果にサブテスト名をつけてみました。これによりテスト結果がわかりやすくなったのとテスト実行対象を絞る事ができるので積極的に使っていきたいです。
最後まで読んで頂いてありがとうございました。