drift-testing について
Drift Skillは、OpenAPIおよびAsyncAPI仕様に基づくDriftのAPIコントラクトテストを案内します。APIテストを作成・保守するチーム向けに、テスト雛形、カバレッジ確認、検証ループのスクリプトと、OpenAPI用のPrismローカルモックを提供します。
主な機能
- 01OpenAPI・AsyncAPI仕様からテスト雛形を生成
- 02操作とレスポンスコードのカバレッジ確認
- 03検証とカバレッジのフィードバックループ実行
- 04OpenAPI用Prismモックサーバーの起動
- 05認証、フック、CLIのリファレンス
活用シーン
- 01未カバーの仕様操作にテストを作成
- 02OpenAPI仕様に照らしてREST APIを検証
- 03AsyncAPI 3.xのイベント駆動サービスをテスト
- 04Prismモックを使ったローカルAPI検証
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ pactflow/ pactflow-agent-skills --skill drift-testingインストール後、Claude Code で/drift-testingを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
evals/01-basic-get-endpoint.json02-stateful-delete-endpoint.json03-debug-failing-test.jsonevals/fixtures/products-api-with-failures.mdsimple-products-api.yamlreferences/auth.mdcli-reference.mdlua-api.mdmock-server.mdpactflow-and-cicd.mdtest-cases.mdscripts/asyncapi_targets.pycheck_coverage.pyextract_channels.pyextract_endpoints.pyrun_loop.ps1run_loop.shstart_mock.ps1start_mock.shscripts/tests/test_asyncapi_support.py詳細
Drift スキル
テスト対象の OpenAPI 仕様を変更しないでください。
参照ファイル
references/test-cases.md— テストケースの YAML スキーマ全体、すべてのパターン、データセット、式references/auth.md— 認証設定、動的トークン、標準以外のスキーム、401/403 のテストreferences/mock-server.md— Prism を使ったローカルテスト:セットアップ、Prefer ヘッダー、仕様の品質に関する問題references/lua-api.md— Lua API の全機能:ライフサイクルイベント、http()、dbg()、エクスポートされる関数references/cli-reference.md— すべての CLI コマンド/フラグ、設定、並列実行、終了コードreferences/pactflow-and-cicd.md— BDCT の公開ワークフロー、GitHub Actions、GitLab CI
スクリプト
scripts/extract_endpoints.py— 仕様を読み取り、すべてのオペレーションとレスポンスコードを出力します。 サマリーモードでは、仕様内に例がないパラメーターにフラグを付けます。スキャフォールドモード(--scaffold)では、認証パターン、404 用の nil UUID、4xx 用のignore.schema、FILL_INマーカーを適切に設定した、すぐに記入できるoperations:YAML ブロックを出力します。既存のテストファイルでまだカバーされていない項目のみを生成するには、--only-missing <drift.yaml>を使用します。pyyamlが必要です。scripts/extract_channels.py—extract_endpoints.pyの AsyncAPI 版です。AsyncAPI 3.x の仕様を読み取り、すべてのオペレーションと、そのアクションタイプ(send/receive)、実行モード(async-observe/async-inject/async-request-reply)、チャネルアドレスを出力します。スキャフォールドモード(--scaffold)では、モードに応じた正しいトリガー/プローブ構造を備えた、すぐに記入できる Drift のoperations:YAML スタブを出力します。まだカバーされていないオペレーションのみをスキャフォールドするには、--only-missing <drift.yaml>を使用します。pyyamlが必要です。AsyncAPI 3.x のみ対応しています。scripts/check_coverage.py— カバレッジチェッカー:OpenAPI または AsyncAPI の仕様と Drift のテストファイルを比較します。 OpenAPI のカバレッジではオペレーションとレスポンスコードを追跡し、AsyncAPI のカバレッジでは各オペレーション/メッセージの対象を追跡します。pyyamlが必要です。scripts/run_loop.sh/scripts/run_loop.ps1— フィードバックループランナー:すべてのテストがパスするまでdrift verify --failedを再試行し、その後check_coverage.pyを実行します。 終了コードが 0 になるには、両方のゲートを通過する必要があります。依存関係は uv を使って自動的にインストールされます。 Windows では.ps1版を使用してください。scripts/start_mock.sh/scripts/start_mock.ps1— OpenAPI 仕様から Prism モックサーバーを起動します。必要に応じて Prism をインストールします。--portおよび--dynamicフラグに対応しています。Windows では.ps1版を使用してください。
ドキュメント全文:https://support.smartbear.com/swagger/contract-testing/docs/en/drift.html
利用可能なページをすべて確認するには、サイトマップを取得してください:https://support.smartbear.com/swagger/contract-testing/docs/sitemap.xml
インストール
# Quickest — no install needed
npx @pactflow/drift --help
# Project-level (recommended for teams)
npm install --save-dev @pactflow/drift
# Global
npm install -g @pactflow/drift
# Verify
drift --version
プロジェクトのセットアップ
drift init # interactive wizard — scaffolds all files below
drift initは対話式です。ユーザーに実行してもらってください。
drift/
├── drift.yaml # Main config — sources, plugins, global settings
├── drift.lua # Lifecycle hooks and helper functions
├── my-api.dataset.yaml # Test data
└── my-api.tests.yaml # Test cases
最小限の drift.yaml:
# yaml-language-server: $schema=https://download.pactflow.io/drift/schemas/drift.testcases.v1.schema.json
drift-testcase-file: v1
title: "My API Tests"
sources:
- name: source-oas # referenced in test targets
path: ./openapi.yaml # or uri: https://... for remote specs
- name: product-data
path: ./product.dataset.yaml
- name: functions
path: ./product.lua
plugins:
- name: oas # spec-first verification
- name: json
- name: data
global:
auth:
apply: true
parameters:
authentication:
scheme: bearer # bearer | basic | api-key
token: ${env:API_TOKEN}
operations:
# test cases here — see references/test-cases.md
テストの実行
# Basic run
drift verify --test-files drift.yaml --server-url https://api.example.com/v1
# Single operation (fast iteration)
drift verify --test-files drift.yaml --server-url https://api.example.com/v1 --operation getProductByID
# Re-run only failures
drift verify --test-files drift.yaml --server-url https://api.example.com/v1 --failed
# Filter by tags
drift verify --test-files drift.yaml --server-url https://api.example.com/v1 --tags smoke
drift verify --test-files drift.yaml --server-url https://api.example.com/v1 --tags '!destructive'
すべてのフラグ、並列実行、JUnit 出力、終了コードについては、references/cli-reference.md を参照してください。
カバレッジ全体のフィードバックループ
エンドポイントの完全なカバレッジが目標の場合:
注意 — 本番環境での破壊的なテスト:
--server-urlが稼働中の 本番 API を指している場合、DELETE および POST テストは取り消せません。必ず専用のテストアカウントを使用し、 DELETE テストで使用するリソースが破棄可能であることを確認してください。
このチェックリストをコピーして、進捗を記録してください:
Coverage Loop Progress:
- [ ] Step 0: Check current coverage (check_coverage.py)
- [ ] Step 1: Parse spec and collect operation list (openapi-parser skill or extract_endpoints.py)
- [ ] Step 2: Assemble initial test file
- [ ] Step 3: Run tests (run_loop.sh / run_loop.ps1)
- [ ] Step 4: Diagnose and fix each failure
- [ ] Step 5: Apply common fixes (hooks for state, data seeding)
- [ ] Step 6: Verify exit code 0 + full coverage
ステップ 0 — 現在のカバレッジを確認する
テストを作成する前、または既存のテストスイートを再開する際に実行してください:
# Run against your spec and test file(s)
uv run path/to/scripts/check_coverage.py \
--spec openapi.yaml \
--test-files drift.yaml
# Multiple files / globs
uv run path/to/scripts/check_coverage.py \
--spec openapi.yaml \
--test-files "tests/*.yaml"
# Machine-readable output (for CI or scripting)
uv run path/to/scripts/check_coverage.py \
--spec openapi.yaml \
--test-files drift.yaml --json
出力には、テストがまったくないオペレーション、特定のレスポンスコードが未対応のオペレーション、オペレーション/コード全体の割合が表示されます。終了コード 0 = カバレッジ完全、1 = 未対応の項目あり。
このスクリプトでは、デフォルトで 500/501/502/503 を除外します(下記のステップ 1 と同じルールです)。除外するコードを変更するには、--exclude-codes を渡してください。
ステップ 1 — 仕様を解析する
extract_endpoints.py を使って、操作の完全な一覧、操作ごとに文書化されたすべてのレスポンスコード、およびすぐに使える operations: YAML スタブを収集します。お使いの環境で openapi-parser スキルが利用できる場合は、代わりにそちらを使用できます。
# See all operations + response codes, flagging params with no spec example
uv run scripts/extract_endpoints.py --spec openapi.yaml
# Generate skeleton stubs for every operation
uv run scripts/extract_endpoints.py --spec openapi.yaml \
--scaffold --source my-oas > operations.yaml
# Generate ONLY the gaps not already in an existing test file
uv run scripts/extract_endpoints.py --spec openapi.yaml \
--scaffold --only-missing drift.yaml --source my-oas >> drift.yaml
GET /products → 200, 401, 404
POST /products → 201, 400, 401
DELETE /products/{id} → 204, 401, 403, 404
重要: 仕様レベルの example がないパラメーターがあると、Value for query parameter X is missing が発生します。それぞれに parameters.query/path/headers で明示的な値を指定してください。
全体で必須のクエリパラメーター(たとえば、すべてのエンドポイントでの ?version=YYYY-MM-DD)は、各テストケースで繰り返す代わりに、http:request フックを使って一度だけ挿入できます。
["http:request"] = function(event, data)
if data.query == nil then data.query = {} end
data.query["version"] = "2024-01-04"
return data -- MUST return modified data
end
重複する operationId 値 — 一部の仕様では、異なる 2 つのパスに同じ operationId が再利用されています。該当する操作には method:path による指定を使用してください。
target: source-oas:post:/orgs/{org_id}/apps/installs/{install_id}/secrets
500 レスポンスはカバレッジ要件の対象外です — 500 はサーバーのバグが必要であり、決定論的に発生させることはできません。
ステップ 2 — 初期テストファイルを作成する
openapi-parser のスタブを drift.yaml に組み込みます。最初から完璧を目指す必要はありません。反復することで不足箇所が明らかになります。各テストはできるだけシンプルな状態から始めてください。
getProduct_Success:
target: source-oas:getProductByID
parameters:
path:
id: 10
expected:
response:
statusCode: 200
すべての操作にタグを追加します。タグを使うと --tags でフィルタリングでき、テストスイートも管理しやすくなります。
getProduct_Success:
target: source-oas:getProductByID
tags: [smoke, read-only, products]
...
getProduct_Unauthorized:
tags: [security, auth]
...
deleteProduct_Success:
tags: [destructive, products]
...
一般的なタグ:smoke、read-only、write、destructive、security、auth、regression。タグの詳細については、references/test-cases.md のタグのセクションを参照してください。
エラーパスについては、401、403、404、400 のパターンを references/test-cases.md で確認してください。
モックサーバーのセットアップについては、references/mock-server.md を参照してください。
ステップ 3 — 実行して失敗を記録する
run_loop.sh スクリプトは、ステップ 6 までのこの全工程を自動化します。
# Runs drift --failed in a loop, then checks coverage. Exits 0 only when both pass.
path/to/scripts/run_loop.sh \
--spec openapi.yaml \
--test-files drift.yaml \
--server-url https://api.example.com/v1
または、drift を手動で実行して反復します。
drift verify --test-files drift.yaml --server-url https://api.example.com/v1
# Re-run only failures to keep the loop fast
drift verify --test-files drift.yaml --server-url https://api.example.com/v1 --failed
モックサーバーを使ってローカルでテストする場合は、先に Prism を起動してください。
path/to/scripts/start_mock.sh --spec openapi.yaml --port 4010
# then in another terminal:
path/to/scripts/run_loop.sh --spec openapi.yaml --test-files drift.yaml --server-url http://localhost:4010
ステップ 4 — 各失敗の原因を特定して修正する
| 症状 | 考えられる原因 | 修正 |
|---|---|---|
| 404 が返り、200 が期待されていた | テストデータが存在しない | リソースをシードするために operation:started フックを追加します |
| 200 が返り、404 が期待されていた | ID が偶然存在している | ${notIn(...)} または nil UUID 00000000-0000-0000-0000-000000000000 を使います |
| 401 が返り、200 が期待されていた | 認証が設定されていない | global.auth を追加するか、トークンの環境変数を確認します |
| 200 が返り、401 が期待されていた | 認証情報が除去されていない | exclude: [auth] と不正なトークンを追加します |
| 403 が返り、200 が期待されていた | トークンに必要なスコープがない | 十分な権限を持つトークンを使います |
| 200 が返り、403 が期待されていた | 有効な認証情報とアクセス禁止のリソースが必要 | トークンでアクセスできないリソースを指定します。references/auth.md を参照してください |
| レスポンスのスキーマ検証エラー | API が仕様から逸脱している、または仕様の例が無効 | 仕様の例が有効か確認します。Drift が仕様のバグを正しく報告している可能性があります |
Value for query parameter X is missing | 任意のパラメーターに仕様の example がない | 仕様に例がないパラメーターにはすべて明示的な値を指定します |
| 200 のテストで 400 が返る | グローバルに必須のクエリパラメーターが不足している | http:request フック経由で挿入するか、各テストケースに追加します |
| 500 が返る | テストデータがサーバーのバグを引き起こした | データを修正します |
ignore: { schema: true } が抑制するのはリクエストのスキーマ検証のみです。4xx のシナリオではこれを使用してください。特にモックサーバーに対するテストでは、Prism は認証を適用せず、不正確なエラーボディを返すことがあります。レスポンスのスキーマ検証を回避する方法はありません。仕様の例にあるバグは失敗として表面化します(references/mock-server.md を参照してください)。
複数の 2xx コード: ドキュメントに記載されたコードごとにテストを 1 つ作成してください。statusCode: [200, 204] の配列構文はサポートされていません。
動的 ID とフックのタイミング: データセットの式は operation:started より前に解決されます。事前にシードした静的 ID を使用するか、http:request 経由で URL を書き換えてください。
ステップ 5 — よくある修正
テストの前にデータが存在している必要があります(DELETE、PUT、PATCH):
["operation:started"] = function(event, data)
if data.operation == "deleteProduct_Success" then
http({ url = server_url .. "/products", method = "POST",
body = { id = 10, name = "test", price = 9.99 } })
end
end,
["operation:finished"] = function(event, data)
http({ url = server_url .. "/products/10", method = "DELETE" })
end,
Lua API 全体と data オブジェクトの構造については、references/lua-api.md を参照してください。
ステップ 6 — 終了コード 0 と完全なカバレッジを確認する
drift verify --test-files drift.yaml --server-url https://api.example.com/v1
echo "Exit code: $?"
完了と判断する前に、カバレッジが完全であることを確認してください。
uv run path/to/scripts/check_coverage.py \
--spec openapi.yaml --test-files drift.yaml
echo "Coverage exit: $?"
両方のコマンドが終了コード 0 で終了したら完了です。
AsyncAPI テスト
AsyncAPI テストでは、AsyncAPI 3.x のドキュメントに照らして、イベント駆動型サービス(Kafka、SNS、SQS)を検証します。実際に稼働しているブローカーが必要です。これに相当するモックサーバーはありません。
サポート対象バージョン: AsyncAPI 3.x のみです。Drift は AsyncAPI 2.x をサポートしていません。
プラグインスタック(すべての AsyncAPI テストファイルで必須)
# yaml-language-server: $schema=https://download.pactflow.io/drift/schemas/drift.testcases.v1.schema.json
drift-testcase-file: v1
title: "<service> AsyncAPI verification"
sources:
- name: <source-name> # referenced in target fields
path: ./asyncapi/<service>.asyncapi.yaml
plugins:
- name: asyncapi # reads the AsyncAPI document
- name: kafka # transport; use aws-messaging for SNS/SQS
- name: json # payload validation
operations:
# test cases go here
AsyncAPI の完全なカバレッジ・フィードバックループ
AsyncAPI Coverage Loop Progress:
- [ ] Step 0: Check current coverage (check_coverage.py auto-detects AsyncAPI)
- [ ] Step 1: Parse spec and collect operation list (asyncapi-parser skill or extract_channels.py)
- [ ] Step 2: Assemble test file with correct plugins and sources
- [ ] Step 3: Start broker (Kafka/SNS/SQS) and run drift verify
- [ ] Step 4: Diagnose and fix failures
- [ ] Step 5: Verify full coverage
ステップ 0 — 現在の AsyncAPI カバレッジを確認する
check_coverage.py は AsyncAPI の仕様を自動検出し、操作/メッセージレベルのカバレッジ(ステータスコードなし)に切り替えます。
修飾子のない <source>:<operationId> ターゲットは互換性のために操作全体をカバーします。複数のメッセージを正確にカバーするには
<source>:<operationId>:<messageId> を使用してください。
uv run path/to/scripts/check_coverage.py \
--spec service.asyncapi.yaml \
--test-files "tests/*.yaml"
# Exit 0 = all operation/message targets have at least one test; 1 = gaps remain
ステップ 1 — 仕様を解析する
extract_channels.py を使ってすべての操作を確認し、ひな形のスタブを生成します。複雑なメッセージスキーマ(ペイロード内の oneOf/anyOf/allOf/discriminator)については、代わりに asyncapi-parser スキルを呼び出します。
# Summary: see all operations + execution modes
uv run scripts/extract_channels.py --spec service.asyncapi.yaml
# Scaffold stubs for all operations
uv run scripts/extract_channels.py --spec service.asyncapi.yaml \
--scaffold --source async-svc > drift/tests.yaml
# Scaffold only gaps in an existing test file
uv run scripts/extract_channels.py --spec service.asyncapi.yaml \
--scaffold --only-missing drift/tests.yaml --fragments >> drift/tests.yaml
--fragments を指定すると、ルートの operations: キーが省略されるため、追記しても YAML マッピングが重複しません。
指定しない場合、生成されるひな形は完全な独立した operations: ブロックとなり、新しいファイルへのリダイレクトを想定しています。
ステップ 2 — テストファイルを組み立てる
ひな形のスタブに、正しい plugins/sources ヘッダーを設定します。各操作には次の項目が必要です。
parametersのcorrelation-id— Drift はこれを使って該当するメッセージを照合しますtimeout-ms— メッセージを待つ時間(デフォルト:5000ms)async-observeにはtriggerフック、async-inject、async-inject-capture、async-request-replyにはpayload- 受信操作には
probeフック(async-inject)またはprobe-topic(async-inject-capture)
trigger フックについて:trigger コマンドは、${parameters.correlation-id} を公開するメッセージのヘッダーに必ず引き継ぐ必要があります。correlation ID が一致しないと、キャプチャがタイムアウトします。
ステップ 3 — テストを実行する
AsyncAPI テストでは --server-url は不要です。Drift は AsyncAPI の servers ブロックで宣言されたブローカーに接続します。
# Run all tests
drift verify --test-files drift/tests.yaml
# Re-run only failures
drift verify --test-files drift/tests.yaml --failed
# Single operation (fast iteration)
drift verify --test-files drift/tests.yaml --operation SendOrderCreated_Observe
注記:
run_loop.shは OpenAPI 用に設計されています(--server-urlが必要です)。AsyncAPI では、すべてのテストが通るまでdrift verify --failedを使って手動で繰り返し実行し、その後check_coverage.pyを実行してカバレッジを確認します。
ステップ 4 — AsyncAPI の失敗を診断する
| 症状 | 考えられる原因 | 対処法 |
|---|---|---|
| キャプチャがタイムアウトし、trigger は成功した | trigger が別の correlation ID を公開した | trigger が ${parameters.correlation-id} をヘッダー値として渡していることを確認します |
| キャプチャがタイムアウトし、trigger が実行されなかった | trigger スクリプトへのパスが間違っている | テストケースの YAML ファイルの場所を基準にしたパスを使います |
| キャプチャがタイムアウトし、trigger が関与していない | channel address が間違っているか、ブローカーが起動していない | AsyncAPI 仕様の address: が実際のトピック名と一致することを確認します |
| probe が誤検知する(通るべきでないテストが通る) | 前のテストの状態がクリアされていない | probe の開始時に状態をクリアするロジックを追加します |
| probe が断続的に失敗する | サービスの処理完了前に probe が実行される | probe スクリプトに再試行ループまたは遅延を追加します |
| 公開時のスキーマ検証に失敗する | ペイロードに必須フィールドがない | components.messages.<name>.payload.required を確認します |
request-reply が想定されるのに async-observe モードになっている | AsyncAPI 操作に reply ブロックがない | parameters に reply-channel: <topic> を追加するか、AsyncAPI 仕様を修正します |
| SNS/SQS:request-reply が動作しない | SNS/SQS は request-reply をサポートしていない | request-reply のやり取りには Kafka を使います |
開発用ローカル Kafka
Docker でローカルブローカーを起動します。
# Start Kafka (KRaft mode, no Zookeeper)
docker run -d --name drift-kafka -p 9092:9092 \
-e KAFKA_ENABLE_KRAFT=yes \
-e KAFKA_NODE_ID=1 \
-e KAFKA_PROCESS_ROLES=broker,controller \
-e KAFKA_CONTROLLER_LISTENER_NAMES=CONTROLLER \
-e KAFKA_LISTENERS="PLAINTEXT://:9092,CONTROLLER://:9093" \
-e KAFKA_ADVERTISED_LISTENERS="PLAINTEXT://localhost:9092" \
-e KAFKA_LISTENER_SECURITY_PROTOCOL_MAP="PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT" \
-e KAFKA_CONTROLLER_QUORUM_VOTERS="1@localhost:9093" \
-e KAFKA_AUTO_CREATE_TOPICS_ENABLE=true \
-e KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1 \
confluentinc/cp-kafka:latest
# Stop when done
docker stop drift-kafka && docker rm drift-kafka
AsyncAPI の servers ブロックでブローカーへの接続を宣言します。プラグイン設定で上書きしない限り、Drift はそこに記載された接続を使います。
クイックリファレンス
| シナリオ | 方法 |
|---|---|
| ステートレスな読み取り専用エンドポイント | 宣言型テスト、フックなし |
| 安定したテストデータ | データセット式 |
| テスト前にデータを作成する | operation:started フック |
| テスト後にクリーンアップする | operation:finished フック |
| 動的な値(UUID、タイムスタンプ) | Lua の exported_functions |
| 404 を確実に返す | ${notIn(...)} または nil UUID |
| モックサーバーでエラーコードを強制する | Prefer: code=X ヘッダー |
| ライブバックエンドなしでテストする | Prism モック — references/mock-server.md を参照 |
| 標準外の認証プレフィックス | http:request フック — references/auth.md を参照 |
| 失敗したテストのみを再実行する | --failed フラグ |
| PactFlow に公開する | --generate-result フラグ |
| AsyncAPI の送信操作(監視) | async-observe モード — trigger フックを追加 |
| AsyncAPI の受信操作(注入) | async-inject モード — probe フックを追加するか、probe-topic を使用 |
| AsyncAPI のリクエスト/リプライ | async-request-reply モード — trigger は不要 |
| 複雑な AsyncAPI メッセージスキーマを解析する | asyncapi-parser スキル |
| AsyncAPI テストスタブを生成する | extract_channels.py --scaffold |
| AsyncAPI のカバレッジを確認する | check_coverage.py(仕様の種類を自動検出) |