⚙️

XcodeCloud上でのテスト実行時間を20%削減した話

2024/10/15に公開

XcodeCloudのテスト実行時間を20%削減することができたので、当時の状況や対応策についてまとめてます。

前提

課題

  • 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を対処することが一番即効性が効くため、以下の方向性を考えてみます

  1. Save artifactsをスキップできないか
  2. 成果物の容量を減らせばCI実行時間が減るのではないか

1. Save artifactsをスキップできないか

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を用いてローカルでテストを実行し検証します。
(xctestproductsbuild-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を利用している方はぜひ参考にしてみてください。

dely Tech Blog

Discussion