やりたかったこと
複数の作業がフォルダを分けて並列で動いているのに、プッシュ直前のフックと PR の CI が毎回数百ファイル分の単体テストをすべて実行していて、同じ作業が繰り返されていました。これを「変更に関連するテストだけ」に減らしつつ、安全装置はそのまま残したいと考えました。
事前に知っておくと良い概念です。
- import グラフ: ファイルが他のファイルを
importで取り込む関係の地図です。変更されたファイルからこの地図を逆向きにたどると、影響を受けるテストを見つけられます。 - fail closed: 判断が不確実なときに通過させず、より安全な側(ここでは全体テスト)へ行く設計です。信号機が故障したときに、すべての方向を赤信号にするのと同じです。
やったこと
自分でツールを作らず、Vitest に組み込まれている --changed <基準> オプションを使いました。コミットの差分、ステージ済み・未ステージの変更、追跡されていないファイルまで集めて、関連テストを選んでくれます。
- 基準点は推測しません。 PR の CI はプラットフォームが教えてくれる実際の base コミットを、プッシュフックは Git がフックに渡してくれる「そのブランチの以前のリモート SHA」を使います。新しいブランチは、分岐元を Git が記録していないので、
TEST_BASEを人が明示するようにしました。 - 不確実なら全体へ行きます。 ファイルの削除・名前変更はグラフから出発点がなくなるので全体テストに切り替え、設定・lockfile・tsconfig・翻訳 JSON・共通アセットのようなグローバルな入力は
forceRerunTriggersですべて再実行します。汚れた作業ツリーや、現在の HEAD ではない ref は止めます。 - 最終候補は別のゲートに置きました。 開発ブランチへの PR は選択実行、メイン候補は全体実行 + E2E です。
- worker 数は自分で測って決めました。1 つのプロセスの worker を増やすことと、プロセスを複数同時に立ち上げることは別物でした。ただ、実際の並列環境でのスループットは測定できなかったので、一般化していません。
- フックの回帰テスト(プッシュの範囲、初回プッシュの基準、汚れた作業ツリー、存在しない base、削除ファイルのフォールバック)を追加しました。
学んだこと
絞り込むルールだけでは危険です。 グラフを信用できない状況を全体実行へ回すルールが一緒に必要です。共有モジュールを直すと関連テストが全体に近くなり、選択実行の効果がなくなることも、実際に確認しました。→ 変更の影響に基づくテスト選択
基準点はツールからではなく、状況から受け取ります。 分かっている値がなければ推測せず、人に求めます。
並列環境でだけ不安定になる E2E は、まず環境の問題を疑います。 ローカルの並列実行では失敗する場所が変わりながら不安定になりましたが、失敗したファイルだけを別に実行すると通り、CI と同じ設定で実行するとすべて通りました。sleep を入れたりアプリのコードを変えたりせず、CI の設定をそのまま使う方を選びました。
まだ確認できていないこと: 実際の並列作業全体の所要時間とリソース使用量の改善、動的 import が原因でテストが漏れる事例です。後者は最終的な全体ゲートに任せています。