ある UI テストが先週から安定して失敗するようになったものの、直近では数十件のコミットがマージされています。コミットを一つずつチェックアウトし、Xcode を開いてテストを実行する方法は時間がかかるうえ、環境差分も見落としがちです。より効率的なのは、「このコミットが対象の不具合を持ち込んだか」を再現可能なコマンドとして定義し、git bisect に二分探索させる方法です。候補が 64 コミットなら、理論上は約 6 回の判定で絞り込めます。難しいのは二分探索そのものではなく、各回の判定結果を信頼できるものにすることです。
まず回帰を機械判定できる結果として定義する
開始前に、正常であることを確認済みのコミットと、異常であることを確認済みのコミットを少なくとも一つずつ用意します。「画面に問題がある」といった曖昧な定義ではなく、特定の XCTest アサーションが失敗する、指定した Scheme をアーカイブできない、固定入力に対して誤った出力が返るなど、回帰を単一の観測項目まで絞り込んでください。
判定条件では、次の変数をすべて固定します。
- Scheme、Configuration、テストターゲット
- 使用する Xcode のパスと依存関係のロックファイル
- シミュレータのモデル、OS バージョン、言語、タイムゾーン
- テストデータ、ネットワーク依存、実行アカウント
- 正常と異常を区別する唯一の根拠
git bisectは判定器の誤差も忠実に増幅します。偶発的な失敗を不良コミットと判定すると、その後の探索が問題なく完了しても、結論が完全に誤っている可能性があります。
まず正常側と異常側のコミットで、同じコマンドをそれぞれ手動で 2 回実行します。結果を安定して再現できない場合は、すぐに二分探索を始めるのではなく、先にテストの分離を改善してください。
候補コミットごとに実行環境を分離する
G-Mini のクラウド Mac で実行する場合は、開発者が作業中のディレクトリではなく、専用の作業コピーを使用することを推奨します。二分探索中はコミットが頻繁に切り替わるため、未追跡ファイル、自動生成された設定、共有キャッシュが判定を汚染する可能性があります。
| 分離対象 | 推奨方法 | 理由 |
|---|---|---|
| Git ワークスペース | 独立した clone または worktree を使用する | 日常の変更を上書きしないため |
| DerivedData | コミットハッシュごとにディレクトリを分ける | 古い成果物が別コミットで再利用されるのを防ぐため |
| シミュレータ | UDID を固定する | destination の自動選択による実行先の変動を防ぐため |
| 結果バンドル | 実行ごとに個別保存する | 最初の異常コミットを再検証しやすくするため |
| 依存関係 | ロックファイルを保持し、暗黙の更新を無効にする | バージョン解決結果の変化を防ぐため |
開始前に git status --porcelain を実行し、出力が空であることを確認します。シミュレータはあらかじめ作成して起動し、その UDID を環境変数に設定してください。判定スクリプト内で名前を使って「利用可能な任意のデバイス」をその都度選んではいけません。同名デバイスの存在や OS のアップデートによって、実際の実行先が変わる可能性があります。
Xcode 用の三値判定スクリプトを作成する
git bisect run が扱えるのは成功と失敗だけではありません。終了コード 0 は正常、1 から 127 は異常、125 は現在のコミットを判定できないためスキップすべきことを表します。この第 3 の状態は、長い履歴を持つプロジェクトで特に重要です。古いコミットは現在の依存関係ではビルドできない場合がありますが、それだけで調査対象の回帰を含んでいるとは限りません。
次のスクリプトは、最初にプロジェクトをビルドできるか確認し、その後で対象テストだけを実行します。基本的なビルドに失敗した場合は判定不能とし、テストが明確に失敗した場合に限って異常と判定します。
#!/bin/zsh
set -u
: "${DESTINATION_ID:?Set DESTINATION_ID first}"
sha="$(git rev-parse --short HEAD)"
root="${TMPDIR:-/tmp}/xcode-bisect"
derived="$root/derived-$sha"
result="$root/result-$sha.xcresult"
log="$root/test-$sha.log"
mkdir -p "$root"
rm -rf "$result"
xcodebuild \
-project App.xcodeproj \
-scheme App \
-configuration Debug \
-destination "platform=iOS Simulator,id=$DESTINATION_ID" \
-derivedDataPath "$derived" \
build >"$root/build-$sha.log" 2>&1
if [[ $? -ne 0 ]]; then
exit 125
fi
xcodebuild \
-project App.xcodeproj \
-scheme App \
-destination "platform=iOS Simulator,id=$DESTINATION_ID" \
-derivedDataPath "$derived" \
-resultBundlePath "$result" \
-only-testing:AppTests/CheckoutReducerTests/testExpiredCart \
test >"$log" 2>&1
status=$?
if [[ $status -eq 0 ]]; then
exit 0
fi
if grep -q "TEST FAILED" "$log"; then
exit 1
fi
exit 125
不具合の種類に応じて分類を調整する
コンパイル回帰を調査する場合、ビルド失敗は 125 ではなく 1 を返すべきです。動作回帰を調査する場合は、依存関係のダウンロード失敗、シミュレータサービスの異常、ディスクへの書き込み失敗など、基盤環境の問題をスキップしなければなりません。xcodebuild のゼロ以外の終了コードを、すべて対象の回帰として扱わないでください。
二分探索を実行し、最初の異常コミットを再検証する
スクリプトを用意したら、現在のブランチとワークスペースの状態を記録してから、次を実行します。
chmod +x ./scripts/bisect-xcode.sh
export DESTINATION_ID="固定的模拟器UDID"
git bisect start
git bisect bad BAD_COMMIT
git bisect good GOOD_COMMIT
git bisect run ./scripts/bisect-xcode.sh
探索が完了すると、Git が最初の異常コミットを示します。ここですぐに問題をクローズせず、そのコミットと親コミットでスクリプトをそれぞれ手動実行し、保存したビルドログと .xcresult を確認してください。コミット差分も読み、観測された動作を本当に説明できる変更なのか、それとも別の失敗を偶然引き起こしただけなのかを確認します。
完了後は git bisect reset を実行し、元のブランチに戻ります。二分探索中に大量の DerivedData が生成された場合は、再検証の完了後にまとめて削除できます。調査中に結果バンドルを早々に削除すると、判定過程の証拠が失われるため避けてください。
不安定なテストと履歴上の断絶に対処する
不安定なテストは多数決で判定する
1 回の実行で結果が安定しない場合は、判定器で 3 回連続実行し、同じ対象エラーが少なくとも 2 回発生した場合にのみ 1 を返すようにできます。3 回の結果が互いに矛盾する場合は 125 を返します。実行時間は長くなりますが、誤って特定したコミットを調査し続けるより低コストです。再実行の前には毎回テストデータをリセットし、前回の状態が次の実行に影響しないようにしてください。
スキップが多すぎる場合は探索範囲を狭める
125 が大量に返されると、Git は一意のコミットを特定できません。よくある原因は、プロジェクト形式、依存関係の管理方法、テスト名が履歴の途中で変更されていることです。その場合は探索範囲を移行後のコミットに限定するか、古いディレクトリ構成に対応する分岐をスクリプトへ追加します。ただし、判定スクリプトがテスト対象のソースコードを自動変更してはいけません。
最終的には、正常コミット、異常コミット、最初の異常コミット、判定スクリプトのバージョン、シミュレータの UDID、Xcode のバージョン、結果バンドルのパスを保存してください。これにより、別のエンジニアが特定結果を再現でき、修正コミットの完成後にも同じ手順をそのまま回帰検証として利用できます。
よくある質問
git bisect の判定スクリプトはどの終了コードを返すべきですか?
正常なコミットは 0、対象の回帰を確認した場合は 1、信頼できる判定ができない場合は 125 を返します。依存関係やシミュレータの障害は通常スキップします。
不安定な Xcode テストにも git bisect を使えますか?
シミュレータ、言語、タイムゾーン、テストデータを固定したうえで、各コミットを複数回実行します。所定の失敗回数に達した場合だけ問題ありと判定します。
次のビルドキューに備えるクラウドMac
2種類のM4構成と5つのノードを比較し、実際の作業サイクルに合わせて日単位、週単位、月単位、または四半期単位でレンタルできます。