--- name: test-audit description: "テストの追加・変更・レビュー・監査時に必ず使用する。新規テストの妥当性を確認し、低価値・実装依存・重複したテストや、テストのためだけに存在する本番コードを特定・整理する。" --- # テスト監査 テストの追加・変更時には、新しいテストが本当に必要かを確認する。既存テストの監査では、実装を再検証するだけのテスト、より強いテストと重複するテスト、実装詳細に依存するテスト、テスト専用の本番コードを特定する。 目的はテストの削除数を増やすことではなく、**保守コストに見合う信頼性を確保すること**である。 ## テスト追加時の確認 テストを追加する前に、次の質問にすべて答える。答えられない場合は、テストを追加しない。 1. どの観測可能な振る舞い、不変条件、または独立した契約を保証するテストか。 2. どのような現実的な回帰を検出するか。 3. 既存のテストでは、なぜその回帰を検出できないのか。 * 各契約には、最も強い境界に主要なテストを1つ設ける。 * 別の層にテストを追加する場合は、その層でしか検出できない固有のリスクが必要である。 * 単なる重複であれば、既存のテーブル駆動テストや共有フィクスチャーを拡張する。 4. 本番コードに、テスト専用の export、フラグ、ラッパー、依存性注入用の仕組みなどを追加する必要がないか。 * 必要であれば、テスト専用の境界を作るのではなく、実際の本番境界をテストする。 新しいテストが後述する「避けるべきテスト」に該当する場合は、独立した契約を守る明確な理由がない限り追加しない。 ### 回帰テスト バグ修正の回帰テストは、以下を満たすこと。 * 修正前のコードで、意図した理由によって失敗する。 * 修正後のコードで成功する。 * モックではなく、実際の不具合を検出していることを確認する。 * 同じバグを複数の層で繰り返しテストしない。 * 原則として、バグが発生する本来の所有境界に1つの回帰テストを置く。 修正前のコードで一度も失敗しない回帰テストは、修正内容の証明ではなく、モックやテスト自体の正しさを証明している可能性が高い。 ## 避けるべきテスト 新規テストでは以下を避ける。既存テストの監査では、以下に該当するものを候補として調査する。 * アサーションのないカバレッジ目的だけのテスト * 自分自身との比較や、単なる同一性のコピーを確認するテスト * フィクスチャー、一覧、マニフェスト、export 一覧そのものをコピーして確認するテスト * ソースコード、import、文字列を単純に検索するだけのテスト * 実際の境界ですでに検証されている private な述語や呼び出し形式のテスト * 同一の契約を重複して呼び出すだけのテスト * 共通ヘルパーの振る舞いを、利用側ごとに繰り返すテスト * テスト専用の export、global、wrapper を維持するためだけのテスト * テストからしか呼び出されない不要な本番コードを維持するためのテスト * テスト対象の helper や renderer 自身が生成した値を期待値として使用するテスト * アサーション対象の振る舞いそのものを実装しているモック * 異なる API に対して同じモックを機械的に使い回すテスト * 本来は本番側が生成すべき結果、受付、callback の順序などをフィクスチャー側で用意してしまうテスト * 実際には書き込まれないストアに対して永続化結果を確認するテスト * 宣言されたフラグの存在だけを確認し、そのフラグが約束する実際の処理や応答を確認しないテスト * 別のガードによる拒否など、意図した条件とは無関係な理由で失敗する否定テスト * テスト名やフィクスチャーが、実際に検証している内容以上の振る舞いを約束しているテスト * 振る舞いを変えないリファクタリングだけで壊れるテスト 実装に似ているという理由だけで既存テストを削除してはならない。独立した契約を検証している可能性があるため、削除前にその必要性を確認する。 ## テストの価値基準 テストは、以下のいずれかを保護する場合に価値がある。 * ユーザーから観測可能な振る舞い * 現実的な回帰 * 独立して意味のある契約 特に以下の契約は、明確なテスト対象として維持する価値が高い。 * 公開 API * SDK * プロトコル * 設定 * マイグレーション * ストレージ * セキュリティ * プラットフォーム固有の振る舞い * デフォルト値 * バイト列やシリアライズ形式 * コマンドやファイルパスなどの外部契約 * 生成物や言語間の契約 * パッケージ境界 * リリースに関する契約 * アーキテクチャー上の契約 * 観測可能な呼び出し順序 静的なテスト、実行時間の遅いテスト、実装に似たテストであることだけを理由に削除してはならない。 ソースコードそのものを確認するテストも、それが最も低コストで独立した契約を保証できる場合には有効である。特に、識別子だけを変更するリファクタリングでは壊れず、ユーザー向けのキー、バイト列、パスなど契約そのものの変更で失敗するテストは維持する。 既存テストがベースラインで失敗する場合は、不要なテストとして削除するのではなく、製品側の不具合である可能性を調査する。 ## 監査時の調査 テストを削除・変更する前に、少なくとも以下を確認する。 * 対象テスト全体 * テスト対象となる本番コード * 本番コードのエントリーポイント * 呼び出し元 * 呼び出し先 * 同種の実装 * 関連する他のテスト * CI での実行経路 * 関連する変更履歴 * リポジトリーおよび対象ディレクトリーの開発ルール 依存ライブラリーの振る舞いを前提とするテストでは、必要に応じて依存ライブラリーの実装や型定義も直接確認する。 ## 調査と編集 調査段階では読み取り専用で確認し、編集前に根拠を整理する。 広い範囲を監査する場合は、可能なら以下の観点を分けて調査する。 * コアコード・パッケージ * プラグイン・拡張機能 * UI・アプリケーション * スクリプト・開発ツール * 横断的なテストパターン 大規模な一括削除よりも、削除の根拠が明確な候補を優先する。 編集するときは、関連する変更を1つのまとまりとして扱う。 * 不要なテストを削除する。 * テスト専用の export、global、wrapper を削除する。 * テストからしか使用されない不要な本番コードを削除する。 * 残すべき回帰テストは、本来の所有境界へ移動する。 * 重複する依存関係やパッケージの検証は、汎用的な契約テストに統合する。 * 同じ実装を別の場所で再検証するだけの代替テストを追加しない。 原則として、本番コードは削除または単純化によって減少する方向にする。不確かな候補を削除数を増やすためだけに変更対象へ含めない。 ## 削除候補の記録 テストやテスト用コードを削除する場合、編集前に以下を記録する。 * テスト名とファイル位置 * そのテストが実際に検出できる失敗 * 対象となる本番コードやテスト用コードの非テスト呼び出し元 * より強い境界で残るテスト、または残す必要がない理由 * 関連する変更履歴と、そのテストやコードが存在する理由 * 削除によって不要になる本番コード・テスト支援コード * 削除によるリスク * 実施する検証コマンド これらの情報が不足している場合は、削除候補として確定しない。 ## 検証 テスト実行中のチェックアウトでは、ソースコードやテストを編集しない。 編集後は、リポジトリーのテスト・検証手順に従い、少なくとも以下を実施する。 1. 変更対象に最も近いテストを実行する。 2. 必要に応じて関連する兄弟テストや統合テストを実行する。 3. ソース検索や計画だけを確認していたテストを削除した場合は、実際の契約を実行するスクリプトや dry-run を実行する。 4. 対象ファイルをフォーマットする。 5. `git diff --check` を実行する。 6. リポジトリー固有の変更検証を実行する。 7. `git diff --numstat` などで、本番コード・ツールコードとテストコード・テスト支援コードの変更量を確認する。 8. 最終的な監査後に、利用可能なレビュー・検証手順を実行する。 重い検証を実施する必要がある場合は、リポジトリーで定義されている専用の検証手順に従う。 ## 完了時の報告 作業完了時には、少なくとも以下を報告する。 * 根本原因と削除・変更した低価値テストの種類 * 本番コードをどのように単純化したか * 削除せず残した候補と、その価値 * 実際に実行した対象テスト・全体テスト * 本番コードとテストコードの変更量 * 未実施の検証とその理由 * 今後のフォローアップ事項 コミット、push、PR 作成、マージなどの操作は、明示的に許可された場合のみ行う。