つばくろぐ @takamii228

知は力なり

Flutterを使ったAndoird・iOSアプリ開発のCIパイプラインを構築する(後半) #flutter

はじめに

前半ではFlutterのCIパイプラインで実行する内容について説明しました。

takamii.hatenablog.com

後半では実際の開発におけるパイプラインの使い分けについて説明します。

CIパイプラインが開発のボトルネックに!?

CIパイプラインは重厚にすればするほど変更に対しての影響が早期に分かり、ソースの安定性を保つことができる一方で、開発が進んでテストケースが増えてくるとそれに比例してビルド時間も長くなってしまいます。

特にFlutterはAndroidiOS両方のレポジトリを1つで管理するプロジェクト構成になるため、前半で説明したCIを毎回実行しているとあっという間にビルド時間が30分を超えてしまいます。

こうなってしまうとせっかくバグの早期発見やデグレ防止のために導入したCIパイプラインが足かせとなってしまい、機能追加・修正プロセスが重くなり、結果として開発のアジリティを下げてしまいます。

開発者がビルドに待てる時間は体感的にだいたい5分〜15分程度だと言われているので、定期的に実行時間を計測してビルドパイプラインを見直す必要があります。

ビルド時間を短縮する方法としては

  1. ビルドの実行内容は固定し、ビルド環境を強化する
  2. ビルドの実行内容は固定し、実行方法を見直す(ジョブ並列化、キャッシュ活用など)
  3. ビルドの実行内容を見直す

の3つが考えられますが、今回は3番目について考えてみます。

3つのビルドパイプラインに分割する

前半で紹介した内容を並べると以下のようになります。

  1. Flutterレイヤ(flutter-ci.sh)
  2. Androidレイヤ (android-ci.sh)
  3. iOSレイヤ (ios-ci.sh)
  4. Androidの結合・E2Eテストレイヤ(android-e2e-test.sh)
  5. iOSの結合・E2Eテストレイヤ(ios-e2e-test.sh)

さて、毎回のPull Request / Merge Requestで本当に毎回重厚なE2Eテストは実施する必要があるでしょうか。もちろんそれを確認できるに越したことはないですが、網羅的なE2Eテストは1OSに対しても10分以上かかってしまうためここを省略するように工夫します。

E2Eテストを全くやらなくしてもよいのですが、アプリが起動しなくなるような破壊的な変更がないかは即座に検知したいため、網羅的なE2Eテストの代わりにアプリの起動と主要導線に限った簡易的なE2Eテストをsmoke-testとして定義します。網羅的なE2Eテストは数時間置き、あるいは日時で最新のmasterからチェックアウトして実行するようにします。

つまり、以下の3つのビルドパイプラインを定義してそれぞれ適切なタイミングで実行するようにします。

  1. PR / MRを出す前のコードPush時に実行されるブランチビルドパイプライン
  2. PR / MRの発行・修正時に本当にマージして大丈夫かチェックするPR/MRパイプライン
  3. 現在のmasterに対して定期的にE2Eテストを実行するデイリーE2Eテストパイプライン

それぞれ説明していきます。

1. ブランチビルドパイプライン

ブランチビルドパイプラインは毎回のPushで実行される一番小さな単位のCIなので、静的解析とユニットテストのみを実行するようにします。10分程度で終わるとよいですね。

f:id:takamii228:20200426142048p:plain

2. PR/MRパイプライン

ここではブランチビルドの内容に加えて簡易的なE2Eテストを実行します。

E2Eテストも網羅的にすべて実行すると時間がかかってしまうため、アプリの起動と主要導線に限った簡易的なE2Eテストを用意し、それを実行するようにして時間を短縮します。通常の機能開発時では最低限アプリが起動することが確認できればよいでしょう。

f:id:takamii228:20200426142229p:plain

3. デイリーE2Eテストパイプライン

2のPR/MRパイプラインでは時間や計算リソース的な制約で簡易的な起動確認までしかチェックできていないため、定期的にmasterからチェックアウトして網羅的なE2Eテストを実行させ、既存の機能が壊れていないかを確認します。こうすることで仮にマージ時にバグを作り込んでしまったとしても、定期的に異常に気づくことができます。

この定期的なE2Eテストでスクリーンショットを撮るようにして、簡単なビュワーと連携させて日時での変更を視覚的に追ったり、簡易的なスナップショットテストみたいなことをやってみてもよいでしょう。

f:id:takamii228:20200426142535p:plain

各ブランチビルドパイプラインで実行する内容

各ブランチビルドパイプラインと実行する内容の対応は以下の表になります。

実行シェル 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日時で実行するように設定すればよいです。

Pipeline schedules | GitLab

まとめ

前半と後半の2つに分けてFlutterのCIパイプラインについてまとめました。

説明の中ではCIパイプラインで実行する中身に関してのみ説明したので、Circle CI / CodeMagic / Bitrise / Travis CI / GitLab CI / Jenkinsなどなど、様々なCIプラットフォームで利用可能な内容だと思います。

CIパイプラインは一度作って終わりではなく、アプリケーションをメンテナンスし続ける限りアプリケーション同様のメンテナンスが必要です。定期的に実行時間を計測し、実行内容を見直さないとアプリケーションと同様にあっという間に負債化してしまうので注意しましょう。

そんなCIパイプラインをメンテナンスするビルドおじさんにぜひ相応のお賃金をください(おい)。

次はFlutterを使ったCDパイプラインについてまとめようと思います。

takamii.hatenablog.com