きり丸の技術日記

技術検証したり、資格等をここに残していきます。

PythonのParametrizeをもっと便利に使う(pytest.param)

始めに

小ネタ。

以前、PythonのPytestでParametrizedTestをする記事を書きました。

調べたときに、もう少し便利できることが分かったのでメモします。

環境

  • Python
    • 3.13
  • Pytest
    • 8.3.4

実装

id属性

次のようにするとテストがどのような期待をもつかを人間に伝えることができます。

@pytest.mark.parametrize(
    "input_birth_day, expected_age",
    [
        (pytest.param(date(2023, 11, 20), 1, id="当日")),
        (pytest.param(date(2023, 11, 19), 2, id="前日")),
        (pytest.param(date(2022, 11, 21), 3, id="1年前+1日")),
        (pytest.param(date(2022, 11, 20), 4, id="1年前当日")),
        (pytest.param(date(2022, 11, 19), 5, id="1年前-1日")),
        (pytest.param(date(1992, 2, 4), 65, id="筆者の年齢")),
    ],
)

設定する前は次のように、[input_birth_day0-1]といったパラメータをそのまま出力してしまいます。

FAILED tests/unit/models/test_user.py::TestAge::test_one[input_birth_day0-1] - AssertionError: assert 0 == 1
FAILED tests/unit/models/test_user.py::TestAge::test_one[input_birth_day1-2] - AssertionError: assert 0 == 2
FAILED tests/unit/models/test_user.py::TestAge::test_one[input_birth_day2-3] - AssertionError: assert 0 == 3
FAILED tests/unit/models/test_user.py::TestAge::test_one[input_birth_day3-4] - AssertionError: assert 1 == 4
FAILED tests/unit/models/test_user.py::TestAge::test_one[input_birth_day4-5] - AssertionError: assert 1 == 5
FAILED tests/unit/models/test_user.py::TestAge::test_one[input_birth_day5-65] - AssertionError: assert 31 == 65

設定すると次のように出力されます。残念なことに日本語が対応されておりません。英語で書けるなら便利なのですが…。

FAILED tests/unit/models/test_user.py::TestAge::test_one[\u5f53\u65e5] - AssertionError: assert 0 == 1
FAILED tests/unit/models/test_user.py::TestAge::test_one[\u524d\u65e5] - AssertionError: assert 0 == 2
FAILED tests/unit/models/test_user.py::TestAge::test_one[1\u5e74\u524d+1\u65e5] - AssertionError: assert 0 == 3
FAILED tests/unit/models/test_user.py::TestAge::test_one[1\u5e74\u524d\u5f53\u65e5] - AssertionError: assert 1 == 4
FAILED tests/unit/models/test_user.py::TestAge::test_one[1\u5e74\u524d-1\u65e5] - AssertionError: assert 1 == 5
FAILED tests/unit/models/test_user.py::TestAge::test_one[\u9069\u5f53\u306a\u5e74\u9f62] - AssertionError: assert 31 == 65

pytest.iniに次の設定を入れても、Pytest 8.3.4では対応されておりません。

# pytestのIDの日本語化
disable_test_id_escaping_and_forfeit_all_rights_to_community_support = True

頑張って英語で書くか、コードを読むとき用であると割り切って使いましょう。または、PRチャンスです。

marks属性

実行するケースの1つ1つにmarks属性を与えることも可能です。

(pytest.param(date(2023, 11, 20), 20, id="当日", marks=pytest.mark.xfail)),

ただし、marks属性は現在のところ、次の4つです。

  • skip
    • テストをスキップさせる
  • skipif
    • 条件付きでテストをスキップさせる
  • xfail
    • 失敗する可能性があるテストに付与して失敗を許容する
      • xfailのマークがついていて、成功・失敗したら次のようなメッセージになる
      • ==== 4 passed, 43 deselected, 1 xfailed, 1 xpassed in 0.09s ====
  • カスタムマーク
    • 自由にマークを付けられる

parametrizeで使いたかったのはnow等の部分的に実行するマークだったのですが、それは提供されていません。

カスタムマークを付与しつつ、実行時に絞り込むしかないようです。

(pytest.param(date(2023, 11, 20), 20, id="当日", marks=pytest.mark.now)),
pytest -m now

indirect属性

paraqmetrizeで受け取ったパラメータをもとに、データのセットアップ等のfixtureを実行する際に使用します。

なお、いくつかケースを探したのですが、fixture内で処理せずに普通に処理したり、テストケース自体を変更したほうがよさそうなユースケースばかりしかヒットしなかったので今回の記事では紹介するだけにとどめます。

これで、処理速度が削減できるなら採用するメリットはあるのですが…。

ソースコード

終わりに

結局のところ、id属性しか有用ではないですが、それもASCIIエスケープされてしまうため、なかなか難しいですね。

1時間くらいASCIIエスケープしている個所を追いましたが、かなり手前で処理されてしまっているようで処理している個所は追えず…。parametrize.valuesのパラメータはASCIIエスケープされていないので、おそらくできないことはないのでしょうがかなり難しいです。

ぜひ興味があれば挑戦してみてください。

類似情報