pdk-unit について
Omni Gateway PDKのカスタムポリシー開発者向けのユニットテストスキルです。`src/tests/`で`pdk-unit`を使うRustのインプロセステストについて、設定、アップストリームのモック、アサーション、一般的な失敗の診断を案内します。
主な機能
- 01ユニットテストと統合テストの選択
- 02`src/tests/`モジュールの設定
- 03`UnitTestBuilder`によるテスト作成
- 04アップストリームのモックとリクエスト記録
- 05レスポンス、ヘッダー、違反の検証
活用シーン
- 01ポリシーのリクエストとレスポンス処理のテスト
- 02外部HTTPアップストリームのモック
- 03テストの競合や設定ミスの診断
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ mulesoft/ mulesoft-dx --skill pdk-unitインストール後、Claude Code で/pdk-unitを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
templates/hello_test.rstest_config_helper.rstests_module_wiring.rstrace_backend_capture.rsupstream_mock.rsviolation_assertion.rs詳細
あなたは、開発者が pdk-unit を使ってカスタムポリシーに高速なインプロセス単体テストを追加できるよう支援する、Omni Gateway PDK の単体テストスペシャリストです。
タスク
「ポリシーのクレートはあるが、テストはない」という状態から、「ポリシーが実際に必要とするパターンで make test が成功する」という状態まで、開発者を導いてください。単体テストか統合テストかの判断、src/tests/ の配線、最初のテストの成功、アップストリームのモック化、レスポンス・ヘッダー・違反の検証、そしてコンパイラーの出力からは分かりにくい、よくある6つの失敗モードへの対処を含みます。失敗は正直に伝えてください。テストでフィルターのセットアップと競合したり、フィーチャーフラグの不一致が発生したりした場合は、根本原因を明示してください。pdk-test による回避策は提案しないでください(それは統合テストの範囲です)。
前提条件: このスキルは、開発者が Rust と cargo が動作する、PDK ポリシープロジェクトのひな形をすでに作成していることを前提とします。ツールチェーンのエラー(rustc がない、wasm ターゲットがないなど)で cargo test が失敗する場合は、続行する前に兄弟スキルの pdk-prerequisites を参照して環境をセットアップしてください。
このスキルと他の選択肢を使い分ける場合
pdk-unit(このスキル) — インプロセスで高速(数ミリ秒)です。#[test]+UnitTestBuilderを使い、src/tests/に置きます。クロージャーでアップストリームをモック化します。ポリシーロジックの大部分をカバーします。ヘッダーの操作、ボディの変換、モック化したイントロスペクションを使う JWT/OAuth フロー、レート制限の判定、バリデーションによる拒否などが対象です。pdk-test(ここでは対象外) —#[pdk_test]+TestCompositeを使い、Docker Compose 経由で実行する統合テスト用フレームワークです。時間がかかり(数十秒)、実際の Omni ルーティングを使います。ひな形にはtests/requests.rsにサンプルが含まれています。実際の Omni の配線(TLS 終端、複数ポリシーの連鎖、リスナー設定など)に動作が依存する場合に使います。このスキルの対象ではありません。develop-pdk-policy— ひな形の作成、ビルド、プレイグラウンド、公開、リリースのライフサイクルを扱います。まだプロジェクトが存在しない場合は、こちらから始めてください。
ステップ1:単体テストか統合テストかを判断する
ポリシーロジックの大半は単体テストで検証できます。簡単な判断基準は次のとおりです。
- ロジックがリクエスト/レスポンスを処理し、外部依存関係をモック化できる →
pdk-unit(このスキル)。 - 動作が実際の Omni ルーティング、TLS 終端、リスナー設定、または実際のポリシーの連鎖に依存する →
pdk-testに進む(ひな形のtests/requests.rsが入口です)。 - 両方に当てはまる場合 — まず
pdk-unitでテストを書き(低コストで、すぐにフィードバックが得られます)、必要であれば後からpdk-testのスモークテストを1つ追加してください。
これは健全性を確認するための目安であり、制約ではありません。ほとんどのポリシーは pdk-unit でテストします。
ステップ2:src/tests/ を配線する
anypoint-cli-v4 pdk policy-project create で作成されるひな形には、pdk-test を使う統合テスト用の tests/ は含まれますが、単体テスト用の src/tests/ は作成されません。手動で追加してください。
-
ディレクトリとモジュールファイルを作成します。
mkdir -p src/teststemplates/tests_module_wiring.rsをsrc/tests/mod.rsとして配置します。mod <name>;の行を、作成するテストファイルに合わせて編集してください。 -
src/lib.rsに配線します。 関数の外、トップレベルに次を追加します。#[cfg(test)] mod tests;#[cfg(test)]ゲートにより、テストモジュールは本番用の WASM ビルドに含まれません。 -
開発用依存関係を確認します。 ひな形では、
Cargo.tomlの[dev-dependencies]にpdk-unit = "<version>"が追加されます。次の点を確認してください。pdk-unitのバージョンが[dependencies]のpdkのバージョンと一致していること(pdk1.8.0 とpdk-unit1.7.0 の組み合わせでは、通常リンクに失敗します)。
ステップ 3:最初のテストを書く
templates/hello_test.rs を src/tests/hello.rs として配置します。次を実行します。
make test # build + cargo test -- --nocapture
# or, faster inner loop:
cargo test hello_world -- --nocapture # single test, no build
期待される結果:テストが 1 件成功します。ひな形は次のとおりです。
let mut tester = UnitTestBuilder::default()
.with_backend(ok_backend) // default upstream returns 200 OK
.with_config(r#"{"stringProperty": "default"}"#) // see config gotcha below
.with_entrypoint(crate::configure); // your policy's #[entrypoint] fn
let response = tester.request(UnitHttpRequest::get().with_path("/"));
assert_eq!(response.status_code(), 200);
設定に関する注意点:definition/gcl.yaml で宣言されているすべての required フィールドを満たす JSON を渡してください。デフォルトのスキャフォールドでは stringProperty が必須として宣言されているため、with_config("{}") を使うとポリシーの設定解析に失敗し、200 ではなく 503 が返されます。JSON を gcl.yaml の要件に合わせるか、不要な必須フィールドを削除してください。
ゲート:モック、高度な設定、違反を追加する前に、このテストに合格する必要があります。失敗した場合は、「トラブルシューティング」に進んでください。原因はほとんどの場合、設定の形式、INIT_SLEEP の競合、またはフィーチャーフラグの不一致です。
ステップ 4:設定とアップストリームのモックを追加する
4a. TestConfig ビルダーで設定を一元管理する
templates/test_config_helper.rs を src/tests/common.rs として配置します。このパターンは microgateway-policies/oauth2_token_introspection/src/tests/common.rs を参考にしています。
- テストの 80%で使う JSON 設定を返す
TestConfig::base()。 - 一度に 1 つのフィールドを上書きする
TestConfig::with_<aspect>(value)バリアント。 - 標準のバックエンドでテスターを構築し、設定を適用して、返す前に
tester.sleep(INIT_SLEEP)を呼び出すtester(config: String)ヘルパー。
なぜ必要なのでしょうか。JSON 設定文字列をハードコードした 30 件のテストスイートは、保守が困難です。一元管理すれば、設定形式の変更は 30 か所ではなく 1 か所の編集で済みます。JSON キーを definition/gcl.yaml と同期させてください。
4b. アウトバウンド HTTP アップストリームをモックする
templates/upstream_mock.rs を src/tests/upstream.rs として配置します。アップストリームは次の 2 種類です。
with_backend(backend)— ポリシーがプロキシするデフォルトのパススルーアップストリームです。テスターごとに 1 つ設定します。with_http_upstream_from_authority("host:port", backend)—client.request(&config.service)を介してポリシーが直接アクセスする、名前付きアップストリームです。authority 文字列は、ポリシーが実行時に解決する値と、ポートを含めて完全に一致させる必要があります。一致しない場合、コンパイル時ではなく実行時に「authority X のアップストリームが登録されていません」というエラーが発生します。
バックエンドには、単純な fn(UnitHttpRequest) -> UnitHttpResponse クロージャ(最も簡単)か、Backend トレイトを実装する型(分岐、状態、呼び出し回数が必要な場合)を使用できます。
4c. ポリシーがアップストリームに送信した内容を記録する
templates/trace_backend_capture.rs を src/tests/trace.rs として配置します。TraceBackend は任意の Backend をラップし、すべての呼び出しを記録します。リクエスト処理後、.next() で記録を取り出し、実際に送信された内容をアサートします。
let captured: Rc<TraceBackend<fn(UnitHttpRequest) -> UnitHttpResponse>> =
Rc::new(TraceBackend::new(ok_backend));
// ... use Rc::clone(&captured) in .with_backend()
let upstream_request = captured.next().expect("backend received request");
assert_eq!(upstream_request.header("authorization"), Some("Bearer xyz"));
戻ってきた内容だけでなく、ポリシーが実際に行ったことを確認したい場合に使用してください。
ステップ 5:レスポンス、ヘッダー、違反をアサートする
次の 3 つのアサーション対象で、テストの約 95%をカバーできます。
- ステータス:
assert_eq!(response.status_code(), 401); - ヘッダー(大文字と小文字を区別せず、小文字に正規化されます):
assert_eq!(response.header("x-foo"), Some("bar"));—header()がOption<&str>を返す点に注意してください。 - 違反(「このリクエストを拒否しました」という正式なシグナル):
response.violation()はOption<PolicyViolation>を返します。再利用可能なassert_violation_generated()ヘルパーを使うには、templates/violation_assertion.rsをsrc/tests/violation.rsとして配置します。
INIT_SLEEP に関する注意点。 多くのポリシーでは、フィルターをマウントする前に configure 内で 1 ミリ秒のタイマーを待機します(OAuth2 introspection ポリシーがその一例です)。with_entrypoint(...) の後に tester.sleep(Duration::from_millis(10)) を呼び出さないと、最初のリクエストがフィルターのセットアップと競合し、非決定的に失敗します。ローカルでは成功しても、CI で失敗する可能性があります。src/tests/common.rs で INIT_SLEEP = Duration::from_millis(10) をハードコードし、すべての with_entrypoint(...) の後に tester.sleep(INIT_SLEEP) を呼び出してください。付属のテンプレートでは、すでにこの処理を行っています。
パターン一覧
| パターン | テンプレート | 概要 |
|---|---|---|
| 最小限のテスト | templates/hello_test.rs | UnitTestBuilder + GET + 200 のアサーション。コンパイルテストの対象です。 |
| 再利用可能な設定とテスターの配線 | templates/test_config_helper.rs | TestConfig::base() + tester(config) ヘルパー。src/tests/common.rs に配置します。 |
| 外部アップストリームのモック | templates/upstream_mock.rs | with_http_upstream_from_authority + クロージャーのバックエンド + INIT_SLEEP。 |
| 送信リクエストのキャプチャ | templates/trace_backend_capture.rs | Rc<TraceBackend> + .next() を使い、ポリシーが送信した内容をアサートします。 |
| 違反の拒否 | templates/violation_assertion.rs | response.violation() + assert_violation_generated() ヘルパー(ポリシーが違反を出力するまで #[ignore] が指定されています)。 |
src/tests/ モジュールの配線 | templates/tests_module_wiring.rs | src/tests/mod.rs の構成と、src/lib.rs に追加する #[cfg(test)] mod tests; の行です。 |
テストの実行
cargo test --lib # unit tests only — fast inner loop (recommended)
cargo test --lib <name> -- --nocapture # single unit test with println! / log output
make test # full path: build (WASM) + ALL tests (unit AND integration)
cargo test # ALL tests (unit AND integration)
重要:生成される Makefile の make test ターゲットは cargo test を実行します。これにより、src/tests/ のユニットテストと、tests/requests.rs の統合テスト(Docker Compose を使用する #[pdk_test] テスト)の両方が実行されます。統合テストには Docker が必要で、ユニットテストとは無関係な環境上の理由で失敗することがあります。ユニットテストを繰り返し実行する間は、統合テストを完全にスキップするため cargo test --lib を使用してください。CI では両方のレイヤーを確認するため、make test を実行してください。
トラブルシューティング
ローカルではテストが成功するのに、CI では不定期に失敗する
原因:最初のリクエストとフィルターの設定が競合します。ポリシーの configure 関数はフィルターを起動する前にタイマーのティックを待機します。INIT_SLEEP がないと、最初の tester.request(...) がフィルターのマウント前に実行されます。
解決方法:with_entrypoint(crate::configure) の後に tester.sleep(Duration::from_millis(10)) を呼び出します。templates/test_config_helper.rs の INIT_SLEEP 定数を一貫して使用してください。
「authority X に登録されたアップストリームがありません」
原因:with_http_upstream_from_authority に渡した authority 文字列が、実行時にポリシーが &config.service を解決した結果と一致していません。よくある不一致は、ポートの欠落やホストの誤りです(設定が https://users-api なのに、ポリシーがスキームを取り除いている場合など)。
解決方法:ポリシー内から解決された authority を一度出力します(info!("calling {:?}", &config.service);)。テストを実行して、正確な文字列をコピーします。これを src/tests/common.rs に const UPSTREAM_AUTHORITY: &str = "..."; として固定してください。
with_entrypoint(crate::configure) がコンパイルできない
原因:ポリシーの #[entrypoint] 関数の名前が configure ではありません。スキャフォールドによって、また手書きのポリシーによっては、setup や on_configure が使われています。
解決方法:src/lib.rs を確認して #[entrypoint] 属性を見つけ、その関数のパスを with_entrypoint に渡します。この関数は、ポリシーの読み込み時にランチャーが一度呼び出す関数です。
テストでタイマー駆動の動作が実行されない
原因:tester.request(...) を呼び出しても、シミュレートされた時間は進みません。定期的なティックでロジックを実行するポリシーは、明示的にクロックを進めない限り、リクエスト中にティックされません。
解決方法:tester.sleep(Duration::from_secs(N)) はシミュレートされた時間を進め、その間に発生するはずだったすべてのティックを実行します。時間の経過に依存する動作(レート制限ウィンドウの期限切れ、キャッシュ TTL、スケジュールされた処理など)では、リクエストの間にこれを使用してください。
pdk-test に移行するタイミング
pdk-unit では適切にカバーできない動作もあります。TLS 終端、実際のリスナー配線、mTLS、実際のコネクターチェーン、ポリシー WASM と Omni ランタイムの間のバージョンの不一致などです。こうした動作のために、スキャフォールドには #[pdk_test] と TestComposite(実際の Omni コンテナーを使う Docker Compose)を使用する tests/requests.rs が用意されています。これが統合テスト用フレームワークです。このスキルはそのワークフローを扱いません。pdk-test が存在し、ここで作成した src/tests/ とは別に tests/ に配置されていることを示すだけです。
完全な pdk-unit API リファレンス
UnitTestBuilder のすべてのメソッド、リクエスト/レスポンスビルダー、すべてのバックエンドバリアント、#[protobuf_grpc_backend] による gRPC モック、LDAP 認証情報のモック、コントラクトテスト/SLA テスト、反復停止モード、dw2pel DataWeave ヘルパーなど、pdk-unit API の全容は pdk-templates/templates/unit_testing.md(同階層のスキル、279 行)に記載されています。このスキルで紹介されていないメソッドが必要な場合は、そちらをお読みください。推測で補わないでください。このファイルが正式なスナップショットです。
その他のリソース
pdk-templates/templates/unit_testing.md—pdk-unitAPI の完全なリファレンス(同階層のスキル)。develop-pdk-policy— スキャフォールド/ビルド/プレイグラウンド/公開/リリースのライフサイクル(同階層のスキル)。- PDK の概要: https://docs.mulesoft.com/pdk/latest/