定義
変更の影響に基づくテスト選択(affected test selection) は、今回の変更の影響を受けるテストだけを選んで実行する方式です。ファイル同士が import で互いを取り込む関係を描いた import グラフ で、変更されたファイルを出発点に逆向きにたどり、そのファイルに行き着くテストだけを実行します。
なぜ必要か
1 行直しただけで数百ファイル分のテストをすべて実行すると、プッシュ直前のフックや PR の CI のたびに同じ作業が繰り返されます。作業が複数並列で動くと(複数の作業フォルダ、複数のエージェント)、同じ全体テストが同時に CPU を分け合うため、待ち時間が長くなります。
選択実行はこのコストを減らしますが、リスクもあります。import グラフが不完全だと、実行すべきテストを取りこぼして壊れたコードが通ってしまいます。そのため、「絞り込むルール」と「不確実なら全体に戻すルール」をセットで設計する必要があります。
動作原理
変更ファイルを収集(基準点との比較)
└→ 削除/名前変更、またはグローバル設定の変更? ── はい ──→ 全テストを実行
│ いいえ
└→ import グラフを逆方向にたどってテストを選択 → 選ばれたテストだけを実行
| 要素 | 説明 | 注意点 |
|---|---|---|
| 基準点(base) | 「何と比べて変わったのか」 | 推測すると少なすぎたり多すぎたりします |
| 静的グラフ | import 文に基づく | 動的 import() や、文字列で組み立てたパスは見逃すことがあります |
| 共有モジュール | 多くのファイルが取り込む | 関連テストが事実上、全体に近づくことがあります |
| 強制再実行 | 特定のファイルが変わったら無条件で全体を実行 | lockfile、tsconfig、テストの setup、翻訳 JSON、共通アセット |
| 最終ゲート | マージ・デプロイ候補の全体実行 + E2E | 選択実行の取りこぼしを最後にカバーします |
基準点は状況から受け取ります。 PR の CI はプラットフォームが教えてくれる PR の base コミットを使い、プッシュフックは Git がフックに渡してくれる「そのブランチの以前のリモート SHA」を使います。新しいブランチは、どのブランチから分岐したのかを Git が記録していないので、TEST_BASE のような入力で人が明示するようにします。
実務での適用
ツールが提供する機能を使い、自分でグラフを作りません。
# Vitest: 基準コミットからの変更に関連するテストだけ
vitest run --changed "$BASE" --maxWorkers=2 --passWithNoTests
# Jest 系
jest --changedSince="$BASE"
// vitest.config.ts — これらのファイルが変わったら、選択せずにすべて再実行する
export default defineConfig({
test: {
forceRerunTriggers: [
"**/package.json", "**/pnpm-lock.yaml", "**/tsconfig*.json",
"**/vitest.setup.*", "messages/**", "public/**",
],
},
});
- 削除・名前変更は全体実行に回します。 なくなったファイルはグラフの出発点になれないので、そのファイルに依存していたテストを見つけられません。
- 汚れた作業ツリーや、現在の HEAD ではない ref は止めます(fail closed)。基準と実際の内容がずれたまま通ってしまうことを避けます。
- フック自体を回帰テストで固定します: プッシュの範囲、初回プッシュの基準、汚れた作業ツリー、存在しない base、削除ファイルのフォールバック。
- worker 数は測って決めます。 1 つのプロセスの worker を増やすことと、プロセスを複数同時に立ち上げることは別物です。同時実行の状況で実際に測ってから決め、他の環境に一般化しません。
トレードオフ
| 得られるもの | 失うもの |
|---|---|
| プッシュ・PR の検証時間と CPU の削減 | 静的グラフが見えない経路の取りこぼしリスク |
| 並列作業間の重複負荷の削減 | フックや CI に選択ロジックとフォールバックルールが増えます |
| 小さな変更への素早いフィードバック | 最終的な全体ゲートという別の段階が必要です |
使うべきでない場合
- テストがすべて数秒で終わる小さなプロジェクトでは、選択ロジックは複雑さを増やすだけです。
- 最終的なマージ・デプロイ候補には、選択実行だけを使ってはいけません。全体実行と E2E を別に実行します。
- 動的 import やコード生成が多いコードベースで、静的グラフだけを信用してはいけません。
よくある間違い
- 分からない状況を通過として扱うこと。 「分からなければ通過」ではなく、「分からなければ全体」であるべきです。
- 基準点を
mainやHEAD~1に固定すること。 実際の PR の対象や、以前のリモート SHA とずれます。 - 共有モジュールを変更したときに、選択実行が速いと期待すること。 このような変更では、関連テストが全体に近くなります。
- 並列環境でだけ不安定になる E2E を sleep で覆い隠すこと。 失敗したテストだけを単独で実行して、環境の問題かどうかを先に切り分けましょう(playwright-flaky-vs-failed-triage)。
- 測定せずに「速くなった」と主張すること。
関連概念
- playwright-flaky-vs-failed-triage — 並列実行でだけ不安定になる失敗を見分ける方法
- mutation-testing — テストが実際に欠陥を捕まえるかを確認する別の方法
- test-oracle — 選ばれたテストが信頼できる正解の基準を持っているか