コンテンツにスキップ

フレーキーなテスト、タイムアウト、最小化

1 回の実行で読み取った kill が常に kill とは限らない。Shi, Bell and Marinov (ISSTA 2019) は、 同一の再実行の間でミューテーションスコアが 4 ポイント変動し、ミュータント × テストの組の 9% が 不安定であることを測定した。テストごとの kill 行列はスコアよりも影響を受けやすい。フレーキーな kill が 1 つあれば、あるテストを essential と判定したり、別のテストを redundant と判定したりするのに 十分である。2 つのフラグが再実行によって確信度を高める。

run -confirm-kills N は、ミュータントを kill したテストを、それぞれが N 回失敗するまで再実行する。 再実行で再現しない失敗は killed_by ではなく suspicious_by に記録される (mutmut の SUSPICIOUS)。 したがってそれは kill でも要件でもない。ミュータントを kill したテストがすべて suspicious になると、 そのミュータントは LIVED になる。再実行するのは kill したテストだけなので、コストはスイートではなく kill の数に比例する。

run -confirm-baseline N は、トレース時に各テストを 1 回ではなく N 回実行する。そのうち一部の実行で 失敗し他で成功したテストは、実行を失敗させる代わりに tests[]"flaky": true とマークされる。 毎回失敗するテストは、失敗するベースラインが常にそうであるように、依然としてエラーである。 フレーキーなテストのミュータント下での失敗は決して kill に数えず、minimize はそのテストを行列から 完全に除外する。選択されることも redundant とされることもなく、代わりに flaky_tests に列挙される。 その観測はどちらの判定の根拠にもならないからである。totals.suspicious は未確認の kill を 1 つ以上 持つミュータントの数であり、実行のどれだけを疑うべきかを示す。

ターミナルウィンドウ
go run ./cmd/mutrim run -test-bin pkg.test -mutants mutants.json \
-confirm-baseline 3 -confirm-kills 3 -out report.json

各ミュータントのタイムアウトは、それに到達するテストに従う。-timeout-factor (3) × それらの トレースされた所要時間 + -timeout-const (2s) で、下限は -min-timeout (10s)、上限は -timeout-factor × ベースライン実行である。したがって、1 つの速いテストが到達するホットな関数で 無限ループするミュータントは、スイート全体分よりずっと早く打ち切られる。各結果は timeout_ms を 記録する。-timeout は代わりにすべてのミュータントに 1 つのタイムアウトを設定する。ループする ミュータントはすべて下限まで待つので、速いユニットテストの実行ではこれが支配的になる。 -min-timeout 1s にすると、mutrim 自身の minimize パッケージは同じ判定のまま 41s ではなく 5s で 実行できた。プロセスを起動したりネットワークに触れたりするテストでは、最悪ケースがトレースされた時間 から大きく外れるので、デフォルトのままにしておく。

トレースとミュータントの実行は -jobs 個のテストプロセスを同時に実行する (デフォルトは GOMAXPROCS)。 実行の終了順にかかわらず、結果は mutants.json の順序を保つ。

minimize はその行列を構成し (到達したサイトの重み 1、kill の重み 5、テスト時間 1 ミリ秒あたり。 -w-site / -w-kill)、貪欲な集合被覆を実行する。kill の要件は支配ミュータント (dominator mutant) だけである (Ammann–Delamaro–Offutt; Kurtz et al.)。あるミュータントを kill する テストがすべて別のミュータントも kill するなら後者は包含 (subsume) されて除かれ、kill するテストが 同じミュータントは 1 つの要件にまとめられるので、1 つの if の変種をすべて kill するテストが過大に 評価されない。totals は kill された (killed) ミュータント、そのうちの dominators、生き残った (survived) ミュータントの数と dominator_score = dominators / (dominators + survived) を示す。その後、他の選択済みテストの組み合わせで要件が満たされる 選択済みテストをすべて取り除く。selected は保持されたテストをゲインとともに、essential なものから 順に列挙する。スイート全体で他のどのテストも満たさない要件を満たすテストは essential であり (Harrold–Gupta–Soffa; Chen & Lau)、それらの要件のラベル (unique) が付く。したがってリストは上から 「必ず保持」→「当面は保持」と読める。redundant は残りのテストを、それぞれを包含する選択済みテストと、 その要件を共有する他のテストの数 (shared_with。1 なら削除 1 回で essential になる) とともに列挙し、 weak_spots (-mutants 指定時) はミュータントが生き残る関数を列挙する。-keep (デフォルト ^TestRegression_) に一致するテスト、または doc コメントで //mutrim:keep タグが 付いたテスト (-srcs は走査する _test.go ファイルまたはディレクトリを指定する) は常に保持される。 -keep はサブテスト行の完全な TestX/case 名を見て、親のタグはそのすべてのサブテストを保持する。 修飾された行では、どちらもパッケージ内の名前に一致する。-matrix は構成したテスト × 要件行列を 厳密ソルバー向けに JSON でエクスポートし、-raw-matrix を付けると支配ミュータントではなく kill された すべてのミュータントを含める。1 つのパッケージのシャードはまとめて渡せ、複数の パッケージのレポートもまとめて渡せる。そのとき修飾されていない各テスト名はレポートのパッケージで 修飾されるので、テストはどこに現れても 1 つの行になる。-extra-testpkg のミュータントに対して 実行され、かつ app 自身のミュータントに対しても実行された app のテストは、両方のサイトと kill を 持つ。