fixing-flaky-tests について
PostHog のエンジニア向けに、CI の不安定なテストを調査して修正するスキルです。実行データで失敗率を測り、問題を再現して根本原因に対処し、反復実行で修正を検証します。
主な機能
- 01GitHub の実行データから失敗率を測定
- 02不安定なテストと決定的な回帰を分類
- 03CI に近い条件で失敗を再現
- 04症状から根本原因を特定
- 05反復実行で修正を検証
活用シーン
- 01CI の断続的なテスト失敗を調査
- 02不安定な問題と決定的な回帰を区別
- 03順序やリソース競合の問題を再現
- 04複数回の実行で修正を確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ posthog/ posthog --skill fixing-flaky-testsインストール後、Claude Code で/fixing-flaky-testsを入力して読み込まれたか確認します。
詳細
フレーキーテストの修正
CI でのスイートの実行方法に変更を提案する前に、すでに試されたことを確認してください。テストの並列化、シャーディング、カバレッジベースの選択について測定済みの判定が記録されているため、却下されたアプローチが再構築されることはありません。
3 つの譲れない原則を、順に示します。
- 修正する前に再現する。 一度も観測していない失敗に対する修正は推測にすぎません。 以下のエスカレーションラダーを使い切った場合にのみ、分析的な修正にフォールバックしてください。
- 根本原因を修正する。 スリープ、タイムアウトの引き上げ、リトライ、弱められたアサーションはフレークを隠すだけで、修正はしません。
- N 回実行のループで検証する。 1 回のグリーンな実行では、断続的な失敗について何も証明できません。 N は観測された失敗率に応じて決めてください。
テストを安定させることは、唯一の有効な結末ではありません。 なぜフレークするのかが分かったら、ステップ 5 でそれが存在すべきかどうかを問います。 実際に何も捉えていないテストは削除する価値があり、実行される階層が原因でフレークするテストは 1 段下に移す価値があります。
これらのいずれよりも前に: 推測せず、測定してください。 フレーキーか決定論的か、そしてその発生率は、検証可能な GitHub の実行データから確立すべき事実です(ステップ 1)— Slack のアラート、チームメイトの推測、ci:insights ラベルから引き継いではいけません。
赤い CI 実行のトリアージ(失敗の発見と分類)には、まず debugging-ci-failures スキルを使用してください — このスキルは、失敗がフレーキーテストとして分類された時点から引き継ぎます。
フレーキーでない新しい Playwright テストを書くには、playwright-test スキルを使用してください。
investigating-ci-failures(グリーン/レッドの境界)と diagnosing-ci-and-merge-bottlenecks(engineering-analytics-flaky-tests ツールの注意点)は products/engineering_analytics/skills/ 配下のプロダクトスキルであり、ここからは呼び出せません。そのパスにある SKILL.md を読んでください。
1. 失敗率を測定する — ダイジェストではなく GitHub から
GitHub Actions API(または GitHub MCP)が信頼できる情報源です。hogli ci:insights はダイジェストであり、神託ではありません。フレーキーと決定論的を誤って分類する可能性があり、GitHub の Webhook が落ち着くまで API より遅延し、そもそも率を出すことができません。CI は失敗を出力しますが、通常の成功した実行は省略するため、分母が存在しないからです。これは仮説の_検証_や履歴コンテキストの取得に使用し、最初の一手や分類の権威として決して使わないでください。
生の実行データから自分でタイムラインを構築します。
# Pass/fail history for the workflow on the branch under suspicion (master shown):
gh run list --repo PostHog/posthog --workflow=ci-backend.yml --branch master \
--status completed --limit 60 --json conclusion,headSha,createdAt,databaseId
実行レベルの結論だけでは不十分です — 赤い実行は_別の_ジョブ/テストで失敗している可能性があります。毎回同じテストであることを、失敗したシャードのログを読んで確認してください。また、緑の実行については、そのテストがそのシャードで実際に実行され、成功したことを確認してください(別の場所にシャーディングされていただけで、修正されたわけではない可能性があります)。
gh api repos/PostHog/posthog/actions/jobs/{job_id}/logs \
| grep -E "<exact::test::id>|short test summary"
分類する前にタイムラインを読んでください。
- 隣接するコミットで成功/失敗が交互に現れる — 同じ変更されていないテストが、ある実行では成功し、別の実行では失敗していることが確認できる → 真にフレーキーです。ここで続行します。
- 長く途切れない失敗の連続(例えば 30 回以上連続)は、フレークとは統計的に相容れません — 実行あたりの率が約 95% を下回る場合、
p^30 ≈ 0となります。これは決定論的なリグレッションです。debugging-ci-failuresに進み、導入コミットを特定してください(ステップ 4)。 - すべての再実行が 1 つのランナーで失敗するのに、
masterでは同じコードが成功する → リグレッションではなく環境依存のレースです。カーネルのクロック粒度、ファイルシステム、ランナーの速度などが原因です。同じランナーで再試行しても意味がないため、「3/3 回の試行で失敗」という事実は、PR が原因かどうかについて何も示しません。マージキューの失敗は、制御を無償で与えてくれます。trunk-merge/**テスト PR の本文には、その基になっている master の SHA が記載されているので、その SHA における master の実行(gh api repos/PostHog/posthog/commits/<sha>/check-runs)を読み、同じテストがそこで実行され成功したことを確認してください。 - 両方が同時に起こることはよくあります。潜在的なフレークの率が約 100% に跳ね上がったケースです。遷移(最後の緑 → 最初の赤)を見つけてください。ダイジェストの一行の判定ではなく、その境界が何が引き金になったかを教えてくれます。失敗が master に到達した場合、境界のクエリは
products/engineering_analytics/skills/investigating-ci-failures/にあります。
失敗が一貫性があると報告された(またはその疑いがある)場合、直列化しないでください — 率を測定し、再現の試みを並行して行ってください。
測定された率(失敗数 / 総実行数、実行データから)を記録します。これはステップ 7 で検証ループの規模を見積もるために必要です。
次に、すでに対処済みでないことを確認します。
git log --oneline -10 -- <test_file_path> # recently fixed already?
hogli ci:insights search "<test name or error>" # cross-run history — corroborate against the run data, do not trust blindly
search は 2 つのサーフェスを報告します。それぞれが実際に主張している内容を読んでください。
- 壊れたテスト(最近の失敗フィンガープリント、過去 2 日間)。
potentially_resolved状態は、そのジョブの最新のデフォルトブランチ実行が再び緑になったことを意味します — 修正が入った弱い証拠であり、証明ではありません。再修正するのではなく報告する前に、_実行データに対して_これが今回の失敗をカバーしていることを確認してください。 - テストヘルス(ブラスト半径でランク付け。
engineering-analytics-flaky-testsMCP ツールが読むものと同じスパン)。confirmed_flakeは証明に裏付けられた唯一の分類です。1 つのコミットが同じマトリックスジョブでテストを失敗させ、成功させた場合、再実行の試行が緑になったか、ジョブ内リトライによるものです。別のマトリックスレッグでの成功は回復ではありません。suspected_regressionは回復が記録されなかったことを意味します — 証明の欠如であり、リグレッションの証明ではないため、自分の実行データが別のことを示すまでは本物として扱ってください。
Trunk Flaky Tests で裏付けを取る
CI はテスト結果を Trunk Flaky Tests にアップロードします。これは master と PR の実行にわたってテストごとの失敗履歴を追跡します。
PR では、trunk-io ボットの「Trunk Test Analytics」コメントが、その PR のスライス (https://app.trunk.io/posthog-inc/flaky-tests/pr/<number>?repo=PostHog/posthog) にリンクします。
.mcp.json 内の trunk MCP サーバーがそれをクエリします (ツールは Trunk によって実験的とマークされています):
search-test(repoName: "PostHog/posthog",testNameSearch: "<テスト名、ファイルパスなし>") → テストケース ID。fix-flaky-test(repoName,testCaseId) → 失敗履歴、最初に確認されたコミット、git blame、Trunk の根本原因調査。createNewInvestigation: trueを指定すると新しい分析がトリガーされます (最大 1 分かかります)。
/mcp → trunk で一度認証します (ブラウザ OAuth)。ヘッドレス環境では代わりに、サーバーエントリに TRUNK_API_TOKEN の組織トークンを指定した Authorization: Bearer ヘッダーを追加します。
ここから始める前に知っておく価値のある 2 つの制限があります。
AI 調査はこのリポジトリでは有効になっていないため、fix-flaky-test は履歴を返すか何も返さないかのどちらかで、根本原因を返すことはありません。
また、検索は名前から ID への一方向のみです。素のダッシュボードリンクは、名前を特定できないテストを識別するものなので、ID を推測するのではなくテスト名を尋ねてください。
Trunk は CODEOWNERS を通じて各テストをチームに割り当てますが、CODEOWNERS は owners.yaml のマップを表現できません。
.github/scripts/trunk-codeowners.sh は、各アップロードの前にそのマップを生成された CODEOWNERS に投影します (hogli owners:codeowners は同じファイルをローカルでビルドします)。そのため、Trunk におけるテストのオーナーは hogli owners:who と一致するはずです。
一致しない場合、その投影は 2 つのチームがどちらも主張するであろう綴りを落としています。
ci:insights と同様に、これは裏付けと履歴であり、分類の権威ではありません。flaky かどうかの判定とその発生率は、依然として上記の実行データから得られます。
2. CI から失敗を抽出する
gh run view <run-id> --log-failed
# If the job was re-run and the latest attempt passed, the flaky failure
# lives in a previous attempt:
gh api "repos/PostHog/posthog/actions/runs/{run_id}/attempts/1/jobs"
gh api "repos/PostHog/posthog/actions/jobs/{job_id}/logs"
先に進む前に記録しておくこと:
- 正確なテスト ID (ファイルパス + テスト名) と、失敗しているアサーションまたはエラー。
- 周辺の警告 —
[MSW] Unhandled、非同期リークの警告、teardown エラー — これらは多くの場合、症状より前に出力される実際の原因です。 - その前に同じワーカー/シャードで実行された他のテスト (順序の容疑者)。
3. ローカルで再現する — 何かに触れる前に
失敗が現れるまで、これらの条件を順にエスカレートしていきます。 再現した最初のレベルで停止してください。そのレベルが、ステップ 7 での検証環境になります。
-
単発実行:
hogli test <path>::<test>— テストがそもそも実行されることを確認します。 -
繰り返しループ(デフォルト N=20): 確率的なフレークを捕捉します。
-
CI に似た条件: CI は競合するランナー上で、少ないワーカー数でシャーディングされた Jest を実行します — 実行前に
frontend/package.jsonのtestスクリプトと.github/workflows/ci-frontend.ymlから現在のフラグを読み取ってください。 例(本稿執筆時点のフラグを使用)、テストをそのシャードの隣接テストと一緒に実行します:pnpm --filter=@posthog/frontend jest <test_file> <neighbor_file> --maxWorkers=2 --forceExitpytest の場合は、単一のテストではなくファイル全体またはクラスを実行してください。そうすることで、モジュールレベルのフィクスチャと順序が CI と一致します。
-
順序: 疑わしい汚染元のテストを、被害側のテストより前に実行します。ファイル内で順序を逆にします。 単独では消えるフレークは順序のバグです。
-
競合: 別のシェルで CPU 負荷の高い処理を実行しながらループを再実行します(例: ファイル全体の jest を並列実行)。 タイムアウト系のフレークは、しばしばここでしか現れません。
ループ用ハーネス — 出力を grep するのではなく、終了コードで判断します:
N=20; PASS=0; FAIL=0
for i in $(seq 1 $N); do
if <test command> >/tmp/flake-run.log 2>&1; then
PASS=$((PASS+1))
else
FAIL=$((FAIL+1)); cp /tmp/flake-run.log /tmp/flake-fail-$i.log; echo "run $i: FAIL"
fi
done
echo "$PASS passed, $FAIL failed out of $N"
ループに関するコストの注意点が 2 つあります:
- 再現中は、最初の失敗の後に
breakしてください — 捕捉した失敗ログは 1 つあれば十分です。 すべての N 回の実行を完了させるのは、失敗率を測定するとき、またはステップ 7 で検証するときだけです。 pnpm --filter=@posthog/frontend jestスクリプトは、呼び出しごとにpnpm build:productsを実行します。 ループ内では、一度ビルドしてからpnpm --filter=@posthog/frontend exec jest ...で反復してください。これは再ビルドをスキップします。
はしご全体を試しても何も再現しない場合、そのフレークは CI 環境固有です。
それで妥協する前に、ランナーにあって自分のマシンにないものを問いかけてください: macOS がナノ秒を返すところを粗いクロックが返す Linux の mtime、大文字小文字を区別するファイルシステム、異なるタイムゾーン、別のファイルシステム上の /tmp などです。
多くの場合、CI の条件を待つのではなく強制できます — os.utime でファイルを粗いクロックが生み出した順序にし、TZ=UTC で実行する — そして結局は実証的に検証できます。
そうでなければ、CI の証拠と根本原因分析に基づいた修正を進め、レポートでその旨を明示してください — その場合、ステップ 7 の検証は実証的ではなく分析的になります。
4. フレークの根本原因を特定する
症状を原因クラスに照合してください。症状にパッチを当ててはいけません。
| 症状 | 考えられる原因クラス |
|---|---|
| promise/リスナー/要素の待機でタイムアウト | await されていない非同期処理、モックの欠落、隠れた保留中のリクエスト |
| 単独では通るが隣接テストと一緒だと失敗する(またはその逆) | 共有状態: モジュールキャッシュ、DB の行、グローバル設定、順序 |
| 真夜中/UTC の境界付近、または遅いランナーで失敗する | 実時刻の使用 — time_machine.travel / フェイクタイマーの欠落 |
| リストの順序や生成された ID に対するアサーション | 非決定的な順序/ID を決定的としてアサートしている |
| クエリが書き込まれたばかりのデータを参照できない | 結果整合性(ClickHouse)、flush/commit の欠落 |
--maxWorkers=2 / 競合下でのみ失敗する | スケジューリングによって表面化する競合状態、厳しすぎるタイムアウト |
| あるランナーでは毎回失敗し、別のランナーでは通る | クロック粒度の競合: 以前の書き込みから読み取られたカットオフ |
原因が明らかでない場合は、バイセクトする
症状の表が明確な原因を指し示しておらず、テストファイル自体も変更されていない場合(git log -- <test_file> が古いままである場合)、トリガーは別の場所にあります。隣接するテスト、依存関係の更新、あるいはプロダクトの変更です。推測するのではなく、いつ 始まったのかを突き止めましょう。
-
まず CI の実行履歴をバイセクトする(低コストで、ローカルビルドは不要): ステップ 1 のタイムラインから、最後に成功した実行 → 最初に失敗した実行の境界を取り、その期間内のコミットを差分します(
git log <good>..<bad>)。この短いリストが、多くの場合そのまま犯人を特定します。 -
ローカルで再現でき、失敗が(ほぼ)決定的である場合は、コードを
git bisectする:git bisect start <bad-sha> <good-sha> git bisect run bash -c '<repro command>' # exit 0 = good, non-zero = bad注意: 断続的なフレークの場合、あるステップでの たまたまの成功 が
git bisectを誤った経路へ導きます。コードのバイセクトを信頼できるのは、失敗が決定的なときだけです。それ以外の場合は、各ステップで再現を N 回実行する(いずれか のイテレーションが失敗したら失敗とする)か、単に CI の実行履歴の境界を使いましょう。
PostHog 固有のパターン:
フロントエンド(Jest + kea)
- MSW モックの欠落:
afterMountローダーを持つ kea ロジックは、connect()チェーンを通じて API 呼び出しを発火します。3 階層深くにあるロジックが、モックされていない fetch をトリガーすることがあります。 現在、未処理のリクエストは無害な空のページネーション付き 200 で解決されるため、症状はネットワークエラーではなく、ローダーが空または誤ったデータで成功することです。 ログ内の[MSW] Unhandled GET ...警告が、欠落しているモックを指し示しています。useMocksエントリを追加してください。 未処理リクエストの挙動は以前にも変更されています(以前はハングしていました)。症状が一致しない場合は、frontend/src/mocks/jest.tsを読んで、現在モックされていないリクエストがどう扱われるかを確認してください。 toFinishAllListeners()のタイムアウト: マウントされているすべての kea ロジックにわたる、すべての kea リスナーの promise を待ちます(デフォルト 3 秒 —kea-test-utilsのLISTENER_FINISH_WAIT_TIMEOUT)。 保留中のローダーを持つ接続済みのロジックは、これをブロックします。 保留中の処理を修正してください。タイムアウトを引き上げないでください。- モック URL の不一致:
mocksToHandlersは末尾のスラッシュを除去しますが、クエリパラメータ、@current形式のセグメント、:paramパターンは実際のリクエスト URL と一致している必要があります。[MSW] Unhandledの行と比較してください。 - テストごとのハンドラリセット:
frontend/src/mocks/jest.tsはグローバルなafterEach(() => mswServer.resetHandlers())を登録します。 各it.eachケースは個別のテストであるため、実行時のモックはbeforeEachで(再)登録する必要があります。 - マウントのリーク:
beforeEachでマウントされ、一度もアンマウントされないロジックは、非同期処理を後続のテストへリークさせます。
バックエンド(pytest)
- DB 状態のリーク: 分離されていないテスト間で行が共有されている — フィクスチャのスコープと、テストに
@pytest.mark.django_db(transaction=True)が必要かどうかを確認してください。 - 実時間:
time_machine.travel(..., tick=False)を使用してください。now()から派生した値に対してアサートしないでください。 - タイムスタンプから派生したカットオフ: 直前の書き込みの記録時刻を基準に「時点」を固定したりフィルタリングしたりする — 代わりに時刻を明示的にスタンプしてください(
/writing-tests、「Two writes in a row are not ordered in time」)。 - ClickHouse の結果整合性: クエリが、たった今挿入されたデータを認識しないことがあります。スリープさせるのではなく、テストのセットアップで明示的にフラッシュしてください。
5. 結果を決める — 修正は三つのうちの一つ
_なぜ_フレークするのかが分かったら、それを安定させるために労力を費やす前に、そのテストがそもそも存在すべきかどうかを問いましょう。
フレークテストは、コストはすでに証明済みで価値はそうではない、唯一のケースです。再実行、実時間、注意力を実際に消費してきたのです。
ですから /writing-tests のゲートを、新しいテストに適用するよりも強い力で、遡って適用します。
このテストは、既存のどのテストもすでに捕捉していない、どのような現実的なリグレッションを捕捉するのか?
有効な結果は三つあります。意図的に選びましょう。最初のものにデフォルトで流されてはいけません。
| 結果 | いつ | 次 |
|---|---|---|
| 修正する | ほぼ適切なコストで実際のリグレッションをガードしている。 | ステップ 6 |
| レベルを下げる | ガードする価値はあるが、フレークがその実行レベルに固有のものである。 | 下記 |
| 削除する | 捕捉するリグレッションを名指しできない、または別のテストがすでに捕捉している。 | 下記 |
削除する
ゲートを通過できない、繰り返し現れる形:
- トートロジー的なスモークテスト。 同じファイル内の多くの兄弟テストが実行するためにすでに必要としている前提条件 — チャートが描画されたこと、リストが空でないこと — をアサートするもの。 それ以降のすべてのテストは、そのより強いバージョンです。
- 重複したカバレッジ。 既存のテストがすでに通しているパスを、より薄く二番目にテストするもの。 パラメータ化されたケースとして畳み込むか、削除しましょう。
- サードパーティのアサーション。 私たちのロジックではなく、ベンダーの結果整合性、スケジューラー、または API を試すためにフレークするもの。 それはベンダーが書くべきテストであり、モックベースの兄弟テストが通常すでにこちらの側をカバーしています。
- 恒久的にゲートされたもの。 CI 以外のどこでもスキップされ(資格情報の欠如、オプトインマーカー)、誰もそれに対して開発せず、CI だけがそのコストを払うもの。
削除は不可逆なので、修正よりも高い基準を伴います。
- ユーザーの明示的な承認を得ること。これは隔離が伴うのと同じゲートです。 削除を提案して引き返しましょう。自分で決行してはいけません。
- カバレッジを説明すること: これがまだどこで捕捉されているかを file:line で名指しするか、何が失われ、なぜそれが許容できるのかを率直に述べましょう。 「おそらく他所でカバーされている」は答えになりません — 兄弟テストを読みに行きましょう。
- 時間的プレッシャーの下で赤いビルドを緑に変えるために削除してはいけません。 それは余計な手間をかけた隔離であり、実際のリグレッションが出荷される原因です。
その後はステップ 8 にスキップします。ループするものは何もありません。
レベルを下げる
その場で堅牢化するのではなく、/writing-tests のコストのはしごを下へテストを移動させます。
直接呼び出しで証明できるロジックを証明するために、実際のブローカー、ブラウザ、またはベンダー API を往復するのは、トランスポートをテストしているのであり、非決定性はトランスポートに宿ります。
レベルを下げることは、フレークを構造的に除去するので、ますます手の込んだ待機よりも優先しましょう。
置き換えは新しいテストです。それに対して /writing-tests のゲートを実行し、もはや存在しない古い再現条件に対してではなく、その新しいレベルで検証しましょう。
6. 根本原因を修正する — 決して隠蔽しない
| 誘惑される隠蔽手段 | 代わりに行うこと |
|---|---|
アサート前に sleep(2) / setTimeout | 特定の条件を待機する(waitFor、expectLogic、明示的なフラッシュ) |
| テスト/リスナーのタイムアウトを引き上げる | 何がハングしているのかを突き止める。タイムアウトはメッセンジャーである |
リトライを追加する(pytest-rerunfailures の --reruns、jest.retryTimes) | 真に非決定的な外部インフラに限定し、コメントとリンクした issue を添える — テスト対象の製品コードには決して使わない |
| テストをスキップ / 隔離する | ユーザーの明示的な承認がある場合のみ、リンクした issue とともに |
| アサーションを緩める | データを決定的にする(ソート、フリーズ、シード)。アサーションは厳密に保つ |
| 存在すべきでないテストを堅牢化する | ステップ 5 に戻る — 削除するかレベルを変更する方が安上がりな修正である |
修正は最小限に留め、可能な限りテストまたはそのフィクスチャ内に収めてください。 競合が製品コードにある場合、そのフレークは本物のバグを発見したということです — 製品コードを修正し、レポートにその旨を記載してください。
7. N 回実行ループで検証する
ステップ 3 のハーネスを修正後のコードに対して失敗を再現したのと同じ条件(同じ隣接テスト、ワーカー数、競合)で実行します。
N の大きさは修正前に観測された失敗率から決めます。およそ k 回に 1 回失敗していたなら、フレークが解消されたという約 95% の信頼度を得るには、およそ N ≥ 3k 回の連続成功が必要です — (1 - 1/k)^(3k) ≈ 5%。
したがって N = max(3k, 20) を使用します。
使用可能な失敗率の推定値がない場合は、50 回実行し、レポートに信頼度が低下していることを記載してください。
フレークがローカルで一度も再現しなかった場合は、リグレッションチェックとして N = 20 を実行し、検証を分析的検証と位置づけてください。
ループ内で何らかの失敗が発生した場合 → ステップ 4 に戻ります。根本原因が誤っていたか不完全でした。 最後に、周辺のファイル/スイートを 1 回通常どおり実行し、修正が兄弟テストを壊していないことを確認します。
レベルを変更した場合旧来の再現条件はもはや当てはまりません。 置き換えたテストをそれ自身のレベルでループ実行し、フレークが解消されたのが旧テストがなくなったからであって高速化したからではないことを確認してください。
削除した場合ループ実行するものはありません。 周辺のファイル/スイートを 1 回実行し、それに依存していたものがないことを確認し、カバレッジに関する論拠をレポートに含めてください。
8. レポート
Test: <file path>::<test name>
Observed in CI: <measured rate from run data, e.g. 8/45 runs over 3h (gh run list); ci:insights state corroborates>
Local repro: <command + conditions, e.g. 3/20 failures with neighbor X, maxWorkers=2 | not reproducible locally>
Root cause: <one or two sentences>
Outcome: fixed | re-leveled (<from> → <to>) | deleted
Change: <what changed and why it removes the cause; for a deletion, what still covers the behavior (file:line) and what coverage is genuinely lost>
Validation: <N>/<N> passes under repro conditions | <N>/<N> at the new level | analytical only (CI-specific) | n/a, deleted
Follow-ups: <product bug found, related tests with the same pattern, or none>
境界
- 修正を「テスト」するために CI ジョブを再実行したり、プッシュしたり、GitHub に投稿したりしないでください — ローカルで検証してください。
- フレーク修正の一環として
.github/workflows/を編集しないでください。 - フレークを成功させるためにスナップショットを承認/更新しないでください。
- 同じ根本原因のパターンが兄弟テストにも明らかに影響している場合は、機械的な場合に限り同じ変更で修正してください。それ以外はフォローアップとして列挙してください。
- ユーザーの明示的な承認なしにテストを削除しないでください。加えて、カバレッジに対する名前付きの代替を用意するか、何が失われるかを明確に述べてください。 削除は有効な結果であり、決してグリーンへの近道ではありません。