pytest について
pytest Standardsは、Django固有のパターンも含むpytestのテスト規約を定めるスキルです。Python開発者がテストを一貫して整理、実行、検証できるよう支援します。
主な機能
- 01Arrange-Act-Assertによるテスト構成
- 02テストファイルと共有フィクスチャの整理
- 03パラメーター化による入力パターンの検証
- 04簡潔なテスト実行設定
- 05DjangoのデータベースとAPIテストの指針
- 06回帰、キャッシュ、モック、E2Eのテスト
活用シーン
- 01Django APIエンドポイントのテスト作成
- 02E2Eテストを除外したテストスイートの実行
- 03統合機能の不具合に対する回帰テスト追加
- 04異なるリクエスト間のキャッシュ分離の検証
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ imankulov/ skills --skill pytestインストール後、Claude Code で/pytestを入力して読み込まれたか確認します。
詳細
Pytest の規約
Django 固有のパターンを含む、pytest の推奨テスト規約です。
AAA パターン(Arrange-Act-Assert)
すべてのテストで、セットアップ、実行、検証を区切る # Arrange、# Act、# Assert のコメントを使って構成します。Arrange は主にフィクスチャを使います。必要のないセクションは空のままにせず、省略してください。論理的に関連していれば、複数のアサーションを記述してもかまいません。
def test_user_service_create(sample_user, db_session):
# Arrange
service = UserService(db_session)
# Act
created_user = service.create(sample_user)
# Assert
assert created_user.id is not None
assert created_user.name == sample_user.name
テストの構成
- 各モジュール内の
tests/サブディレクトリにテストを保存します。 - テスト対象のモジュールに合わせてテストファイルに名前を付けます:
services.py→tests/test_services.py。 - フラットな pytest 関数を記述し、テストクラスは使いません。
フィクスチャ
フィクスチャを作成する前に、次の順序で既存のものを探してください:ルートの conftest.py、モジュールの tests/conftest.py、現在のテストファイル。それらのどれも適さない場合に限り、新しいフィクスチャを作成します。
モジュール内の複数のテストファイルで共有するフィクスチャは、個々のテストファイルではなく tests/conftest.py に配置します。フィクスチャはスコープの順に並べます:session → module → function。
パラメーター化
ほぼ同じテスト本体を繰り返すのではなく、複数の入力と出力の組み合わせをテストするには、@pytest.mark.parametrize を使います。
テストの実行
# All tests (excluding e2e)
uv run pytest -m "not e2e" -q --tb=short
# Stop at the first failure, then iterate on only what failed
uv run pytest -m "not e2e" -q --tb=short -x
uv run pytest --lf -q --tb=short
# Full assertion diff for one test, once you know which one failed
uv run pytest path/to/test_file.py::test_function_name -vv
-q --tb=short を付けて実行します。-q は成功したテストを進捗ドットにまとめつつ、FAILED path::test_name - reason のサマリーは保持するため、テストスイートが大きくなっても出力が簡潔です。一方、-v/-vv は成功したテストごとに1行を出力するため、500件のテストがすべて成功する場合、-q では約150トークンで済むコンテキストを約10kトークン消費します。
-s は付けないでください。pytest は失敗したテストの Captured stdout call、Captured stderr call、Captured log call の各セクションをすでに再表示します。-s を付けても追加されるのは成功したテストの出力だけです。
-vv に切り替える場合は、スイート全体ではなく単一のノード ID に対して使います。デフォルトの詳細度では、pytest は長いアサーションの差分を異なる項目に絞って表示し、use -vv to show と出力します。切り詰められた差分では不十分な場合は、そのテストだけを再実行してください。
デフォルトでは e2e テストを除外します。e2e テストは外部サービスを呼び出し、実行が遅く、不安定になりやすいためです。変更が e2e テスト対象のコードに直接影響する場合に限り、e2e テストを実行します。
すべての実行でデフォルトが適用されるよう、pyproject.toml に設定します:
[tool.pytest.ini_options]
addopts = "-q --tb=short"
Django テストデータベース
Django プロジェクトでは、addopts に --reuse-db を追加し、テストデータベースを毎回削除してマイグレーションから再構築するのではなく、実行間で保持します:
[tool.pytest.ini_options]
DJANGO_SETTINGS_MODULE = "myproject.settings"
addopts = "-q --tb=short --reuse-db"
マイグレーションを追加しても、特別なフラグは必要ありません。--reuse-db がスキップするのはデータベースの削除と再作成だけで、その後の migrate は実行されるため、新しいマイグレーションは次回の実行時に適用されます。これに --no-migrations を組み合わせると問題が起きます。この場合、スキーマは run_syncdb によって構築されますが、作成されるのは不足しているテーブルだけで、既存のテーブルは変更されません。そのため、以降はスキーマを変更するたびに --create-db が必要になります。
再利用したデータベースが本当に壊れており、現在のモデルと矛盾するスキーマエラーが発生した場合は、--create-db を使います:
OperationalError: table myapp_thing has no column named shade— 既存のマイグレーションがその場で編集されたため、データベースでは適用済みとして記録され、再実行されていません。IntegrityError: NOT NULL constraint failed: ...またはInconsistentMigrationHistory— 現在のブランチにはないマイグレーションが、データベースに残っています。
uv run pytest --create-db
--create-db はその実行時に --reuse-db より優先され、再構築したデータベースは次回の実行にも保持されます。そのため、addopts に追加せず、1回だけ指定してください。
Django のデータベースアクセス
フィクスチャが db に直接または間接的に依存していれば、テストは自動的にデータベースへアクセスできます。どのフィクスチャもその依存関係を持たない場合に限り、テストに @pytest.mark.django_db が必要です。
# No decorator — client_with_user brings db via the user fixture
def test_create_report_success(client_with_user):
response = client_with_user.post("/api/v1/reports", ...)
assert response.status_code == 200
# Decorator needed — client alone doesn't bring db
@pytest.mark.django_db
def test_create_report_requires_auth(client):
response = client.post("/api/v1/reports", ...)
assert response.status_code == 401
Django API のテスト
API エンドポイントのテストには Django のテストクライアントを使います。認証されていないリクエストには client フィクスチャ、認証済みのリクエストには client_with_user を使います。ninja.testing.TestClient ではなく、これらを使ってください。JSON を送信する際は content_type="application/json" を指定します。データベースを変更するテストでは、変更後の状態を ORM アサーションで確認します:
assert User.objects.get(id=user.id).settings.theme == "dark"
モッククライアントのテスト
テスト環境では、実際のモックプロバイダーを使用する .env.test を通じてクライアントを設定するため、モッククライアントを直接使用し、キャプチャしたデータを検証してください。モッククライアントのクラス定数を直接参照し、グローバル状態は変更しないでください(make_empty() などは使用しないでください)。get_*_client() ファクトリー関数はモックしないでください。モックすると依存関係の連鎖が壊れます。
def test_event_is_captured(posthog_client):
some_function_that_captures_event()
assert len(posthog_client.events) == 1
assert posthog_client.events[0]["event"] == "oauth_completed"
HTTP レスポンスのアサーション
短いレスポンスでは、本文全体を検証してください(assert response.json() == {...})。長いレスポンスでは、ペイロード全体ではなく、重要な主要フィールドを検証してください。
リグレッションテスト
報告されたバグは、できるだけ安定した公開境界で再現してください。リグレッションテストでは、新たに追加されたヘルパーだけをテストしたり、引数が転送されたことだけを検証したりするのではなく、失敗したリクエスト形式、入力の正規化、出力を使用してください。
インテグレーションでは、生成されたプロバイダーリクエストがテスト対象の動作である場合、サードパーティへの実際の呼び出しは避けてください。設定済みのモック/クライアント境界でリクエストをキャプチャし、バグの原因となったフィルター、正規化された識別子、またはキャッシュ入力を正確に検証してください。複雑なヘルパーには焦点を絞ったユニットテストを用意しますが、それを境界でのリグレッションテストの代わりにしないでください。
キャッシュ識別性テスト
動作がキャッシュされる場合、結果を変える可能性のある入力をすべて列挙してください。各入力はキャッシュキーに含めるか、選択されたプロパティ ID のように、キャッシュキーに含まれる識別情報に解決される必要があります。意味的に異なる 2 つのリクエストが同じキャッシュエントリーを共有しないことを示すテストを追加してください。特に、テナント、ロケール、スコープ、フィルター、権限、日付の入力に注意してください。
より強力な解決済みの識別情報によって結果がすでに区別される場合は、キーの構成要素を重複して追加しないでください。識別情報をテスト内に記載し、後から解決方法が変更された際に、前提が知らないうちに無効にならないようにしてください。
E2E テスト
ローカルの pytest の実行時にデフォルトでスキップすべきテストには、@pytest.mark.e2e マーカーを付けてください。対象となるのは、次のようなテストです。
- 外部/サードパーティのサービス(実際の LLM プロバイダー、Stripe、GitHub API など)を呼び出す
- 有料リソースが必要、または API クォータを消費する
- 標準的な開発環境以外の追加セットアップや認証情報が必要(実際のデータベース、ブラウザードライバー、シークレット、内部ホストへのネットワークアクセスなど)
- 実際のブラウザーを操作する(Playwright、Selenium)
- 目立って時間がかかる(数秒以上)か、本質的に不安定である
プロセス内でサーバー、モック対象、またはローカルのサブプロセスを起動し、自己完結して実行するテストは、インテグレーションテストであり、E2E テストではありません。ランダムなポートで実際の uvicorn を起動して localhost にリクエストするテストも同様です。
マーカーだけで十分です。os.environ のチェックは不要です。CI では pytest -m "not e2e" でスキップします。ファイル内のすべてのテストにマーカーを付けるには、モジュールレベルで pytestmark = pytest.mark.e2e を設定してください。