こんにちは。モバイルアプリの開発を担当をしているzigeninです。
ANDPADのiOSアプリのCI/CDワークフローを紹介します。 ベタなツールにベタなワークフローですので、他のiOSアプリにも適用できると思います。 一例として参考になればと思います。
目次
事前情報
ビルドに使用しているツール
ツール | 用途 |
---|---|
Bitrise | 特定のブランチにpushした時にワークフローを実行 |
Firebase App Distribution | アプリの配布 |
fastlane | matchでアプリの署名用のcertificates & provisioning profileを取得 |
pod | 依存ライブラリを入手 |
iOSアプリのビルドScheme
Scheme | 用途 |
---|---|
andpad | Release版アプリをビルドする。 |
andpadUITests | Release版アプリで、安定したUIテストのみ実行する用。 |
andpad_develop | Develop版アプリをビルドする。Unitテスト実行用でもある。 |
andpadUITestsDevelop | Develop版アプリで、安定したUIテストのみ実行する。 |
andpadNightlyUITests | 全UIテストを実行する。 |
ANDPADではビルドSchemeによって、普段の開発に使うDevelop版アプリと、Storeに公開する用のRelease版アプリを分けています。 Develop版アプリは開発用アプリで、developサーバと接続しています。 Release版アプリはStoreに公開するアプリと同一で、本番サーバに接続しています。
UIテストのSchemeを3つ分けていますが、状況によってUIテストの実行の仕方を変えるためです。 UIテストをCI/CDのワークフローに組み込むための肝です。 詳細は、後述のNightly UI Testのワークフローで説明します。
ブランチ戦略
大体、GitHub Flowです。
ブランチ | 用途 |
---|---|
master | Storeに公開したアプリのコードの置き場所 |
develop | 開発本線。最新のコードの置き場所 |
feature/* | 機能開発用ブランチ |
運用図は、ワークフローの全体像と一緒に示します。
ワークフローの全体像
GitHubのブランチをベースに、ワークフローを示します。
主要なワークフローは3つです。
- 内部配信のワークフロー
- 用途
- Develop版アプリの自動テストを実施 + 社内にアプリを配布する
- トリガー
- ブランチをdevelopにマージした時
- やること
- ビルドの準備をする
- コードのチェックアウト
- 証明書をfastlane matchで入手
- 依存ライブラリをpodで入手
- Unitテストを実行する
- アプリをビルドする
- ビルドしたアプリをFirebase App Distributionに配布する
- 安定しているUIテストを実行する*1
- ビルドの後始末をする
- テスト結果を保存する
- Slackにビルド結果を通知する
- ビルドの準備をする
- 用途
- Store配信ワークフロー
- 用途:Release版アプリの自動テストを実施 + Storeにアップロードする
- トリガー:developブランチをmasterブランチにマージした時*2
- やること(内部配信フローと大体同じ。違いは太字で強調)
- ビルドの準備をする
- Unitテストを実行する
- アプリをビルドする
- ビルドしたアプリをStoreに配布する
- 安定しているUIテストを実行する
- ビルドの後始末をする
- Nightly UI Testワークフロー
- 用途:追加したばかりのUIテストが安定していることを確認する
- トリガー:毎日定刻に、developブランチに対して実行
- やること(内部配信フローと大体同じ。違いは太字で強調)
- ビルドの準備をする
- Develop版アプリで 全てのUIテストを実行する
- ビルドの後始末をする
以降、この3つのワークフローとそれらを構成するユーテリティワークフローを、Bitriseでどのように設定しているかを紹介します。
ユーティリティワークフロー
先程示した3つのワークフローには、共通部分が多いです。 共通部分は、4つのBitriseのユーテリティワークフローとしてまとめています*3。
- ビルドの準備(_BeforeCommonWorkflow)
- Unitテスト実行&アプリのビルド(_BuildAndUnitTest)
- UIテスト実行(_UITest)
- ビルドの後始末(_AfterCommonWorkflow)
ワークフロー名の先頭に"_"を付けると、ユーティリティワークフローとなります。ユーティリティワークフローにすると、他のワークフローの部品として扱われ、独立して実行することができなくなります*4。
4つのユーテリティワークフローの設定の詳細を紹介します。
ビルドの準備(_BeforeCommonWorkflow)
- Activate SSH Key
- Git Clone Repository
- Bitrise.io Cache:Pull
- fastlane match
- Develop版アプリの署名用のcertificates & provisioning profileを取得するStep*5
- "fastlane lane"に"match development"を設定
- fastlaneに必要なFASTLANE_USER, MATCH_PASSWORD, FASTLANE_PASSWORDは、BitriseのSecretsに設定
- fastlane match
- リリース版アプリの署名用のcertificates & provisioning profileを取得するStep*6
- "fastlane lane"に"match appstore"を設定
- Run CocoaPods install
このワークフローは、ワークフローごとにパラメータや処理に違いはありません。
Unitテスト実行&アプリのビルド(_BuildAndUnitTest)
- Set Xcode Project Build Number
- Bitriseのビルド番号をアプリのBundle Versionとして設定するStep
- 配布したアプリと対応するBitriseのビルドが分かりやすくなり、問題が起きた時の追跡などの時に便利です
- "Info.plist file path"に"$BITRISE_SOURCE_DIR/$INFO_PLIST_PATH"を設定
- AndpadではDevelop版とRelease版でInfo.plistが違うので、フローごとに切り替える必要がある
- Xcode Test for iOS
- Unit Testを実行するStep
- Schemeとして、"andpad_develop"を設定
- Simulator Deviceには、"iPhone Xs Max"を設定
- Xcode Archive & Export for iOS
- アプリをビルドするStep
- "Rebuild from bitcode"を"no"に設定
- export methodが"development"の時にbitcodeを生成するかのオプション
- 內部配信の場合、bitcodeをリビルドする意味があまりない*7 かつ ビルド時間短縮のために、Offにしている
- "Select method for export"に"$BITRISE_EXPORT_METHOD"を設定
- "Configuration name"に"$BUILD_CONFIG"を設定
- "iCloud container environment"に"$ICLOUD_CONTAINER_ENVIRONMEN"を設定
- IPA info
- ビルドしたアプリの情報をBitriseの環境変数に設定してくれるStep
- ここで設定される$IOS_IPA_PACKAGE_NAME(値はBundle Identifier)をビルド結果をSlackに通知する時に使う
UIテスト実行(_UITest)
- Xcode Build for testing for iOS
- UIテスト用のアプリをビルドするStep
- "Configuration name"に"$BUILD_CONFIG"を設定
- "Scheme name"に"$BITRISE_UITEST_SCHEME"を設定
- iOS Device Testing
- UIテストをFirebase Test Labで実行するStep
- "Test devices"に"iphonexs,12.3,ja,portrait"を設定(iPadや複数OSバージョンをTestするようにする予定です))
ビルドの後始末(_AfterCommonWorkflow)
- Bitrise.io Cache: Push
- Deploy to Bitrise.io - Apps, Logs, Artifacts*8
- Slackで結果を通知しているのでEmail通知は無効化
- "Notify: User Roles"を"none"に設定
- Slackで結果を通知しているのでEmail通知は無効化
- Send a Slack message
- ワークフローごとに通知先のチャンネルを切り替えられるよう設定
- "Slack API token"に$SLACK_API_TOKENを設定($SLACK_API_TOKENの値はBitriseのSecretsで設定)
- "Target Slack channel"に$SLACK_TARGET_CHANNELを設定
- Web Hook URLを使わないのは、Web Hook URLだとチャンネルの数だけSecrets変数が設定が必要なため
- SlackのメッセージにBundle IDを含めるように設定
- "A list of fields to be displayed in a table inside the attachment"に次の内容を記述。
App|${BITRISE_APP_TITLE}
BundleID|${IOS_IPA_PACKAGE_NAME}
Branch|${BITRISE_GIT_BRANCH}
Workflow|${BITRISE_TRIGGERED_WORKFLOW_ID}- BundleIDを含めるのは、ビルドされたのがDevelop版なのかRelease版なのかを区別するため
- ワークフローごとに通知先のチャンネルを切り替えられるよう設定
内部配信ワークフロー
Steps
ワークフローの大まかな構成を示します。 - BeforeCommonWorkflow - BuildAndUnitTest - 內部配信ワークフロー本体のStep - UITest - AfterCommonWorkflow
長く見えますが、ほぼ前述のユーテリティワークフローで構成されています。 このワークフロー本体のStepは、Firebase App Distributionのみです。
- Firebase App Distribution*9
- ビルドしたアプリをFirebase App Distributionに配布するStep
- "Firebase Token"には、Firebaseの認証トークンを設定
- "Release Notes"には以下を設定
$GIT_CLONE_COMMIT_MESSAGE_SUBJECT
$GIT_CLONE_COMMIT_MESSAGE_BODY
- "Test Groups"には、Firebase App DistributionのTester Groupを設定
- "Firebase App ID"には、Firebase上のアプリのIDを設定
環境変数の設定
本体のStepが少ない分、環境変数の設定が重要になるので、示しておきます。
環境変数 | 用途 | 値 | 使っているワークフロー |
---|---|---|---|
BITRISE_PROJECT_PATH | xcworkspaceファイルの場所を指定 | andpad.xcworkspace | BuildAndUnitTest, UITest |
BITRISE_SCHEME | アプリのビルドSchemeを指定 | andpad_develop | _BuildAndUnitTest |
BITRISE_EXPORT_METHOD | アプリのビルド時のExport Methodを指定 | development | _BuildAndUnitTest |
SLACK_TARGET_CHANNEL | ビルド結果の通知先のSlackチャンネル名 | #andpad-app-build | _AfterCommonWorkflow |
ICLOUD_CONTAINER_ENVIRONMENT | アプリビルド時のiCloud Containerを指定 | Development | _BuildAndUnitTest |
BITRISE_UITEST_SCHEME | UIテストアプリのビルドSchemeを指定 | andpadUITestsDevelop | _UITest |
BUILD_CONFIG | アプリのビルドconfigを指定 | Debug | BuildAndUnitTest, UITest |
INFO_PLIST_PATH | Info.plistの場所を指定 | andpad-develop-Info.plist | _AfterCommonWorkflow |
Store配信ワークフロー
Steps
ワークフローの全体構成を示します。 - BeforeCommonWorkflow - BuildAndUnitTest - Store配信ワークフロー本体のStep - UITest - AfterCommonWorkflow
このワークフロー本体のStepは、Deploy to iTunes Connectのみです。
- Deploy to iTunes Connect
- ビルドしたアプリをStoreに配布するStep
- "Apple ID"に、Storeにアップロードする権限のあるユーザIDを設定
- "Password"には、Storeにアップロードする権限のあるユーザのパスワードを設定
- "Team ID"には、iTunes ConnectのアプリのTeam IDを設定
- "App Bundle ID"には、アプリのBundle IDを設定。
- _BuildAndUnitTestのStep "IPA Info"のおかげで$IOS_IPA_PACKAGE_NAMEが使えるので、それを設定
環境変数の設定
本体のStepが少ない分、環境変数の設定が重要になるので、示しておきます。
環境変数 | 用途 | 値 | 使っているワークフロー |
---|---|---|---|
BITRISE_PROJECT_PATH | xcworkspaceファイルの場所を指定 | andpad.xcworkspace | _BuildAndUnitTest, _UITest |
BITRISE_SCHEME | アプリのビルドSchemeを指定 | andpad | _BuildAndUnitTest |
BITRISE_EXPORT_METHOD | アプリのビルド時のExport Methodを指定 | app-store | _BuildAndUnitTest |
SLACK_TARGET_CHANNEL | ビルド結果の通知先のSlackチャンネル名 | #andpad-app-build | _AfterCommonWorkflow |
ICLOUD_CONTAINER_ENVIRONMENT | アプリビルド時のiCloud Containerを指定 | Production | _BuildAndUnitTest |
BITRISE_UITEST_SCHEME | UIテストアプリのビルドSchemeを指定 | andpadUITests | _UITest |
BUILD_CONFIG | アプリのビルドconfigを指定 | Release | _BuildAndUnitTest, _UITest |
INFO_PLIST_PATH | Info.plistの場所を指定 | andpad/Info.plist | _AfterCommonWorkflow |
Nightly UI Testワークフロー
Steps
ワークフローの全体構成を示します。 - BeforeCommonWorkflow - UITest - _AfterCommonWorkflow
このワークフローは、本体のStepはなしです。
環境変数の設定
環境変数の設定は內部配信のワークフローとほぼ同じです。違いだけ示します。
環境変数 | 用途 | 値 | 使っているワークフロー |
---|---|---|---|
BITRISE_UITEST_SCHEME | UIテストアプリのビルドSchemeを指定 | andpadNightlyUITests | _UITest |
SLACK_TARGET_CHANNEL | ビルド結果の通知先のSlackチャンネル名 | #team-qa-mobile-cicd | _AfterCommonWorkflow |
IOS_IPA_PACKAGE_NAME | Slackで通知のアプリのBundle ID | jp.reformpad.ios.develop | _AfterCommonWorkflow |
Slackの通知先のチャンネルを、弊社のアプリのCI/CDを整備する活動のチャンネルにしています。 定期実行のテスト結果を、配信用のチャンネルに通知すると、配信の情報が埋もれるためです。
補足
このワークフローは、追加したばかりのUIテストの安定性を確認するために、毎日定時に実行しています。
UIテストはUnitテストに比べると不安定です*10。 そのため、最初から內部配信ワークフローやStore配信ワークフローに組み込んでしまうと、ビルドエラーの頻度が上がります。 そうなってしまうと、UIテストのせいで却って開発効率が落ちてしまいます。
そこで、毎日定時にUIテストを実行し、連続で7回成功したら、配信ワークフローに組み込む運用にしています。 それであれば、配信ワークフローで実行するUIテストを安定したものだけに限定できます*11。 全UIテストを実行するSchemeを"andpadNightlyUITests"と、配信ワークフロー用のUI TestのSchemeを"andpadUITests", "andpadUITestsDevelop"に分けることで、この運用を実現しています。
参考情報
参考書籍
本ワークフローを構築にするにあたって、以下の書籍の「9章 CI/CD」を参考にしています*12。
ここで紹介したワークフローは、書籍で紹介されているワークフローを大分デチューンしています。 もっとしっかりしたワークフローを組みたいという方は、この本がおすすめです。テストやCI/CDのそもそも論も学べます。
Bitriseの設定のソースコード
--- format_version: '8' default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git project_type: ios trigger_map: - push_branch: develop workflow: InternalRelease - push_branch: master workflow: Release - push_branch: test/* workflow: RunAllUITests workflows: InternalRelease: steps: - firebase-app-distribution@0.5.0: inputs: - firebase_token: "$FIREBASE_TOKEN" - release_notes: |- $GIT_CLONE_COMMIT_MESSAGE_SUBJECT $GIT_CLONE_COMMIT_MESSAGE_BODY - groups: Andpad - app: "$FIREBASE_APP_ID" before_run: - _BeforeCommonWorkflow - _BuildAndUnitTest after_run: - _UITest - _AfterCommonWorkflow Release: steps: - deploy-to-itunesconnect-deliver@2.17.0: inputs: - itunescon_user: "<アプリのアップロードする権限を持つユーザID>" - team_id: <アプリのTeamID> - bundle_id: "$IOS_IPA_PACKAGE_NAME" - password: "<アプリのアップロードする権限を持つユーザのパスワード>" envs: - opts: is_expand: false BITRISE_SCHEME: andpad - opts: is_expand: false BITRISE_EXPORT_METHOD: app-store - opts: is_expand: false APP_BUNDLE_ID: jp.reformpad.ios.production - opts: is_expand: false FASTLANE_MATCH_TYPE: appstore - opts: is_expand: false ICLOUD_CONTAINER_ENVIRONMENT: Production - opts: is_expand: false BITRISE_UITEST_SCHEME: andpadUITests - opts: is_expand: false BUILD_CONFIG: Release - opts: is_expand: false INFO_PLIST_PATH: andpad/Info.plist before_run: - _BeforeCommonWorkflow - _BuildAndUnitTest after_run: - _UITest - _AfterCommonWorkflow _BeforeCommonWorkflow: steps: - activate-ssh-key@4.0.5: run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}' - git-clone@4.0.17: {} - cache-pull@2.1.3: {} - fastlane@2.7.0: inputs: - lane: match development title: fastlane match - fastlane@2.7.0: inputs: - lane: match appstore title: fastlane match - cocoapods-install@1.10.0: {} _AfterCommonWorkflow: steps: - cache-push@2.2.3: {} - deploy-to-bitrise-io@1.9.5: inputs: - notify_user_groups: none - slack@3.1.3: inputs: - channel: "$SLACK_TARGET_CHANNEL" - fields: | App|${BITRISE_APP_TITLE} BundleID|${IOS_IPA_PACKAGE_NAME} Branch|${BITRISE_GIT_BRANCH} Workflow|${BITRISE_TRIGGERED_WORKFLOW_ID} - api_token: "$SLACK_API_TOKEN" before_run: [] _BuildAndUnitTest: steps: - set-xcode-build-number@1.0.9: inputs: - plist_path: "$BITRISE_SOURCE_DIR/$INFO_PLIST_PATH" - xcode-test@2.4.3: inputs: - generate_code_coverage_files: 'yes' - scheme: "andpad_develop" - simulator_device: iPhone Xs Max - xcode-archive@2.7.1: inputs: - compile_bitcode: 'no' - export_method: "$BITRISE_EXPORT_METHOD" - configuration: "$BUILD_CONFIG" - icloud_container_environment: "$ICLOUD_CONTAINER_ENVIRONMENT" - ipa-info@1.1.0: {} before_run: [] after_run: [] _UITest: steps: - xcode-build-for-test@0.4.0: inputs: - configuration: "$BUILD_CONFIG" - scheme: "$BITRISE_UITEST_SCHEME" - virtual-device-testing-for-ios@0.9.10: inputs: - test_devices: iphonexs,12.3,ja,portrait before_run: [] after_run: [] NightlyUITests: envs: - opts: is_expand: false SLACK_TARGET_CHANNEL: "#team-qa-mobile-cicd" - opts: is_expand: false BITRISE_UITEST_SCHEME: andpadNightlyUITests - opts: is_expand: false IOS_IPA_PACKAGE_NAME: jp.reformpad.ios.develop before_run: - _BeforeCommonWorkflow - _UITest after_run: - _AfterCommonWorkflow app: envs: - opts: is_expand: false BITRISE_PROJECT_PATH: andpad.xcworkspace - BITRISE_SCHEME: andpad_develop opts: is_expand: false - opts: is_expand: false BITRISE_EXPORT_METHOD: development - opts: is_expand: false SLACK_TARGET_CHANNEL: "#andpad-app-build" - opts: is_expand: false ICLOUD_CONTAINER_ENVIRONMENT: Development - opts: is_expand: false BITRISE_UITEST_SCHEME: andpadUITestsDevelop - opts: is_expand: false BUILD_CONFIG: Debug - opts: is_expand: false INFO_PLIST_PATH: andpad-develop-Info.plist - opts: is_expand: false FIREBASE_APP_ID: "<FirebaseのアプリのID>" meta: bitrise.io: machine_type: elite
*1:アプリ配布後にUI Testを実行させているのは、アプリ外の要因でUIテストが失敗することがあるためです。テストが失敗した時、ログを見て外部要因であれば、アプリの再配信はしない運用にしています。
*2:masterにマージする前に、Develop版アプリを使って手動テストを実施しています。それで問題なければ、masterにマージしています。
*3:詳細は https://devcenter.bitrise.io/bitrise-cli/workflows/#utility-workflows を参照
*4:これがユーテリティワークフローの良い所です。手動でビルドを開始する時などにワークフローリストに表示されなくなるので見通しが良くなります。
*5:fastlane matchを使っているのは、Bitrise導入前からfastlane matchでcertificates & provisioning profileを管理していたのを踏襲したからです。
*6:Develop版とリリース版アプリの両方のcertificates & provisioning profileが必要になるケースがある
*7:bitcodeは、Storeにアップロードした時に、Apple側で最適化をするためのもの。
*8:iOSテスト全書の事後ワークフローに含まれていませんが、個人的には含めておいた方が良いと思います。Unitテストの結果がBitriseのTest Reportで見れる、ビルドログやビルドしたアプリがArtifactに保存される、BitriseのSlackやメールの通知からアプリをインストールできるなどの利点があります。
*9:設定の意味の詳細は、 https://firebase.google.com/docs/app-distribution/ios/distribute-cli?hl=ja を参照。
*10:通信が発生する、OSバージョン、Deviveの置かれた環境に依存するためです。弊社固有の事情ですが、最近まで弊社にモバイルアプリのUIテストのノウハウがなかったことも原因です
*11:なお、OSのバージョンアップ等で、配信ワークフローに組み込んだUIテストが不安定になってしまった場合は、配信ワークフローから外す運用です
*12:弊社のテスト面の技術顧問の平田さんが記述しています