XcodeCloud上でのテスト実行時間を20%削減した話
XcodeCloudのテスト実行時間を20%削減することができたので、当時の状況や対応策についてまとめてます。
前提
- iOSプロジェクトの構成はSwift Package Manager + Cocoapodsで管理(Swift Package Managerを活用したクラシルリワードのiOSアプリ構成)
- CI/CDはXcodeCloudを利用
課題
- CIの実行時間がプロジェクトの規模にしては遅い
- 2024/10時点では1回のテスト実行に24分の時間がかかっていました
- トランクベース開発で細かくPRを作成しているため、早くCIの結果を確認したいものの、待ち時間が長くなり開発フローに支障が出ていました
- e.g. CI完了直前にテストがこけて再修正、軽微な修正だがCI実行完了まで待たないといけないなど
- また、XcodeCloudの実行時間上限も考慮する必要があり、無駄な実行を極力避けたい状況でした
XcodeCloudの実行結果
調査
CI時間を削減するためにまずは何に時間がかかってるかわからないため、ログを見てみます。
実際のCIログ
大きく下記の3つのステップで時間がかかっていそうです。
-
build-for-testing
: 8分 Save artifacts
: 6分-
test-without-building
: 2分
特に普段の開発では見慣れない「Save artifacts
」が時間かかっています。Save artifacts
はXcodeCloudの成果物を保存するためのステップであり、成果物ページからDL可能です。
成果物を全て見る必要がなければこのステップは不要となるので、このステップを修正できるかを検討してみます。
(※build-for-testing
はローカルのプロジェクト構成に依存するため、今回の調査スコープからは対象外としています)
方向性の整理
Save artifacts
を対処することが一番即効性が効くため、以下の方向性を考えてみます
-
Save artifacts
をスキップできないか - 成果物の容量を減らせばCI実行時間が減るのではないか
Save artifacts
をスキップできないか
1. - 処理を差し込める箇所が
post-clone
,pre-xcodebuild
,post-xcodebuild
のみのため、Save artifacts
をスキップすることはできません (Writing custom build scripts) - 同様の質問がforumに上がっているが、現状解決策はなし(Skip the "Save artifacts" step in Test action)
2. 成果物の容量を減らせばCI実行時間が減るのではないか
Save artifacts
に時間がかかってるということは、対象の成果物の容量を減らせばCIの実行時間が短くなると仮説立てました。
まずはテストモジュールを一つにして、CI実行時間・容量に変化があるかを確認します。
テストモジュール一つのログ
テストモジュール一つの成果物ログ
テストモジュール数を減らすだけで、Save artifacts
のCI実行時間、成果物のサイズともに半分ほど小さくなっていることが確認できました。
成果物を減らせばCI時間は削減できそうなのがわかったので、次に何が削除できるかを見ていきます。
成果物からxctestproducts
をDLし、中身を確認すると下記のようなツリー構造になっていることがわかります。
├── Binaries
│ └── 0
│ └── Debug-iphonesimulator
│ ├── AFile.o
│ ├── AFeature.swiftmodule
│ ├── AFeature.xctest
│ ├── AFeature.xctest.dSYM
│ ├── Package_AFeature.bundle
│ ├── B.framework
│ ...
│
├── Info.plist
└── Tests
└── 0
├── Debug-iphonesimulator -> ../../Binaries/0/Debug-iphonesimulator
└── Package.xctestrun
※XcodeCloud(Xcode15以降)上ではxctestproducts
を利用したテスト実行がされています。(リリースノート)
Xcode 15以降で実行されるXcode Cloudテストアクションで、新しい*.xctestproductsテストバンドル形式が使用されるようになりました。これは、システム間でのテストバンドルの転送をより簡単にする、新しいテストプロダクト形式です。CIスクリプトでは、CI_TEST_PRODUCTS_PATH環境変数を使って、テストアクションでどのテストプロダクト形式が生成/使用されるかを判断できます。テストアクションが新しい形式を使用している場合、パスは.xctestproductsで終わります。
テスト実行に必要なファイルのみを残し、xctestproducts
を用いてローカルでテストを実行し検証します。
(xctestproducts
はbuild-for-testing
によってテスト実行可能ファイルとして生成されているため、ローカルPCでもテストが実行できます)
$ xcodebuild test-without-building -destination 'platform=iOS Simulator,name=iPhone 15,OS=17.5' -testProductsPath TestProducts.xctestproducts -derivedDataPath DerivedData -resultBundleVersion 3 -IDEPostProgressNotifications=YES CODE_SIGN_IDENTITY=- AD_HOC_CODE_SIGNING_ALLOWED=YES COMPILER_INDEX_STORE_ENABLE=NO -hideShellScriptEnvironment
.
.
.
Test session results, code coverage, and logs:
/Path/DerivedData/Logs/Test/Test-Package-2024.10.14_15-16-44-+0900.xcresult
** TEST EXECUTE SUCCEEDED **
何度かテスト実行とファイル削除を繰り返し、最終的にはci_post_xcodebuild.sh
で下記のファイル(dSYM
, o
, a
, bundle
)を削除することにしました。
# ci_post_xcodebuild.sh
if [[ $CI_WORKFLOW = "Test" ]]; then
find "$CI_TEST_PRODUCTS_PATH/Binaries/0/Debug-iphonesimulator" \
-maxdepth 1 \
\( -type d -name "*.dSYM" -o \
-type f -name "*.o" -o \
-type d -name "*.bundle" -o \
-type f -name "*.a" \) \
-exec rm -rf {} +
fi
結果
-
Save artifacts
にかかっていた時間が6分半から1分半になりました
- 元々24分かかっていたものが19分前後で終わるようになり、実質20%CI時間の改善が見込めました
まとめ
XcodeCloudのテスト実行で生成されたテスト実行に不要な成果物は、ci_post_xcodebuild.sh
で削除することでCI時間の削減が可能になります。手軽に検証しやすい部分だとは思うのでXcodeCloudを利用している方はぜひ参考にしてみてください。
Discussion