実際の実行例

Spectre.Console に対する実際の変更 1 件(コミット 01027ef)を、カバレッジマップからジョブサマリーまでたどります。このページの出力はすべてその実行からコピーしたもので、手で書いたものではありません。

変更内容

Rule ウィジェットの 1 行のリファクタリングです。動作は何も変わりませんが、コードの変更であるため、通常のパイプラインではすべてのターゲットフレームワークで 141 クラスの全 787 テストを実行することになります。

// src/Spectre.Console/Widgets/Rule.cs, GetLineWithoutTitle
 var text = border.GetPart(BoxBorderPart.Top).Repeat(maxWidth);
+var style = Style ?? Spectre.Console.Style.Plain;

 return
 [
-    new Segment(text, Style ?? Spectre.Console.Style.Plain),
+    new Segment(text, style),
     Segment.LineBreak
 ];

実行

マップは main で td record によって記録されています。プルリクエストでは次のように実行します。

$ td test --base main -- -f net10.0td: 1 changed file(s), 1 affected test project(s).Tests to run:  filtered src/Spectre.Console.Tests/Spectre.Console.Tests.csproj    (14 of 141 test class(es) run or reference the changed code, 0.6 s of 3.0 s (80% saved))Recorded test time: 0.6 s of 3.4 s (83% saved).Passed!  - Failed:     0, Passed:   156, Skipped:     0, Total:   156 - Spectre.Console.Tests.dll (net10.0)td summary:  passed  src/Spectre.Console.Tests/Spectre.Console.Tests.csproj  (14 class(es), 4.6 s)1 project(s) run, 0 failed, 0 skipped.Recorded test time: 0.6 s of 3.4 s (83% saved).

141 クラスの 787 テストではなく、14 クラスの 156 テストが実行されました。Spectre.Console.Ansi.Tests プロジェクトはビルドすらされていません。依存しているものが何も変更されていないためです。

その 14 クラスは正しかったのか

確認のため、GetLineWithoutTitle が例外をスローするようにして、テストスイート全体を実行しました。失敗したのはちょうど 14 クラスで、それは TestDetta が選択した 14 クラスと一致しました。該当するのは AlignTests+Center、+Left、+Right、CanvasTests、ColumnsTests、ExceptionTests、LayoutTests、PadderTests、PanelTests、RecorderTests、RowsTests、RuleTests、TableTests、TextPathTests です。そのほとんどは Rule に一切言及していません。レイアウトやパネルを経由して到達しており、これはカバレッジマップでは把握できても、名前による検索では見つけられないものです。

ジョブサマリー

--summary-file "$GITHUB_STEP_SUMMARY" を指定すると(GitHub Action では自動的に設定されます)、すべてのプルリクエストに次のようなサマリーが付きます。何が実行されたか、各クラスが実行された理由、何がスキップされたかが示されます。

TestDetta test selection

1 changed file(s) compared with main.

  • 1 of 1 affected test project(s) ran, 0 skipped.
  • Recorded test time: 0.6 s of 3.4 s (83% saved).
  • All selected tests passed.
  • src/Spectre.Console.Tests/Spectre.Console.Tests.csproj: passed, 14 test class(es), recorded time 0.6 s of 3.0 s (80% saved)
    • why: 14 of 141 test class(es) run or reference the changed code
    • classes:
      • AlignTests+Center: ran Spectre.Console.Rule.GetLineWithoutTitle(RenderOptions,int), which changed (map 01027efcd6c4)
      • AlignTests+Left: ran Spectre.Console.Rule.GetLineWithoutTitle(RenderOptions,int), which changed (map 01027efcd6c4)
      • AlignTests+Right: ran Spectre.Console.Rule.GetLineWithoutTitle(RenderOptions,int), which changed (map 01027efcd6c4)
      • CanvasTests: ran Spectre.Console.Rule.GetLineWithoutTitle(RenderOptions,int), which changed (map 01027efcd6c4)
      • ColumnsTests: ran Spectre.Console.Rule.GetLineWithoutTitle(RenderOptions,int), which changed (map 01027efcd6c4)
      • ExceptionTests: ran Spectre.Console.Rule.GetLineWithoutTitle(RenderOptions,int), which changed (map 01027efcd6c4)
      • LayoutTests: ran Spectre.Console.Rule.GetLineWithoutTitle(RenderOptions,int), which changed (map 01027efcd6c4)
      • PadderTests: ran Spectre.Console.Rule.GetLineWithoutTitle(RenderOptions,int), which changed (map 01027efcd6c4)
      • PanelTests: ran Spectre.Console.Rule.GetLineWithoutTitle(RenderOptions,int), which changed (map 01027efcd6c4)
      • RecorderTests: ran Spectre.Console.Rule.GetLineWithoutTitle(RenderOptions,int), which changed (map 01027efcd6c4)
      • RowsTests: ran Spectre.Console.Rule.GetLineWithoutTitle(RenderOptions,int), which changed (map 01027efcd6c4)
      • RuleTests: ran Spectre.Console.Rule.GetLineWithoutTitle(RenderOptions,int), which changed (map 01027efcd6c4)
      • TableTests: ran Spectre.Console.Rule.GetLineWithoutTitle(RenderOptions,int), which changed (map 01027efcd6c4)
      • TextPathTests: ran Spectre.Console.Rule.GetLineWithoutTitle(RenderOptions,int), which changed (map 01027efcd6c4)
    • skipped: 127 class(es) that ran none of the changed code and name none of it

ここではクラス名を短縮しています。実際のサマリーには完全な名前(Spectre.Console.Tests.Unit.RuleTests)が出力されます。

理由を確認する

すべての判断には、読んで確認できる理由があります。-v を指定すると、その経緯が stderr に書き込まれます。

[DBG] CodeChangeDetector: src/Spectre.Console/Widgets/Rule.cs: Modified Spectre.Console.Rule GetLineWithoutTitle(RenderOptions,int)
[DBG] CodeChangeDetector: src/Spectre.Console/Widgets/Rule.cs: 1 changed symbol(s), affects whole project: False
[DBG] TestSelector: Spectre.Console.Rule GetLineWithoutTitle(RenderOptions,int) is affected because it changed
[DBG] TestSelector: Spectre.Console.Rule Render(RenderOptions,int) is affected because GetLineWithoutTitle
[DBG] TestSelector: Spectre.Console.Rule GetLineWithoutTitle(RenderOptions,int): test classes taken from coverage

また、--format json では、スクリプトやダッシュボード向けに、すべてのクラスの判断を根拠と推定時間とともに出力します。

{
  "project": "src/Spectre.Console.Tests/Spectre.Console.Tests.csproj",
  "mode": "Filtered",
  "reason": "14 of 141 test class(es) run or reference the changed code",
  "estimate": {
    "fullSeconds": 3,
    "selectedSeconds": 0.6,
    "savedSeconds": 2.4
  },
  "decisions": [
    {
      "class": "Spectre.Console.Tests.Unit.AlignTests+Center",
      "selected": true,
      "kind": "coverage",
      "evidence": "ran Spectre.Console.Rule.GetLineWithoutTitle(RenderOptions,int), which changed (map 01027efcd6c4)"
    },
    {
      "class": "Spectre.Console.Tests.PublicApiTests",
      "selected": false,
      "kind": "skipped",
      "evidence": "ran none of the changed code and names none of it (map 01027efcd6c4)"
    },
    "…139 more"
  ]
}

独自の方法でテストを実行する

td affected --format commands は TestDetta が行う dotnet test の呼び出しを出力するため、独自のスクリプトで実行できます。

dotnet test src/Spectre.Console.Tests/Spectre.Console.Tests.csproj --filter 'FullyQualifiedName~Spectre.Console.Tests.Unit.AlignTests+Center.|FullyQualifiedName~Spectre.Console.Tests.Unit.AlignTests+Left.|FullyQualifiedName~Spectre.Console.Tests.Unit.AlignTests+Right.|FullyQualifiedName~Spectre.Console.Tests.Unit.CanvasTests.|FullyQualifiedName~Spectre.Console.Tests.Unit.ColumnsTests.|FullyQualifiedName~Spectre.Console.Tests.Unit.ExceptionTests.|FullyQualifiedName~Spectre.Console.Tests.Unit.LayoutTests.|FullyQualifiedName~Spectre.Console.Tests.Unit.PadderTests.|FullyQualifiedName~Spectre.Console.Tests.Unit.PanelTests.|FullyQualifiedName~Spectre.Console.Tests.Unit.RecorderTests.|FullyQualifiedName~Spectre.Console.Tests.Unit.RowsTests.|FullyQualifiedName~Spectre.Console.Tests.Unit.RuleTests.|FullyQualifiedName~Spectre.Console.Tests.Unit.TableTests.|FullyQualifiedName~Spectre.Console.Tests.Unit.TextPathTests.'

40 のリポジトリ全体での結果

1 つの変更は 1 つのデータポイントにすぎません。評価用コーパスでは、40 のオープンソースライブラリのメンバーを 1 つずつ変更しています。1 つのメンバーへの変更に対して TestDetta が実行したテストクラスの割合は中央値で 28% で、40 リポジトリのうち 18 リポジトリでは 4 分の 1 以下でした。ほぼすべてのテストがコアに触れるライブラリ(Scriban、Markdig)ではあまり効果がなく、レイヤー化されたコードで最も効果があります。失敗したクラスはすべて選択結果と照合しました。失敗した変更 2,941 件のうち見逃しは 3 件で、それぞれ修正済みです。

変更ごとに実行されたテストクラスの割合(リポジトリ別) 評価用コーパスの第 1 ラウンド:40 の各リポジトリについて、1 つのメンバーへの変更に対して TestDetta が選択したテストクラスの割合の平均。中央値は 28%。 0% 25% 50% 75% 100% opentelemetry-dotnet: 失敗した変更 79 件の平均で、テストクラスの 6% を実行opentelemetry-dotnet6% polly: 失敗した変更 65 件の平均で、テストクラスの 7% を実行polly7% roslynator: 失敗した変更 80 件の平均で、テストクラスの 8% を実行roslynator8% spectre: 失敗した変更 80 件の平均で、テストクラスの 9% を実行spectre9% swashbuckle: 失敗した変更 80 件の平均で、テストクラスの 12% を実行swashbuckle12% nsec: 失敗した変更 79 件の平均で、テストクラスの 13% を実行nsec13% aspnetcore-odata: 失敗した変更 79 件の平均で、テストクラスの 13% を実行aspnetcore-odata13% guardclauses: 失敗した変更 24 件の平均で、テストクラスの 13% を実行guardclauses13% mimekit: 失敗した変更 79 件の平均で、テストクラスの 14% を実行mimekit14% autofixture: 失敗した変更 80 件の平均で、テストクラスの 16% を実行autofixture16% communitytoolkit: 失敗した変更 76 件の平均で、テストクラスの 18% を実行communitytoolkit18% libgit2sharp: 失敗した変更 57 件の平均で、テストクラスの 19% を実行libgit2sharp19% castle-core: 失敗した変更 80 件の平均で、テストクラスの 19% を実行castle-core19% serilog: 失敗した変更 79 件の平均で、テストクラスの 20% を実行serilog20% mediator: 失敗した変更 80 件の平均で、テストクラスの 21% を実行mediator21% fastendpoints: 失敗した変更 80 件の平均で、テストクラスの 23% を実行fastendpoints23% fluid: 失敗した変更 79 件の平均で、テストクラスの 23% を実行fluid23% carter: 失敗した変更 80 件の平均で、テストクラスの 24% を実行carter24% lamar: 失敗した変更 80 件の平均で、テストクラスの 26% を実行lamar26% awesomeassertions: 失敗した変更 81 件の平均で、テストクラスの 27% を実行awesomeassertions27% nsubstitute: 失敗した変更 3 件の平均で、テストクラスの 29% を実行nsubstitute29% immediate-handlers: 失敗した変更 43 件の平均で、テストクラスの 29% を実行immediate-handlers29% open-xml-sdk: 失敗した変更 80 件の平均で、テストクラスの 30% を実行open-xml-sdk30% fluentvalidation: 失敗した変更 80 件の平均で、テストクラスの 32% を実行fluentvalidation32% unitsnet: 失敗した変更 80 件の平均で、テストクラスの 34% を実行unitsnet34% vogen: 失敗した変更 77 件の平均で、テストクラスの 36% を実行vogen36% nodatime: 失敗した変更 80 件の平均で、テストクラスの 43% を実行nodatime43% graphql-dotnet: 失敗した変更 80 件の平均で、テストクラスの 45% を実行graphql-dotnet45% newtonsoft-json: 失敗した変更 80 件の平均で、テストクラスの 47% を実行newtonsoft-json47% autofac: 失敗した変更 77 件の平均で、テストクラスの 49% を実行autofac49% scrutor: 失敗した変更 80 件の平均で、テストクラスの 57% を実行scrutor57% meziantou-analyzer: 失敗した変更 80 件の平均で、テストクラスの 63% を実行meziantou-analyzer63% automapper: 失敗した変更 80 件の平均で、テストクラスの 67% を実行automapper67% protobuf-net: 失敗した変更 80 件の平均で、テストクラスの 78% を実行protobuf-net78% yamldotnet: 失敗した変更 80 件の平均で、テストクラスの 78% を実行yamldotnet78% cliwrap: 失敗した変更 69 件の平均で、テストクラスの 81% を実行cliwrap81% nlog: 失敗した変更 65 件の平均で、テストクラスの 82% を実行nlog82% mapperly: 失敗した変更 79 件の平均で、テストクラスの 89% を実行mapperly89% markdig: 失敗した変更 80 件の平均で、テストクラスの 98% を実行markdig98% scriban: 失敗した変更 71 件の平均で、テストクラスの 100% を実行scriban100%
変更ごとに実行されたテストクラスの割合の平均(評価の第 1 ラウンド)。行にカーソルを合わせると、その元になった変更の件数が表示されます。