0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【pytest】ディレクトリ分割とマーカーをどう使い分けるか整理する

0
Posted at

概要

pytest で unit / integration / e2e テストを運用するときに、ディレクトリで実行対象を分けるべきか、pytest のマーカーで制御するべきかを整理します。

今回は、以下のような前提のプロジェクトを想定します。

  • tests/unit, tests/integration, tests/e2e でディレクトリを分けている
  • pyproject.tomlintegration / e2e マーカーを登録している
  • CI では uv run pytest tests/unit -v のようにディレクトリ単位で実行している

結論としては、CI の主制御はディレクトリで行い、マーカーは補助的に使うのが扱いやすいです。


対象読者

  • pytest のマーカー運用を整理したい方
  • unit / integration / e2e テストを分けて運用したい方
  • CI でどのようにテストを実行対象ごとに分離するか悩んでいる方

前提条件

今回の前提となる pytest 設定は以下です。

[tool.pytest.ini_options]
# markerの誤字を検知するため、未登録markerはエラーにする
addopts = "--strict-markers"
markers = [
    "integration: Databricks Connect経由でServerless computeに接続する統合テスト",
    "e2e: Databricks Asset Bundles経由でジョブ/パイプラインを実行するE2Eテスト",
]

また、テストディレクトリは以下のように分かれている想定です。

tests/
├─ unit/
├─ integration/
└─ e2e/

結論

今回のように tests/unit, tests/integration, tests/e2e で物理的に分かれている構成 なら、方針は次のようにするのが妥当です。

  • CI の実行対象制御はディレクトリで行う

    • uv run pytest tests/unit -v
    • uv run pytest tests/integration -v
    • uv run pytest tests/e2e -v
  • integration / e2e マーカーは残す

    • テストの意味をコード上で明示できる
    • --strict-markers により誤字や誤分類を検知できる
    • 将来的に -m で柔軟に絞り込める
  • unit テストは現時点ではマーカーなしでもよい

    • tests/unit という配置自体が unit テストであることを表せるため

つまり、実行制御はディレクトリ、意味付けと保険としてマーカーという役割分担にすると整理しやすいです。


背景・課題

pytest では、テストを絞り込む方法として大きく次の 2 つがあります。

  • ディレクトリで分ける

    • pytest tests/unit
  • マーカーで分ける

    • pytest -m integration

一見するとどちらでも実現できますが、両者は役割が少し異なります。

特に次のような状態だと迷いやすくなります。

  • すでに tests/unit, tests/integration, tests/e2e に分けている
  • さらに @pytest.mark.integration@pytest.mark.e2e も使っている
  • CI ではどちらを基準に実行するべきか決めたい

解決方法

方法概要

おすすめの役割分担は次のとおりです。

目的 使うもの
CI で unit / integration / e2e を分けて実行する ディレクトリ
テストの意味をコード上で明示する マーカー
typo や未登録マーカーを検知する --strict-markers
将来的に細かく絞り込む pytest -m ...

なぜディレクトリ主導がよいのか

1. CI の意図が分かりやすい

たとえば CI で unit テストを実行するなら、次のコマンドはかなり直感的です。

uv run pytest tests/unit -v

一方で、マーカーだけで unit テストを表現しようとすると、たとえば次のようになります。

uv run pytest -m "not integration and not e2e" -v

これは動作としては可能ですが、コマンドだけ見たときに 「unit テストを実行している」ことが分かりづらいです。


2. ディレクトリ構成自体がテスト分類になる

tests/unit に置かれていれば unit テスト、tests/e2e に置かれていれば e2e テストです。
つまり、配置場所そのものが分類情報になるため、レビュー時にも把握しやすくなります。

たとえば tests/unit に Databricks Connect を使う重いテストが紛れ込んだ場合、ディレクトリだけでも違和感に気づきやすくなります。


3. unit を「消極的定義」にしなくて済む

マーカー中心の運用だと、unit テストは次のように定義されがちです。

  • integration でも e2e でもないもの = unit

これは unit が 「残り物のカテゴリ」 になりやすいです。
一方で、ディレクトリで tests/unit を用意しておけば、unit を明示的に扱えます。


マーカーは不要なのか

不要ではありません。
ディレクトリで実行制御していても、integration / e2e マーカーは残す価値があります。

理由1: テストの意味がコード上で分かる

たとえば integration テストに次のようなマーカーが付いていると、テストを単体で見たときにも意味が分かります。

import pytest

@pytest.mark.integration
def test_read_table():
    ...

ファイル単体を読んだときにも、**「これは Databricks Connect を使う統合テストだな」**と分かります。


理由2: --strict-markers で誤字を防げる

今回の設定では、未登録マーカーを使うとエラーになります。

[tool.pytest.ini_options]
addopts = "--strict-markers"
markers = [
    "integration: Databricks Connect経由でServerless computeに接続する統合テスト",
    "e2e: Databricks Asset Bundles経由でジョブ/パイプラインを実行するE2Eテスト",
]

たとえば次のような typo はエラーになります。

import pytest

@pytest.mark.integrtion  # 誤字
def test_x():
    ...

マーカーは実行制御だけでなく、分類ラベルの品質保証にも役立ちます。


理由3: 将来的に -m で柔軟に絞り込める

今は CI をディレクトリで回していても、将来的にこんな要件が出ることがあります。

  • integration の中でも重いテストだけ除外したい
  • e2e の中でも一部だけ回したい
  • nightly 実行だけ対象を増やしたい

そういうとき、マーカーがあると -m による柔軟な抽出ができます。


実装例

pytest の設定

[tool.pytest.ini_options]
# markerの誤字を検知するため、未登録markerはエラーにする
addopts = "--strict-markers"
markers = [
    "integration: Databricks Connect経由でServerless computeに接続する統合テスト",
    "e2e: Databricks Asset Bundles経由でジョブ/パイプラインを実行するE2Eテスト",
]

テストディレクトリ構成

tests/
├─ unit/
├─ integration/
└─ e2e/

integration テストの例

import pytest

@pytest.mark.integration
def test_read_table():
    # Databricks Connect を使う統合テスト
    ...

e2e テストの例

import pytest

@pytest.mark.e2e
def test_pipeline_run():
    # Databricks Asset Bundles 経由でジョブ/パイプラインを実行するE2Eテスト
    ...

CI での実行例

unit テスト

uv run pytest tests/unit -v

integration テスト

uv run pytest tests/integration -v

e2e テスト

uv run pytest tests/e2e -v

このように、CI ではディレクトリで分けると意図が分かりやすくなります。


pytest -m を使うとどうなるか

マーカーを使うと、次のような絞り込みができます。

integration だけ実行

pytest -m integration

e2e だけ実行

pytest -m e2e

integration と e2e を除いたテストだけ実行

pytest -m "not integration and not e2e"

ただし、今回のようにディレクトリ構成がきれいに分かれている場合、CI の主制御としてはディレクトリの方が分かりやすいです。
-m は補助的に使うのが扱いやすいです。


ハマりどころ

1. マーカーを付けただけでは通常実行から除外されない

たとえば次のようなテストがあるとします。

import pytest

@pytest.mark.e2e
def test_pipeline():
    ...

この状態で pytest を実行すると、e2e テストも普通に実行されます
マーカーは「分類ラベル」であり、付けただけでは自動で除外されません

除外したい場合は、-m で条件を指定する必要があります。

pytest -m "not e2e"

2. pytest -m integration は unit テストを含まない

pytest -m integration

integration マーカーが付いたテストだけを実行します。
unit テストが一緒に実行されるわけではありません。

そのため、CI で「unit テストを実行する」目的で -m を使うと、式がやや読みにくくなりがちです。

pytest -m "not integration and not e2e"

この点も、unit テストの主制御をディレクトリに寄せる理由になります。


3. ディレクトリとマーカーがズレると運用が崩れやすい

たとえば tests/integration にあるのに @pytest.mark.integration が付いていない、あるいは tests/unit@pytest.mark.e2e が付いている、といった状態は避けたいです。

ルールとして次を決めておくと運用しやすくなります。

  • tests/integration/** のテストには @pytest.mark.integration を付ける
  • tests/e2e/** のテストには @pytest.mark.e2e を付ける
  • tests/unit/** には integration / e2e を付けない

まとめ

  • tests/unit, tests/integration, tests/e2e のようにディレクトリが分かれているなら、CI の主制御はディレクトリで行うのが分かりやすいです
  • integration / e2e マーカーは削らず、意味付けと保険として残すのが有効です
  • --strict-markers を使うと、マーカーの誤字や未登録マーカーを検知できます
  • pytest -m ... は便利ですが、今回のような構成では CI の主制御より補助用途に向いています
  • 役割分担としては、実行制御はディレクトリ、分類と柔軟な抽出はマーカーに寄せると整理しやすいです
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?