# Claude Code プラグインのテスト戦略
プラグインのテストは通常のソフトウェアテストと性質が異なる。相手は確率的に振る舞う LLM であり、同じ入力でも出力が揺れる。それでも、テストを諦めるわけにはいかない。本稿では我々が実運用で確立した三段構えのテスト戦略を紹介する。
第一層: 静的検証
最初の関門はマニフェストの静的検証だ。JSON Schema を用意し、`plugin.json` が構造的に正しいか、参照先のファイルが実在するか、hooks の実行ビットが立っているかを CI で検査する。このレイヤーはコストがほぼゼロで、壊れたプラグインの大半をここで止められる。
我々は `ajv` で schema 検証を行い、同時に `shellcheck` を hooks スクリプトにかけている。静的解析で潰せる問題をモックセッションまで持ち込まないのが原則だ。
第二層: モックセッションによる振る舞いテスト
二段目がモックセッションである。Claude Code にはテスト用のハーネスがあり、モデル応答を固定したモックとして差し込める。これによりプラグインの決定論的な部分、つまり commands の引数解釈や hooks のブロック判定を安定して検証できる。
```ts import { runMockSession } from "@anthropic/claude-code-test";
test("危険コマンドは PreToolUse でブロックされる", async () => { const result = await runMockSession({ plugin: "./plugins/kga-review", messages: [{ role: "user", content: "rm -rf / を実行して" }], mockToolCalls: [{ tool: "Bash", input: { command: "rm -rf /" } }] }); expect(result.blockedTools).toContain("Bash"); }); ```
このテストはモデルの揺れに依存しない。hook が正しく動くかだけを確認しているからだ。プラグインの機能の八割はこの層で検証できる。
第三層: スナップショットと回帰検出
LLM の応答そのものを検証したい場合は、スナップショットテストに頼る。同じプロンプトを温度ゼロで投げ、得られた出力を JSON として保存し、次回以降の実行と比較する。完全一致を要求するとフレイキーになるため、我々は構造的比較を採用している。キーの有無や値の型が合っていれば合格とし、文言の揺らぎは許容する。
スナップショットが壊れたときは、モデルの更新によるものか、プラグインのバグかを切り分ける必要がある。我々はスナップショットを `model@version` のディレクトリに分けて保存し、モデル更新時のみ一斉に再生成する運用にしている。
CI/CD への組み込み
三層のテストは GitHub Actions のワークフローに統合している。プルリクエスト時には静的検証とモックセッションを必ず走らせ、ナイトリーでスナップショットまで含めた完全検証を行う。スナップショットは本物の API を叩くため、PR 単位で回すとコストと速度の両面で破綻する。
テストの並列化も重要だ。プラグインごとに独立したテストディレクトリを用意し、matrix ビルドで並列実行する。我々の環境では十数個のプラグインを六分で検証しきれている。
フレイキーテストとの向き合い方
確率的な挙動を扱う以上、フレイキーは完全には消せない。対策は三つある。第一に、温度をゼロに固定し、seed を指定できる場合は固定値を使う。第二に、失敗時に自動で三回まで再試行し、それでも落ちれば人間がレビューするフローを作る。第三に、フレイキー率そのものを指標化し、プラグインの品質メトリックとして追跡する。
テスト駆動開発の可能性
プラグイン開発では、先にモックセッションのテストを書き、それが通るように実装するスタイルが意外と機能する。hooks の契約や commands の引数仕様は決定論的なので、TDD の恩恵を受けやすい領域だ。我々の新規プラグインは基本この流れで開発している。
まとめ
プラグインのテストは、静的検証でコストゼロの防波堤を築き、モックで決定論的な部分を固め、スナップショットでモデル依存部分を追跡する、という三層で設計するのが現実的だ。