Flutterを使ったAndroid・iOSアプリ開発のCDパイプラインを構築する #flutter
はじめに
前回はCIパイプラインについて前半・後半に分けて説明を行いました。
今回はCDパイプラインについて説明します。
CDとは継続的デリバリー(Continuouse Delivery)の略で、アプリケーションを継続的に短いサイクルでリリース可能な状態にするプラクティスのことを指します。
Android / iOSアプリにおけるCDパイプラインとは、ソースコードからアプリを端末にインストールしたりストア申請可能な状態であるapkファイル・ipaファイルをいつでも作成できる状態にする、ということになります。
今回の検証に利用したFlutter環境は以下の通りです。
$ flutter doctor Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel unknown, v1.12.13+hotfix.9, on Mac OS X 10.15.4 19E266, locale en-JP) [✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3) [✓] Xcode - develop for iOS and macOS (Xcode 11.4.1) [✓] Android Studio (version 3.6)
また今回はapk/ipaファイル作成のみについて触れるのですが、実際の開発現場では検証環境や内部テスト向けのアプリと実際の配信するアプリを環境差分で分けて管理する必要があると思います。
Flutterの場合はflavorを独自に定義することで、それらについても対応することができるのですが今回はそこの説明はスコープ外とします。
軽く触れると、Androidは通常のAndroidと同様のやり方でできます。iOSについては命名規則に従ってSchemeを定義すればよいです。 こちらが参考になります。
Flutterを使ったAndroidアプリでapkファイルを作成する
Flutterで構築したアプリケーションからapkファイルを作成する方法は基本的には公式のガイドに従えばよいです。
Android固有の設定
FlutterのAndroid側のプロジェクトにはandroid pluginを内包しているため、基本的には通常のAndroidと同じビルド設定で問題ありません。
公式のガイド通りに以下をbuild.gradleに設定していけばよいでしょう。
- apkの署名設定
- R8によるアプリ圧縮設定
- R8(proguard)による難読化設定
- バージョン番号とバージョンコードの設定
Flutterにはデフォルトでdebug
、profile
、release
の3種類のbuildTypeが用意されていますが、apkを作るときにはreleaseのところに設定を記述しましょう。
ビルドスクリプトを構築する
公式ガイド通りにflutter build apk --release
コマンドを実行すればよいです。
引数でtarget-platformやビルド番号・バージョン番号が指定できるので、32bitと64bitを分けてビルドする場合は指定するようにしましょう。
FlutterはAppBundleにも対応しているようなのでそれを利用しても良いかもしれません。
今回はバージョン番号を引数に受け取って32bit/64bit別々にビルドするシェルスクリプトを作ってみました。
ストア申請のときに32bitと64bitを分けて申請する場合は32bitのバージョン番号より64bitのバージョン番号を大きくしておく必要があるので、シェルの中で+1するようにしました。
build-android-apk.sh
#!/bin/sh set -x set -e # TARGET_APP_VERSION TARGET_APP_VERSION=${1} # TARGET_VERSION_CODE TARGET_VERSION_CODE_ARM=${2} TARGET_VERSION_CODE_ARM64=$(expr ${2} + 1) # Artifacts Path OUTPUT_APK_PATH="./artifacts/android" # cleanを実行 flutter clean # 依存関係を解決 flutter pub get # arm用のapkを作成 flutter build apk --release --target-platform=android-arm \ --build-name=${TARGET_APP_VERSION} --build-number=${TARGET_VERSION_CODE_ARM} # 成果物をコピー cp build/app/outputs/apk/release/app-release.apk \ ${OUTPUT_APK_PATH}/app-release-arm-${TARGET_APP_VERSION}.${TARGET_VERSION_CODE_ARM}.apk # cleanを実行 flutter clean # arm64用のapkを作成 flutter build apk --release --target-platform=android-arm64 \ --build-name=${TARGET_APP_VERSION} --build-number=${TARGET_VERSION_CODE_ARM64} # 成果物をコピー cp build/app/outputs/apk/release/app-release.apk \ ${OUTPUT_APK_PATH}/app-release-arm64-${TARGET_APP_VERSION}.${TARGET_VERSION_CODE_ARM}.apk
実行ログは以下のようになります。
$ ./build-android-apk.sh 1.0.0 100 + set -e + TARGET_APP_VERSION=1.0.0 + TARGET_VERSION_CODE_ARM=100 ++ expr 100 + 1 + TARGET_VERSION_CODE_ARM64=101 + OUTPUT_APK_PATH=./artifacts/android + flutter pub get Running "flutter pub get" in appcenter_sample... 0.3s + flutter clean Cleaning Xcode workspace... 3.0s Deleting build... 105ms Deleting .dart_tool... 1ms + flutter build apk --release --target-platform=android-arm --build-name=1.0.0 --build-number=100 Removed unused resources: Binary resource data reduced from 44KB to 35KB: Removed 20% Running Gradle task 'assembleRelease'... Running Gradle task 'assembleRelease'... Done 39.4s ✓ Built build/app/outputs/apk/release/app-release.apk (5.1MB). + cp build/app/outputs/apk/release/app-release.apk ./artifacts/android/app-release-arm-1.0.0.100.apk + flutter clean Cleaning Xcode workspace... 2.0s Deleting build... 82ms Deleting .dart_tool... 2ms + flutter build apk --release --target-platform=android-arm64 --build-name=1.0.0 --build-number=101 Removed unused resources: Binary resource data reduced from 44KB to 35KB: Removed 20% Running Gradle task 'assembleRelease'... Running Gradle task 'assembleRelease'... Done 35.3s ✓ Built build/app/outputs/apk/release/app-release.apk (5.4MB). + cp build/app/outputs/apk/release/app-release.apk ./artifacts/android/app-release-arm64-1.0.0.101.apk
Flutterを使ったiOSアプリでipaファイルを作成する
Android同様、iOSでもFlutterの公式ドキュメント通りに実行すればよいです。
iOS固有の設定
通常のiOSアプリ開発と同様、ipaファイル作成に必要な証明書とprovisioning profileを準備してビルドを実行するマシンにインストールして おきます。
Android同様、FlutterにはデフォルトでDebug
、Profile
、Release
の3種類のSchemeが定義されていて、ipa作成時にはReleaseのスキームを利用します。Androidと異なって頭文字が大文字な点に注意が必要です。
追加でビルド時に実行したいものがあればReleaseのスキームの設定の中に追記しておくとよいでしょう。
ビルドスクリプトを構築する
公式ガイドを読むと以下を順番に実行せよと書いてあります。
flutter build ios --release
を実行する- XCode上でバージョン番号とビルド番号を設定し、Generic iOS DevicesでArchiveビルドを実行する
- Archiveファイルをビルドしてipaファイルを作成する
要はflutterコマンドを実行した後にxcodebuildコマンドを実行すればよいのです。
Archiveファイルからipaファイルを作成するときにExportOptions.plist
ファイルが必要なので、環境に応じて準備しておきましょう。
iOSもバージョン番号は外から注入できるようにしてスクリプトを組みましょう。
build-ios-ipa.sh
#!/bin/sh set -x set -e # TARGET_APP_VERSION TARGET_APP_VERSION=${1} # TARGET_BUILD_NO TARGET_BUILD_NO=${2} # Artifacts Path OUTPUT_APK_PATH="./artifacts/ios" # cleanを実行 flutter clean # 依存関係を解決 flutter pub get # flutter buildを実行 flutter build ios --release \ --build-name=${TARGET_APP_VERSION} --build-number=${TARGET_BUILD_NO} cd ios # archiveを作成 EXPORT_OPTION_PLIST="ExportOptions.plist" xcodebuild -workspace Runner.xcworkspace -scheme Runner -sdk iphoneos \ -configuration Release archive -archivePath ../build/Runner.xcarchive # ipa ファイルを作成 xcodebuild -allowProvisioningUpdates -exportArchive \ -archivePath ../build/Runner.xcarchive \ -exportOptionsPlist ${EXPORT_OPTION_PLIST} -exportPath ../build/ios-release cd .. # 成果物をコピー cp ./build/ios-release/Runner.ipa ${OUTPUT_APK_PATH}/app-release-${TARGET_APP_VERSION}.${TARGET_BUILD_NO}.ipa
実行ログは途中は省略しますが以下のようになります。
$ ./build-ios-ipa.sh 1.0.0 100 + set -e + TARGET_APP_VERSION=1.0.0 + TARGET_BUILD_NO=100 + OUTPUT_APK_PATH=./artifacts/ios + flutter clean Cleaning Xcode workspace... 2.0s Deleting build... 34ms Deleting .dart_tool... 0ms + flutter build ios --release --build-name=1.0.0 --build-number=100 Building com.takamiii.appcentersample for device (ios-release)... Automatically signing iOS for device deployment using specified development team in Xcode project: xxxxxxxx Running Xcode build... ├─Building Dart code... 13.9s ├─Generating dSYM file... 0.1s ├─Stripping debug symbols... 0.0s ├─Assembling Flutter resources... 0.7s └─Compiling, linking and signing... 9.0s Xcode build done. 26.1s Built /Users/takami228/Documents/flutter-dev/flutter_sample/build/ios/iphoneos/Runner.app. + cd ios + EXPORT_OPTION_PLIST=ExportOptions.plist + xcodebuild -workspace Runner.xcworkspace -scheme Runner -sdk iphoneos -configuration Release archive -archivePath ../build/Runner.xcarchive ... ** ARCHIVE SUCCEEDED ** + xcodebuild -allowProvisioningUpdates -exportArchive -archivePath ../build/Runner.xcarchive -exportOptionsPlist ExportOptions.plist -exportPath ../build/ios-release 2020-04-26 17:29:59.502 xcodebuild[34691:937911] [MT] IDEDistribution: -[IDEDistributionLogging _createLoggingBundleAtPath:]: Created bundle at path '/var/folders/6n/llxh8m296896rymtgjfbkmxw0000gn/T/Runner_2020-04-26_17-29-59.501.xcdistributionlogs'. Exported Runner to: /Users/takami228/Documents/flutter-dev/flutter_sample/build/ios-release ** EXPORT SUCCEEDED ** + cd .. + cp ./build/ios-release/Runner.ipa ./artifacts/ios/app-release-1.0.0.100.ipa
クラッシュレポートやFirebase Crashlyticsでクラッシュを解析するためにビルド時のdSYMファイルが必要になるケースがあるので、忘れないようにここでアップロードしたり退避させるのがよいでしょう。
CD環境と統合する
あとはbuild-android-apk.sh
および build-ios-ipa.sh
を署名情報や証明書がインストールされたマシンやCI環境でtagプッシュ契機で実行するようにすればCDパイプラインの完成です。
下の図はGitLab CIでの実行の流れを表した図になります。
GitLab CIでは以下のように.gitlab-ci.yml
を定義すればよいでしょう。
... release: stagie: build tags: - release-ci-runner only: - tags script: - ./build-android-apk.sh ${TARGET_APP_VERSION} ${TARGET_VERSION_CODE} - ./build-ios-ipa.sh ${TARGET_APP_VERSION} ${TARGET_BUILD_NO}
まとめ
CI編と合わせてapk、ipaの作成を自動化するCDパイプラインを構築する例を説明しました。
CDパイプラインを自動化しておくことでリリース作業のハードルが下がり、リリース作業の属人化を防ぎ、結果としてリリース頻度を増やすことができます。
リリース頻度を増やすことはサービスの競争力を強化する大きなポイントとなるので、ぜひ参考にしてみてください。
またCIパイプラインと同様に、CDパイプラインも一度作って終わりではなく、アプリケーションを提供し続ける限り同様にメンテナンスが必要です。
Flutterはまだまだ変化が激しい技術領域なので、これから破壊的な変更が発生する可能性は0ではないので、前のめりで追従していく気持ちで頑張ってついていきましょう。
2021/7/2 追記
ipaを作成する flutter build ipa
コマンドがflutter 2.0から利用できます。xcodebuildコマンドが省略できるのでぜひ利用してみてください。
Flutterを使ったAndoird・iOSアプリ開発のCIパイプラインを構築する(後半) #flutter
はじめに
前半ではFlutterのCIパイプラインで実行する内容について説明しました。
後半では実際の開発におけるパイプラインの使い分けについて説明します。
CIパイプラインが開発のボトルネックに!?
CIパイプラインは重厚にすればするほど変更に対しての影響が早期に分かり、ソースの安定性を保つことができる一方で、開発が進んでテストケースが増えてくるとそれに比例してビルド時間も長くなってしまいます。
特にFlutterはAndroidとiOS両方のレポジトリを1つで管理するプロジェクト構成になるため、前半で説明したCIを毎回実行しているとあっという間にビルド時間が30分を超えてしまいます。
こうなってしまうとせっかくバグの早期発見やデグレ防止のために導入したCIパイプラインが足かせとなってしまい、機能追加・修正プロセスが重くなり、結果として開発のアジリティを下げてしまいます。
開発者がビルドに待てる時間は体感的にだいたい5分〜15分程度だと言われているので、定期的に実行時間を計測してビルドパイプラインを見直す必要があります。
ビルド時間を短縮する方法としては
- ビルドの実行内容は固定し、ビルド環境を強化する
- ビルドの実行内容は固定し、実行方法を見直す(ジョブ並列化、キャッシュ活用など)
- ビルドの実行内容を見直す
の3つが考えられますが、今回は3番目について考えてみます。
3つのビルドパイプラインに分割する
前半で紹介した内容を並べると以下のようになります。
- Flutterレイヤ(flutter-ci.sh)
- Androidレイヤ (android-ci.sh)
- iOSレイヤ (ios-ci.sh)
- Androidの結合・E2Eテストレイヤ(android-e2e-test.sh)
- iOSの結合・E2Eテストレイヤ(ios-e2e-test.sh)
さて、毎回のPull Request / Merge Requestで本当に毎回重厚なE2Eテストは実施する必要があるでしょうか。もちろんそれを確認できるに越したことはないですが、網羅的なE2Eテストは1OSに対しても10分以上かかってしまうためここを省略するように工夫します。
E2Eテストを全くやらなくしてもよいのですが、アプリが起動しなくなるような破壊的な変更がないかは即座に検知したいため、網羅的なE2Eテストの代わりにアプリの起動と主要導線に限った簡易的なE2Eテストをsmoke-test
として定義します。網羅的なE2Eテストは数時間置き、あるいは日時で最新のmasterからチェックアウトして実行するようにします。
つまり、以下の3つのビルドパイプラインを定義してそれぞれ適切なタイミングで実行するようにします。
- PR / MRを出す前のコードPush時に実行されるブランチビルドパイプライン
- PR / MRの発行・修正時に本当にマージして大丈夫かチェックするPR/MRパイプライン
- 現在のmasterに対して定期的にE2Eテストを実行するデイリーE2Eテストパイプライン
それぞれ説明していきます。
1. ブランチビルドパイプライン
ブランチビルドパイプラインは毎回のPushで実行される一番小さな単位のCIなので、静的解析とユニットテストのみを実行するようにします。10分程度で終わるとよいですね。
2. PR/MRパイプライン
ここではブランチビルドの内容に加えて簡易的なE2Eテストを実行します。
E2Eテストも網羅的にすべて実行すると時間がかかってしまうため、アプリの起動と主要導線に限った簡易的なE2Eテストを用意し、それを実行するようにして時間を短縮します。通常の機能開発時では最低限アプリが起動することが確認できればよいでしょう。
3. デイリーE2Eテストパイプライン
2のPR/MRパイプラインでは時間や計算リソース的な制約で簡易的な起動確認までしかチェックできていないため、定期的にmasterからチェックアウトして網羅的なE2Eテストを実行させ、既存の機能が壊れていないかを確認します。こうすることで仮にマージ時にバグを作り込んでしまったとしても、定期的に異常に気づくことができます。
この定期的なE2Eテストでスクリーンショットを撮るようにして、簡単なビュワーと連携させて日時での変更を視覚的に追ったり、簡易的なスナップショットテストみたいなことをやってみてもよいでしょう。
各ブランチビルドパイプラインで実行する内容
各ブランチビルドパイプラインと実行する内容の対応は以下の表になります。
実行シェル | 1.ブランチビルドパイプライン | 2.PR/MRビルドパイプライン | 3.デイリーE2Eテストパイプライン |
---|---|---|---|
flutter-ci.sh | ○ | ○* | ○* |
android-ci.sh | ○ | ○* | ○* |
iOS-ci.sh | ○ | ○* | ○* |
android-smoke-test.sh | - | ○ | - |
ios-smoke-test.sh | - | ○ | - |
android-e2e-test.sh | - | - | ○ |
ios-e2e-test.sh | - | - | ○ |
*マークを付けましたが、PR/MRビルドパイプラインはブランチPush時に各CIが実行済みのためsmoke-testのみの実行でもよいでしょう。これについてはGitHubやGitLab等実行するレポジトリによって挙動が異なるため省略可能かどうかは実際に試してみるとよいです。
各ビルドパイプラインを.gitlab-ci.yml
に定義した例が以下になります。
... branchBuild: stage: build tags: - ci-runner except: - merge_requests script - ./flutter-ci.sh - ./android-ci.sh - ./ios-ci.sh mergeRequestBuild: stage: build tags: - ci-runner only: - merge_requests script - ./flutter-ci.sh //省略可 - ./android-ci.sh //省略可 - ./ios-ci.sh //省略可 - ./android-smoke-test.sh - ./ios-smoke-test.sh dailyE2ETest: stage: build tags: ci-runner only: - schedules script - ./flutter-ci.sh //省略可 - ./android-ci.sh //省略可 - ./ios-ci.sh //省略可 - ./android-e2e-test.sh - ./ios-e2e-test.sh
dailyE2ETestはGitLabのCI/CD設定のスケジュールジョブで数時間置きor日時で実行するように設定すればよいです。
まとめ
前半と後半の2つに分けてFlutterのCIパイプラインについてまとめました。
説明の中ではCIパイプラインで実行する中身に関してのみ説明したので、Circle CI / CodeMagic / Bitrise / Travis CI / GitLab CI / Jenkinsなどなど、様々なCIプラットフォームで利用可能な内容だと思います。
CIパイプラインは一度作って終わりではなく、アプリケーションをメンテナンスし続ける限りアプリケーション同様のメンテナンスが必要です。定期的に実行時間を計測し、実行内容を見直さないとアプリケーションと同様にあっという間に負債化してしまうので注意しましょう。
そんなCIパイプラインをメンテナンスするビルドおじさんにぜひ相応のお賃金をください(おい)。
次はFlutterを使ったCDパイプラインについてまとめようと思います。
Flutterを使ったAndroid・iOSアプリ開発のCIパイプラインを構築する(前半) #flutter
はじめに
アプリケーションを継続的に安全に機能追加していくプラクティスとして継続的インテグレーション(CI:Continuous Integration)があります。
コードの追加、すなわちPushやPull Request / Merge Request契機でCIを実行し、その変更が既存機能を破壊していないことを確認してからマージするような仕組みにして、対象のブランチを常にクリーンに保つことでアプリケーションが常に安定して動くことを保証します。
今回はFlutterを使ったAndroidアプリ・iOSアプリケーションにおけるCIパイプラインを構築した例を紹介します。
FlutterのCIパイプラインで実行すること
一般的な CIパイプラインでやることは以下のようなものがあります。
Flutterを使ったAndroid / iOSアプリ開発では、ネイティブコードの部分を考慮して以下の4つのレイヤのCIパイプラインを実行すればよいでしょう。
それぞれ順番に見ていきます。
1. Flutterレイヤ(Dart)のCIパイプライン
FlutterレイヤではDart部分のCIパイプラインを見ていきます。Flutterではflutterコマンドの中で静的解析やユニットテストのコマンドが用意されているのでそれを実行すればよいでしょう。
静的解析
静的解析については、DartのLintがflutterコマンド経由で利用することができます。プロジェクトのrootにanalysis_options.yaml
を定義すると、そのルールに従ったLintを実行してくれます。
analysis_options.yaml
についてはflutterのレポジトリにもサンプルが用意されているのでまずはこちらを参考にするとよいでしょう。
flutter/analysis_options.yaml at master · flutter/flutter · GitHub
LintのルールはDartの公式で一覧が公開されています。
実行はflutterコマンドを実行するだけです。
$ flutter analyze Analyzing flutter_app... No issues found! (ran in 10.9s)
ユニットテスト
Dart層のユニットテストは dartの単体テストとWidgetテストの2つがあり、いずれもflutterコマンドで実行ができます。
$ flutter test
00:09 +3: All tests passed!
ユニットテストやWidgetについては公式のガイドが参考になります。
ユニットテストのカバレッジレポートも出すことができます。testコマンドに--coverage
オプションをつけて実行すると、 coverage/lcov.info
ファイルが出力されます。これをlcov
コマンドのgenhtml
でhtmlを出力することができます。
$ flutter test --coverage 00:10 +3: All tests passed! Collecting coverage information... 19ms $ genhtml -o coverage coverage/lcov.info Reading data file coverage/lcov.info Resolved relative source file path "lib/counter.dart" with CWD to "/Users/takami228/git/flutter_app/lib/counter.dart". Found 2 entries. Found common filename prefix "/Users/takami228/git/flutter_app" Writing .css and .png files. Generating output. Processing file lib/main.dart Processing file lib/counter.dart Writing directory view page. Overall coverage rate: lines......: 96.2% (25 of 26 lines) functions..: no data found
htmlファイルを開くと、以下のようなC0カバレッジのレポートを見ることができます。
2. Androidレイヤ (Kotlin/Java)のCIパイプライン
Flutterを使ったAndriodレイヤのパイプラインは基本的には通常のAndroidアプリと同様のことを実行すればよいでしょう。
メインのソースはFlutterで記述し、ネイティブレイヤのモジュールをKotlin/Javaで記述する、というケースが多いと思うのでそのモジュールに対して静的解析・単体テストを実行すれば十分だと思います。
Flutterプロジェクトのrootからandroid
フォルダに入れば通常のAndroidプロジェクトと同様に扱えるので、graldeのタスクで静的解析や単体テストを実行するように定義すればよいです。
# 静的解析を実行 $ ./graldew :app:lint # ユニットテストを実行 $ ./graldew :app:test
3. iOSレイヤ (Swift/Objective-C)のCIパイプライン
iOSレイヤもAndroidと同様に、Swift/Objectiv-Cで記述したモジュールに対しての静的解析とユニットテストを実行すればよいでしょう。
ここでは静的解析には swiftlint
、ユニットテストにはXCTestを実行するコマンドを例にあげます。XCTestを実行可能なビルドスキームを定義しておきましょう。
# 静的解析を実行 $ swiftlint lint --strict # ユニットテストを実行 $ xcodebuild test -scheme Runner-Test
4. 結合・E2Eテストレイヤ(Android / iOS)のCIパイプライン
最後は結合テスト・E2Eテストです。FlutterにはFlutter driverというE2Eテストのフレームワークが用意されています。実際にアプリを動かして、UI操作をコードで制御し、画面遷移や描画内容が正しいことを確認することができます。AppiumはまだFlutterには対応中のようなので、UIテストにはFlutter driverを利用するのがよいでしょう。
An introduction to integration testing - Flutter
flutter drive
コマンドでテスト対象のターゲットを指定することでE2Eテストが実行できます。
$ flutter drive --target=test_driver/app.dart Using device Android SDK built for x86. Starting application: test_driver/app.dart Installing build/app/outputs/apk/app.apk... 3.0s Running Gradle task 'assembleDebug'... Running Gradle task 'assembleDebug'... Done 27.2s ✓ Built build/app/outputs/apk/debug/app-debug.apk. Installing build/app/outputs/apk/app.apk... 2.7s .... [info ] FlutterDriver: Connecting to Flutter application at http://127.0.0.1:63515/lShiCMdeCec=/ [trace] FlutterDriver: Isolate found with number: 3216252190998139 [trace] FlutterDriver: Isolate is paused at start. [trace] FlutterDriver: Attempting to resume isolate [trace] FlutterDriver: Waiting for service extension [info ] FlutterDriver: Connected to Flutter application. 00:02 +0: Counter App starts at 0 00:03 +1: Counter App increments the counter 00:03 +2: Counter App (tearDownAll) 00:03 +2: All tests passed! Stopping application instance.
flutter driverでは動作環境が複数ある場合は一番上位のもの上でしか実行されないため、実行対象を指定したい場合は-d
オプションでdeviceIdを指定します。
$ flutter devices 2 connected devices: Android SDK built for x86 • emulator-5554 • android-x86 • Android 10 (API 29) (emulator) iPhone 11 Pro Max • DB3DD7DC-1574-41D3-B739-B53E281F003B • ios • com.apple.CoreSimulator.SimRuntime.iOS-13-4 (simulator) $ flutter drive --target=test_driver/app.dart -d DB3DD7DC-1574-41D3-B739-B53E281F003B Starting application: test_driver/app.dart Running Xcode build... ├─Assembling Flutter resources... 24.5s └─Compiling, linking and signing... 19.8s Xcode build done. 63.0s flutter: Observatory listening on http://127.0.0.1:64370/ZIVy-qOa5uM=/ 00:00 +0: Counter App (setUpAll) [info ] FlutterDriver: Connecting to Flutter application at http://127.0.0.1:64370/ZIVy-qOa5uM=/ [trace] FlutterDriver: Isolate found with number: 1868789005249195 [trace] FlutterDriver: Isolate is paused at start. [trace] FlutterDriver: Attempting to resume isolate [trace] FlutterDriver: Waiting for service extension [info ] FlutterDriver: Connected to Flutter application. 00:02 +0: Counter App starts at 0 00:02 +1: Counter App increments the counter 00:02 +2: Counter App (tearDownAll) 00:02 +2: All tests passed! Stopping application instance.
まとめ:FlutterのCIパイプラインを構築する
以上の4つのレイヤで実行するコマンド類をシェルスクリプトにまとめて、順番に実行するようにすればFlutterのCIパイプラインは完成です。 実際にCIパイプラインを組む場合はビルド実行に冪等性をもたせる必要があるため、依存関係解決やcleanの実行も忘れずに行うようにしましょう。
flutter-ci.sh
#!/bin/sh set -x set -e # cleanを実行する flutter clean # 依存関係を解決する flutter pub get # 静的解析を実行する flutter analyze # ユニットテストを実行する flutter test --coverage
android-ci.sh
#!/bin/sh set -x set -e # フォルダを移動する cd android # cleanを実行 ./graldew :app:clean # 静的解析を実行 ./graldew :app:lint # ユニットテストを実行 ./graldew :app:test # フォルダを移動する cd ..
ios-ci.sh
#!/bin/sh set -x set -e # フォルダを移動する cd ios # cleanを実行 xcodebuild clean -scheme Runner-Test rm -rf ~/Library/Developer/Xcode/DerivedData/ # 依存関係を解決 pod install # 静的解析を実行 swiftlint lint --strict # ユニットテストを実行 xcodebuild test -scheme Runner-Test # フォルダを移動する cd ..
android-e2e-test.sh
#!/bin/sh set -x set -e # AndroidのdeviceIdを取得 ANDROID_DEVICE_ID=`flutter devices | grep "android" | cut -f2 -d "•" | tr -d " "` # AndroidのE2Eテストを実行する flutter driver --target=test_driver/app.dart -d ${ANDROID_DEVICE_ID}
ios-e2e-test.sh
#!/bin/sh set -x set -e # iOSのdeviceIdを取得 IOS_DEVICE_ID=`flutter devices | grep "ios" | cut -f2 -d "•" | tr -d " "` # iOSのE2Eテストを実行する flutter driver --target=test_driver/app.dart -d ${IOS_DEVICE_ID}
あとはこれらのシェルスクリプトを各種CIツール上で実行するように設定すればよいでしょう。
それについては長くなってきたので後半の記事で説明します。