Datadog における Intelligent Test Runner の仕組み

概要

Intelligent Test Runner は、Datadog のテスト影響度分析ソリューションです。テスト影響度分析は、過去数十年にわたって人気を博してきた手法です。しかし、その実装は一般的に難しく、時間がかかります。Intelligent Test Runner は、この複雑さを簡素化します。

テスト影響度分析では、各テストを、そのテストが使用するリポジトリ内のコードファイル群にマップします (テストコードカバレッジごとに)。その目的は、コード変更の影響を受けないテストをスキップすることです。これは、CI でテストに費やす時間の直接的な短縮につながります。

極端な例として、README ファイルのタイポを修正するだけのプルリクエストがあります。この PR では、すべてのテストを実行しても何の価値もありません。それどころか、テストが不安定だと CI が失敗し、マージする前に何度もパイプラインを再試行しなければならなくなるかもしれません。これは開発者にとっても CI にとっても時間の無駄です。Intelligent Test Runner では、PR が README ファイルを変更すると、すべてのテストがスキップされます。

他との違い

テスト選択ソリューションの中には、コードカバレッジデータに頼らず機械学習を使用することでそれを補うものもあります。このようなシステムでは、どのテストが関連性があるのかを確率的に推測します。そのため、関連性のあるテストを見逃してデフォルトブランチのビルドに失敗してしまう可能性があります。また、機械学習ベースのテクニックは、通常、動作するまでに長時間のデータ収集を必要とします。Intelligent Test Runner は、コードカバレッジのベースラインが収集されると、すぐに機能し始めます。

他のテストソリューションもコードカバレッジを使用してテスト影響度分析を計算しますが、どのテストを実行するかを評価する際には、最後のコミットの差分のみを考慮します。例えば、GitHub のプルリクエストでは、最新のコミットの CI ステータスのみを考慮してマージを行っています。その結果、すべてのコミットを CI で実行しなければなりません。そうしなければ、実行すべきテストをスキップするリスクが生じます。

Intelligent Test Runner は、Test Visibility のデータとともにテストごとのコードカバレッジ情報を活用し、 関連するすべての過去のコミットにおける以前のテストを検索します。Intelligent Test Runner の構成は、ほとんどの言語でワンクリックで行うことができ、結果は正確で他の方法よりも精度が高いです。

テスト選択の仕組み

Intelligent Test Runner を有効にすると、テストごと (フレームワークによってはスイートごと) のコードカバレッジが透過的に収集され、Datadog に送信されます。

Datadog のバックエンドは、その情報を使って以前のテスト実行を検索し、あるテストがスキップ可能かどうかを判断します。もし Datadog が、カバーファイルと追跡ファイルが現在のコミットと同一であるコミットでテストをパスした記録を持っていれば、そのテストはスキップされます。これは、コードの変更がテストに影響を与えなかったという証拠として使われます。

Intelligent Test Runner のテスト選択プロセスにおいて、何がテストをスキップ可能にするかを説明するベン図

次に Datadog ライブラリは、スキップ可能なテストリストから、ソース中でスキップ不可とマークされたテストを削除します。そしてテストの実行を進めますが、 スキップ可能なテストリストに残っているテストはスキップするようにテストフレームワークに指示します。

Intelligent Test Runner によるテストのスキップ

具体的な例を見てみましょう。

メインブランチとフィーチャーブランチへの複数のコミットを含むプルリクエストが、追跡ファイルによってどのように異なる結果になるかを説明する図

上の図は、main から分岐した開発者ブランチで、いくつかのコミットがあることを示しています。それぞれのコミットにおいて、CI は 2 つのテスト (A と B) を実行し、それぞれ異なる結果を得ています。

  • コミット 1 は両方のテストを実行しました。このコミットには、追跡ファイルと、A と B の両方のカバーファイルに影響した変更が含まれています。
  • コミット 2 は両方のテストを再度実行しました。
    • このコミットはテスト A には影響しませんでしたが (追跡ファイルやカバーファイルに変更はなかったため)、テスト A が以前にパスしたテスト実行がないため、テスト A を実行する必要があります。今回、テストがパスしたことから、これは不安定なテストであることがわかります。
    • テスト B が実行されたのは、このテストに成功した以前のテスト実行がないからであり、またコミット 2 がこのテストに影響するファイルを変更したからです。
  • コミット 3 は、追跡ファイルが変更されたため、すべてのテストを実行します。
  • コミット 4 はすべてのテストを実行します。
    • すべての基準を満たす以前のテスト実行がないため、テスト A は実行されます。コミット 1 と 3 のテスト実行は失敗したため使用できず、コミット 2 のテスト実行はコミット 2 からコミット 4 まで追跡ファイルが変更されているため使用できません。
    • すべての基準を満たす以前のテスト実行がないため、テスト B も実行されます。コミット 1 と 2 のテスト実行は失敗したため使用できず、コミット 3 のテスト実行は、コミット 3 と 4 の間にテスト B 用のカバーファイルが変更されたため使用できません。
  • コミット 5 はテストを 1 つスキップすることができました。
    • コミット 4 のテスト実行により、テスト A はスキップすることができました。このテスト実行は必要な条件をすべて満たしています。追跡ファイルはコミット 4 と 5 の間で変更されていませんし、テスト A の影響ファイルもありません。そして、テスト A はコミット 4 でパスされました。したがって、テストを実行してもコミット 4 と同じコードパスを実行することになり、CI に新しい情報を提供することはありません。この場合、テスト A をスキップすることには 2 つの利点があります。テストを実行しないことによるパフォーマンス/コストの利点と、テスト A が不安定であることによる CI の信頼性の向上です。
    • コミット 5 でカバーファイルが変更されたため、テスト B は実行される必要がありました。
  • コミット 6 は両方のテストをスキップすることができました。
    • コミット 4 のテスト実行のおかげで、テスト A をスキップすることができました。
    • コミット 5 のテスト実行のおかげで、テスト B をスキップすることができました。

その他の参考資料