選択のしくみ

TestDetta は、根拠がある場合に限り、1 ステップずつ選択を絞り込みます。追跡できないものは、多めに実行する側にフォールバックします。

少なくではなく、多めに選択する

テストの見逃しはバグですが、余分なテストは CI の時間がかかるだけです。以下のすべてのルールはこの考え方に基づいています。変更がプロジェクトやクラスに影響するかどうか分析で確信が持てない場合は、影響を受けるものとして扱い、その理由を出力に表示します。

5 つのステップ

1. プロジェクト

変更されたファイルをそのプロジェクトに対応付け、ProjectReference をたどって、それに依存するすべてのプロジェクトに広げます。Directory.Build.props、global.json、.editorconfig のようなビルド全体に関わるファイルは、その配下のすべてのプロジェクトに影響します。

2. シンボル

C# ファイルについては、変更前と変更後の構文ツリーを比較します。コメントや空白の編集では何も選択されません。それ以外の場合、TestDetta はどの型とメンバーが変更されたかを把握します。

3. カバレッジ

td record を実行すると、どのテストクラスがどのメソッドを実行したかを示すマップがベースコミットで記録されます。このマップから、変更されたメソッド、プロパティ、コンストラクターに対応するテストクラスが直接わかります。

4. 名前

新しいコード、フィールド、定数など、カバレッジでは捉えられないものは、テストクラスかカバレッジマップが把握しているコードに到達するまで、ソース内を名前でたどります。

5. フォールバック

変更を追跡できない場合は、テストプロジェクト全体を実行します。

フィルターの自己チェック

TestDetta は、選択したプロジェクトをテストクラスのフィルター付きで実行します。どのテストにも一致しないフィルターは TestDetta のバグとして扱い、フィルターなしでそのプロジェクトを再実行します。

カバレッジマップ

td record はすべてのテストをカバレッジ付きで実行し、テストプロジェクトごとに 1 つのマップを書き込みます。マップはブランチのマージベースで記録するのが最適で、CI では既定のブランチのコミットごとに 1 つ記録します。

古いマップとドリフトブリッジ

マージベースから最大 50 コミット前までに記録されたマップは引き続き使用されます(--max-map-age、既定値 50。0 を指定するとマージベースで記録されたマップだけを使用します)。その間に変更されたコードによって、マップが把握していない呼び出しが生まれている可能性があるため、TestDetta は、それ以降に変更されたメンバーのうち今回の変更に到達しうるものを実行したクラスも選択し、新しいコードは名前でたどります。

ビルドファイルや appsettings.json のように、その間の変更をメンバー単位で表せない場合は、影響を受けるテストプロジェクトを丸ごと実行します。--drift-fallback name を指定すると、マップがない場合と同様に名前で判断します。高速ですが、構成やリフレクションによってつながった呼び出しは捉えられません。

マップがない場合

セットアップ直後の最初のプルリクエストや、大きく遅れているプルリクエストでは、到達できる範囲にマップが見つかりません。その場合 TestDetta は名前だけで選択します。安全ですが選択されるテストは多くなり、その旨をログに出力します。既定のブランチで td record が実行されると、選択は精密になります。

理由を確認する

td affected と td test --dry-run は、影響を受けるすべてのテストプロジェクトを、フィルター付きで実行するのか、丸ごと実行するのか、まったく実行しないのかという理由とともに出力します。td affected --format json では、テストクラスごとの判断も加わります。-v を指定すると、プロジェクトが影響を受けた理由や、クラスがどの変更によって実行されたかなど、すべての判断を stderr に書き込みます。

$ td test --dry-run --base origin/main
td: 1 changed file(s), 2 affected test project(s).
Tests to run:
  filtered tests/Shop.Tests/Shop.Tests.csproj  (3 of 41 test class(es) run or reference the changed code)
  skip     tests/Api.Tests/Api.Tests.csproj  (no test class runs or references the changed code)