前書き
私が担当している iOS プロジェクトでは、最近 Xcode Cloud を導入し、テストを中心に運用を試し始めました。
最初に実行してみたところ、想定外だったのが テスト実行時間 です。
1 回の実行に 約 49 分 かかっており、正直なところ「これはさすがに使い続けるのは厳しそうだな……」という印象でした。
Xcode Cloud には無料枠(25 時間 / 月)があるため、このペースだとすぐに上限に達してしまいます。
それ以上に、結果が返ってくるまでに時間がかかりすぎて、日常の開発フローに組み込むのは現実的ではありませんでした。
そこで、
- なぜここまで時間がかかっているのか
- 本当に Xcode Cloud は遅いのか
を一度整理し、原因を調べた上で、いくつかの対応を行ってみることにしました。
この記事では、Xcode Cloud の特性を踏まえつつ、テスト時間が長くなっていた原因と、それに対して行った改善内容をまとめています。
同じように Xcode Cloud の導入を検討している方や、「入れてみたけど遅くて困っている」という方の参考になれば幸いです。
なぜ Xcode Cloud でテスト時間が長くなっていたのか
原因を切り分けるため、まず Xcode Cloud がどのような環境で動くか を整理しました。
Xcode Cloud には、ローカル環境や自前 CI と異なる特徴があります。
- 毎回クリーンな環境で実行される
- 依存ツールや成果物は基本的にキャッシュされない
- Xcode の Build Phase に含まれる処理は毎回必ず実行される
この前提を踏まえてプロジェクトの構成を見直したところ、
テスト時間が長くなっていた大きな要因は Build Phase に集約されていた処理 でした。
当時の構成
- SwiftLint を Mint で管理
- Xcode の Build Phase で SwiftLint を実行
- LicensePlist などのツールも Build Phase に含まれている
この構成はローカルではあまり問題になりませんが、Xcode Cloud では以下のような挙動になります。
- Mint が 毎回ツールをソースからビルド
- SwiftLint 実行前にそのビルド時間が発生
- テスト本体とは直接関係ない処理に時間を取られる
結果として、
「テストが遅い」のではなく、「テスト前の準備が重い」
という状態になっていました。
方針:CI から重い処理を外す
原因が見えたので、次に方針を決めました。
今回重視したのは次の 3 点です。
- Xcode Cloud 上では極力シンプルにする
- コード品質のチェック自体は弱めない
- チーム全体で無理なく運用できる構成にする
その結果、
- CI(Xcode Cloud) → ビルドとテストに集中
- ローカル開発 → コミット時に品質を担保
という役割分担にすることにしました。
具体的にやったこと
1. SwiftLint を Build Phase から外す
まず、SwiftLint の実行を Build Phase から削除しました。
Build Phase に置くと、
- ローカルビルド
- CI ビルド
- テスト実行
のすべてで実行されてしまうため、Xcode Cloud ではオーバーヘッドが大きくなります。
2. SwiftLint を Homebrew 管理に変更
SwiftLint は Mint ではなく、Homebrew でインストールするように変更しました。
- Mint:ソースビルドが発生
- Homebrew:ビルド済みバイナリを利用
これにより、ツール準備にかかる時間を大きく削減できます。
3. Git pre-commit フックで SwiftLint を実行
SwiftLint の実行タイミングを ビルド時 → コミット時 に変更しました。
- コミット時に変更ファイルのみをチェック
-
--strictモードで警告もエラー扱い - 問題があればコミットをブロック
これにより、
- 新規コードの品質は担保される
- CI では SwiftLint を実行しない
という構成になります。
この構成のメリット
従来(Mint + Build Phase)
-
CI の速度
遅い(毎回ツールをソースからビルド) -
チェック対象
全ファイル -
新規負債の防止
△ 警告があってもビルド・テストは通る -
チーム共有
× 各自がローカルで設定する必要がある
今回(Homebrew + pre-commit)
-
CI の速度
速い(ビルド済みバイナリを利用) -
チェック対象
変更ファイルのみ -
新規負債の防止
◎ 警告があるとコミットがブロックされる -
チーム共有
◎ リポジトリに含めて全員で共有できる
全体構成
[ローカル開発]
│
└── コミット時 ───→ SwiftLint(strict モード)
変更ファイルのみ、警告でコミット中止
実装例:SwiftLint を pre-commit で実行する
ここでは、今回採用した 「Homebrew + pre-commit」 の実装例を紹介します。
狙いはシンプルで、CI(Xcode Cloud)ではビルドとテストに集中させ、コード品質チェックはローカルのコミット時に担保することです。
ポイントは次の 3 つです。
- 変更ファイルのみ を対象にする(既存の警告に引きずられない)
-
--strictを付けて 警告もエラー扱い にする(新規負債を増やさない) -
.githooks/をリポジトリに含めて チーム全員に配布 する(属人化させない)
※ 本記事では SwiftLint に絞って紹介しますが、同様の考え方で LicensePlist などの Build Phase 処理も整理しました。
(例:毎回生成する運用をやめ、事前生成してリポジトリ管理する/CI では生成しない など)
1. pre-commit フックを作成
プロジェクトルートに .githooks/pre-commit を作成します。
このフックは ステージングされた Swift ファイルだけ を対象に SwiftLint を実行し、警告があればコミットを止めます。
#!/bin/bash
set -e
echo "Running SwiftLint on staged Swift files..."
# ステージングされた .swift ファイルを取得(追加・変更・コピーのみ)
SWIFT_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.swift$' || true)
# Swift ファイルがない場合は何もせず終了
if [ -z "$SWIFT_FILES" ]; then
echo "No Swift files staged for commit."
exit 0
fi
# SwiftLint がインストールされているか確認
if ! command -v swiftlint >/dev/null 2>&1; then
echo "Error: SwiftLint not found. Please install via 'brew install swiftlint'"
exit 1
fi
# strict モードで実行(警告もエラー扱い)
echo "$SWIFT_FILES" | xargs swiftlint lint --strict
echo "SwiftLint check passed!"
これにより、
- 変更ファイルのみをチェック
- 警告もエラー扱い
- 問題があればコミットが止まる
という挙動になります。
詳細なセットアップ手順(Makefile や .swiftlint.yml の設定など)は、別記事で紹介する予定です。
まとめ
Xcode Cloud のテスト時間が長くなっていた原因は、
テストそのものではなく Build Phase に集約された重い処理 でした。
Xcode Cloud は毎回クリーンな環境で実行されるため、
- Build Phase に何を置くか
- どこでコード品質を担保するか
を整理しないと、簡単に実行時間が膨らみます。
今回の対応では、
- SwiftLint を Build Phase から外す
- CI ではビルドとテストに集中させる
- 品質チェックは pre-commit で担保する
という役割分担にすることで、
テスト時間を 約 49 分 → 約 19 分 まで短縮できました。
Xcode Cloud は「入れれば速くなる仕組み」ではありませんが、
クリーン環境前提で構成を見直すことで、十分実用的な CI になります。
これから Xcode Cloud を導入する場合は、
「CI で何をやらないか」 を先に考えてみるのがおすすめです。