testing-craft について
testing-craftは開発者向けのテストスキルです。各振る舞いを適切な証明・テスト段階に割り当て、それらで証明できない点にテストを絞ります。
主な機能
- 01不変条件を証明・テスト段階に割り当て
- 02振る舞いと故障要因に応じたテスト選択
- 03型付きダブルと実際の依存先の使用
- 04簡潔で決定的なテストスイートの維持
- 05回帰検出力の確認と検証結果の報告
活用シーン
- 01コード変更の検証計画
- 02単体・統合・エンドツーエンドテストの選択
- 03テストが実際の欠陥を検出するかの確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ jssblck/ agents --skill testing-craftインストール後、Claude Code で/testing-craftを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
agents/openai.yamllanguages/go.mdpython.mdrust.mdtypescript.mdreferences/episodes.md詳細
テスト設計
テストが担うのは、より強い段階では証明できないことだけです。各不変条件は、コード設計の 証明の階層のうち、実際に証明できる最も強い段階が 一つだけ担います。順に、型とリンター、エフェクト、証明、データベース制約、単体テスト、 統合テスト、エンドツーエンドテストです。可能であれば、問題のあるケースがコンパイルまたは 構築に失敗するようにし、残りはその証明に必要な最も強いテスト段階で、証明を維持できる 最小限のテストで検証します。
範囲と検証
- 変更する動作と、新規または変更する各テストが検出すべき具体的な不具合を特定します。テストで無関係な不具合まで検出する必要はありません。
- 新しいコードをテストする前に、失敗モードを列挙し、それぞれを担える最も強い段階に割り当てます。
- ここに示す既定の方針よりも、プロジェクトの指示と既存のテスト規約を優先します。
- 影響を受けるテストと必須のチェックを実行します。変更の依存関係、失敗、または未解決のリスクに応じて、検証範囲を広げます。
- テストの好みに合わせるためだけに、本番アーキテクチャを変更しないでください。依頼された修正に必要な場合は再構成し、独立したリファクタリングは別途提案します。
- 変更した動作を既存の自動化で検証できる場合は、それを使用します。視覚的なレイアウトなど、自動化では検出できないリスクを見つけられる場合は、手動での確認も有用です。
- ランナーやフィクスチャに関するガイダンスが必要な場合に限り、該当する言語のファイルだけを読みます。設計ガイダンスは
code-craftとdatabase-craftにあります。
残りをテストする
型では、計算、パースの境界ケース、SQL、順序、タイミング、リモートでの動作、レンダリング、プロセス間の配線は証明できません。それぞれを証明できる最も強いテスト段階で検証します。
| 特性 | 段階 | 方法 |
|---|---|---|
| 純粋な計算:フォーマット、ランキング、差分、日付計算、パーサー | 単体テスト | 実際の値で直接呼び出します |
| サービスロジック | 単体テスト | 宣言済みの依存関係に、型付けされたテストダブルを使用します |
| レンダリングされたコントロール | 単体テスト | 型付けされたプロパティと注入されたサービスを使って、実際のコンポーネントをテストします |
| クエリ、リポジトリ、制約、ジョブ、マイグレーション | 統合テスト | 実際の Postgres を使用します(database-craft) |
| プロバイダー SDK とトランスポート | 統合テスト | Vercel Emulate に対して実際のクライアントを使用します |
| ネイティブモジュール、ファイル、プロセス | 統合テスト | 実際のコード、フィクスチャ用 SQLite、一時ディレクトリ、使い捨ての Git リポジトリを使用します |
| ナビゲーション、認証、プロセスとプラットフォーム間の配線 | エンドツーエンドテスト | リリースを妨げる、小規模で決定的なテスト一式 |
単体テストは統合テストより上位であり、統合テストはエンドツーエンドテストより上位です。段階が下がるごとに実行は遅く、ノイズが増え、1 回の実行で扱える入力が減ります。上位の段階では確認できないことに限り、下位の段階を選びます。
テストスイートを最小限に保つ
- 型、リンタールール、エフェクトシグネチャ、証明、または制約ですでに証明できることはテストしないでください。
- 各特性は一つの段階で証明します。ルートのテストでリポジトリの SQL ケースを繰り返さず、エンドツーエンドのフローでフォーマッターの境界ケースを繰り返しません。コントラクトスイートは、意図的に認める唯一の例外です。
- より強い段階がテストケースを引き継いだら、そのテストを削除し、新たに担う段階を明記します。削除前に代替の検証を実行します。
- カバレッジを移動または削除する際は、引き続きテストが必要な所有権、並行処理、キャンセル、無効な入力、失敗のケースを維持します。
- コンパイル失敗チェックで、証明と所有権の境界を保護します。型エラーを確認する行を設け、証明型を緩めるとビルドが失敗するようにします。通常の型には追加しないでください。
- テストで実装をそのまま再現せず、テストだけを目的に非公開ヘルパーを公開しないでください。
ダブル
より強い段階で役割を果たした後であれば、ダブルを使ってかまいません。型は依存関係の形を、エフェクトは依存関係が必要とするものと失敗の仕方を、証明は依存関係の前提条件をそれぞれ固定するため、型付けされたダブルで単体テストにおける依存関係の振る舞いを代替できます。実際の依存関係が引き続き同じように振る舞うことはダブルでは確認できません。その確認にはスモークテストを使います。
- 実際のインターフェースに対して型付けします。 コンパイラーが形の異なるダブルを拒否する必要があります。サービスのインターフェースから作成するか、実際の関数として型付けしてください。型付けされていないダブル(
as any、as unknown as)はキャストです。 - 証明対象ではなく、依存関係を置き換えます。 SQL のテストには Postgres が必要で、プロバイダーのプロトコルのテストにはエミュレーターが必要です。それらを呼び出すロジックにはダブルを使えます。テスト対象の単位をダブルに置き換えてはいけません。
- 既存の差し替え箇所を優先します。 モジュールのモックに手を伸ばす前に、サービスのタグにテスト実装を用意するか、型付けされたパラメーターを渡してください。モジュールのモックはテストをファイル構成に依存させます。
- 宣言された失敗を発生させます。 型付けされた失敗であれば、ダブルを通して各エラーのバリアントを簡単に発生させられます。呼び出し元が処理するものを網羅してください。
- 結果を検証します。 呼び出し自体が契約である場合に限り、呼び出しを検証してください。たとえば、メールを 1 通送信すること、キャッシュヒット時にネットワーク通信をスキップすること、書き込み前に認可を確認することなどです。関連する呼び出しと引数だけを検証してください。
- フェイクが振る舞いを持つ場合は契約テストスイートを用意します。 共有フェイクが状態、ページネーション、または多くのテストが依存するエラーをモデル化する場合は、各実装が保証するケースを一度だけ記述し、フェイクと実際の実装の両方に対して実行してください。実際の振る舞いが変わったら、まず契約を変更してください。
スモークテストは監視役です
すべての統合ポイントで、実際の対象に対するテストを少なくとも 1 つ維持してください。対象には、Postgres、各プロバイダー(Emulate またはプロバイダーのサンドボックス経由)、各プロセス境界、エンドツーエンドテストを通じた各アプリのサーフェスが含まれます。主要な経路に加え、実際の対象でしか壊れない認証、シリアライゼーション、トランスポートを網羅してください。スモークテストは統合ポイントの挙動が変わったときに失敗させるためのものであり、単体テストのケースをそのまま複製するものではありません。
実際の連携先
統合の段階では、分離されたデータベースインスタンス、フィクスチャーの SQLite、一時ディレクトリ、使い捨ての Git リポジトリなど、実際のストレージを使ってください。
プロバイダーやサービスの API には、Vercel Emulate を使ってください。組み込みのプロバイダーを優先し、対応していない API やプロジェクト独自のサービスにはカスタムエミュレーターを使います。プロジェクトのスキルインストーラーを通じて、上流の emulate スキルをリポジトリにインストールし、使用前に内容を確認してください。これをインポートする各ワークスペースで、テスト依存関係を宣言してください。
- ネイティブ HTTP 経由で実際の SDK またはクライアントを使用します。既存の依存関係境界を通じて、エミュレーターの URL を注入してください。ヘッダー、本文、キャンセルを維持してください。
- カスタムエミュレーターのルートでリクエスト本文をデコードする際は、アプリケーションが使うものと同じスキーマを使い、外部から確認可能な状態を保持してください。HTTP サーバーの背後に成功レスポンスを固定しても、統合の証明にはなりません。
- 各フィクスチャーに独自の状態と、OS が割り当てるポート(
port: 0)を用意してください。シナリオごとに状態をリセットまたは再作成し、失敗した場合もクリーンアップの完了を待ってください。並列ワークツリー間で、可変状態、固定ポート、永続化ファイルを共有してはいけません。 - サービス境界を通じて、関連する失敗を検証します。契約に含まれる場合は、認可、ページネーション、再試行、キャンセルを確認してください。
ツールの制約がある場合は、忠実度の低いテストを黙って代用して統合テストやエンドツーエンドテストと呼ぶのではなく、その制約を報告してください。
エンドツーエンドテストでリリースをゲートします
実行中のアプリケーション、公開 CLI、またはパッケージ化されたアプリを操作し、永続化された効果を含めて、ユーザーが確認できる内容を検証する、小規模で決定論的なテストセットを維持してください。決定論的なアサーションだけをリリースのゲートにしてください。モデルによる判断は失敗の説明には使えますが、ゲートにはできません。レンダリングされたコンポーネントのテストだけでは、ナビゲーション、認証、ネイティブプラットフォームとの連携を証明できません。
実装をなぞるテストを避ける
ソーステキストのアサーションや、内部の呼び出しを厳密に記述したスクリプトでは、契約を保護できないまま実装を固定してしまうことがよくあります。問題が疑われるテストについて、次の点を特定します。
- そのテストが保護する振る舞い。
- そのテストを失敗させる具体的な不具合。
- 同じ振る舞いを持つ有効な実装を、そのテストが誤って拒否しないか。
脆弱なアサーションは、結果、状態、レンダリングされた出力、または外部への作用を確認するものに書き換えます。より強い段階で検出できる実際の不具合を検出できないテストは削除します。文書化されたリポジトリのポリシーを適用するチェックは残します。
読みやすいテストを書く
- シナリオと期待する結果に名前を付けます。原因と結果を分かりやすく示します。
- 関連する入力と期待値はテスト本体に記述します。無関係な準備処理はヘルパーに隠してかまいません。並行するシナリオには、テーブル駆動のケースが役立ちます。
- 一連のケースは
check(input, expected)ヘルパーに通し、API の変更時にすべてのテストではなくヘルパーを編集するだけで済むようにします。期待される出力が大きい場合や頻繁に変わる場合は、インラインスナップショット(expect)テストを使用します。 - 本番アルゴリズムを繰り返して期待値を計算しないでください。
- 入力の入れ替わりや意図しないデフォルト値が分かる、互いに異なる値を選びます。テスト対象の振る舞いに該当する場合は、空、ゼロ、境界値も含めます。
- 重要なフィールドをアサートします。オブジェクト全体が契約である場合は、付随的な詳細を広くスナップショットに取るのではなく、オブジェクト全体の等価性を確認します。
- 失敗時に期待値と実際の値が分かるようにします。
- 関連する失敗経路と境界値を網羅します。不変条件や大きな入力空間を検証するのに適している場合はプロパティテストを使用します。ライブラリが使えるという理由だけで使用しないでください。
テストの決定性を保つ
- 任意の時間だけスリープして処理が完了したと想定するのではなく、タイムアウト付きで完了シグナルまたは観測可能な条件を待ちます。
- 結果に影響する場合は、時刻と乱数を制御します。
- テスト間で変更可能な状態を分離します。
- 重要な失敗を、制御可能な依存関係または入力によって再現できるようにします。
回帰に対する検出感度を確立する
バグ修正では、まず不具合が発生する状態を示す最も強い段階で失敗を再現します。たとえば、その不正な状態を拒否する型、または失敗するテストです。意図した理由で失敗することを確認し、修正後に成功することを確かめます。実際の失敗を再現できない場合は、根拠と残る不確実性を明記します。
その他の変更では、アサーションが検出する不具合を特定します。検出感度が不明な場合や、リスクに見合う場合は、対象を絞ったミューテーションを使用します。本番コードを変更してミューテーションを行う場合は、実験を分離し、続行する前に変更を元に戻します。
アサーションを反転して確認できるのは、処理が実行されたことだけであり、意図した不具合に対する検出感度ではありません。通常のテスト編集や、成功から成功へのリファクタリングでは、本番コードのミューテーションは不要です。対象となる振る舞いを維持し、リファクタリング前後で関連する結果を比較します。実行できなかった検証については報告します。
繰り返し可能な証拠を残す
保証内容を明記します。どの無効な入力、状態、または呼び出しが、どこでコンパイルまたは構築できなくなるかを説明します。残す各テストについて、検出する不具合とその段階を明記します。
各エンドツーエンドテストの最後に、検証可能な成果物を残します。正確なコマンドまたは再実行可能なフローと、それによって生成された出力、スクリーンショット、または記録を示します。プロジェクトの成果物ディレクトリと公開ルールに従います。
反復中は対象を絞ったシナリオを実行し、リリース前に必須の全ゲートを実行します。成功したチェックを再実行するのは、関連する編集、失敗、または未解決のリスクがある場合に限ります。実行時間の遅いスイートを変更する前に測定します。実行時間を改善する際も、分離性と有用なカバレッジを維持します。
言語別のリファレンス
テストランナー、フィクスチャ、または非同期テストの詳細が必要な場合は、該当するファイルを読みます。
その他の言語については、プロジェクトの規約に従います。
出典
Google の Testing on the Toilet シリーズをもとに、振る舞いに焦点を当てたテスト、テストダブル、DAMP、SMURF を取り入れています。各エピソードの一覧は、shamashel/testing-on-the-toilet を通じて references/episodes.md にまとめられているほか、matklad の How to Test も参考にしています。