24/7 twenty-four seven

iOS/OS X application programing topics.

FolioのiOSチームで利用しているFastfileとBitriseワークフロー

FolioのiOSチームではさまざまなタスクをそこそこ高度に自動化していると思うので、(そのまま別のプロジェクトで使いまわせるほどポータブルではないけど)参考にしてもらえる部分はけっこうありそうと思うので公開リポジトリに置いてみました。

github.com

簡単に解説します。

Fastfile

lane :snapshot_test

Folioアプリのユニットテストはいわゆる一般的なロジックテストに加えてスクリーンショットを用いたスナップショットテストがあります。

GitHub - uber/ios-snapshot-test-case: Snapshot view unit tests for iOS

目的は修正によって意図しない影響が起こっていないことを検証するためと、現状の画面の一覧をGitHubで変更管理したいからです(これについては詳細を後述)。

(ボタンを追加したら関係ないはずのラベルのテキストが溢れた、とか。あるいはエラー系の画面など通常の開発で確認を忘れたがちな画面についても安心できるとか)

これはよほど再現が難しい一部の画面をのぞいて、現在はかなりの画面をカバーできているので、安心できる反面、UIの変更でテストを一緒に修正しなければならない手間もあります。

なので、意図的なUIの変更はあまり何も考えずに関係する画面のスクリーンショットを撮り直して、git add -Aしてコミットする、というワークフローにしています。

(そうするとPRで確認できるし、変更は全部Gitに残るわけだからちょっと間違えても問題ない)

(それでもテストケースが増えたことでそこそこ大変になりつつあるので改善の余地あり)

このタスクは、スナップショットテストを必要なデバイスぶん記録モードで実行してローカルのスクリーンショットを上書きする、というものです。

このFastfileに定義されているタスクでは珍しくCIではなく手元で実行するタスクです。

lane :add_release_tag

アプリがリリースされたらそのタイミングでタグを作成する、というタスクですが、ちょうど良いトリガーのタイミングがなくて今は利用されていません。

lane : update_dependencies

導入しているライブラリの更新があったら自動的にCarthage.resolvedかPodfile.lockを更新してPRを出します。

ライブラリに更新があったら次のようなPRが夜の間に自動的に作成されます。基本的にテストが通っていればサッと確認してマージするだけ、Breaking Changeがある場合は必要に応じてこのPRにコミットを追加するか別PRでマニュアル作業で対応します。

f:id:KishikawaKatsumi:20190619155451p:plain

目的はライブラリの更新を溜めずに小さい単位にまとめてコントロール可能にすることです。

そのためにまず自動化したことで更新に気づくことができます。

そのままマージするだけのこともけっこうあるので負担の軽減ができます。

そうでない場合は対処が必要ですが、放置していると同じPRが無限に作成され続けるので、気持ちが悪いからどこかで対応しようという圧力になります。

このタスクはCIで平日の深夜に毎日実行されています。

https://github.com/folio-sec/Fastfile/blob/master/bitrise.yml#L323-L337

lane : update_license_list

LicensePlistを実行して、利用しているライブラリのライセンス情報を更新します。

このタスクもCIで平日の深夜に毎日実行され、自動的にPRを作成します。基本的にただマージするだけです。

https://github.com/folio-sec/Fastfile/blob/master/bitrise.yml#L338-L348

lane : update_tools

アプリで使用する以外のツール(主にFastlaneかCocoaPods)を自動的にアップデートします。 CIで平日の深夜に毎日実行され、自動的にPRを作成します。ただマージするだけです。それなら直接masterにコミットしてもいいのですが、PRを経由したほうが変更がわかりやすいのでそうしています。

https://github.com/folio-sec/Fastfile/blob/master/bitrise.yml#L349-L359

lane : sync_bitrise_yml

https://github.com/folio-sec/Fastfile/blob/master/Fastfile#L91-L104

Bitrise.ymlをリポジトリにダウンロードします。変更があったら自動的にPRを作成します。

BitriseはWebインターフェースがとてもよくできていますが、一般的なCIサービスと同様に設定ファイルベースでも動作します。

というか、設定ファイルベースで動かしてタスクはGitで管理できる方が良いのですが、Bitriseの設定ファイル(Bitrise.yml)はWebインターフェースに比べると格段に難しく、またBitrise.ymlを優先で使うためには追加のセットアップが必要なので、それをやるのはいろいろ面倒になるのでやめました。

代わりに発想を逆転させて、Webインターフェースで設定した内容をリポジトリに定期的にダウンロードするようにしました。そのためのタスクがこれです。

平日の深夜にCIで実行されるので、最大で1日の遅延がありますが、CIのワークフローは今や頻繁に変更するものでもないので、変更があったタイミングでそれがわかって、追跡可能であるという要件はこれで完全に満たせていて、かつ明らかにこちらの仕組みの方が簡単なのでこのようにしています。

https://github.com/folio-sec/Fastfile/blob/master/bitrise.yml#L252-L262

lane : screenshots_preview_generator

https://github.com/folio-sec/Fastfile/blob/master/Fastfile#L119-L123

スナップショットテストの実行で得られたスクリーンショットをMarkdown形式に整形してGitHubで簡単に確認できるようにします。

lane : refresh_dsyms

https://github.com/folio-sec/Fastfile/blob/master/Fastfile#L125-L144

AppStore Connectからデバッグシンボルをダウンロードして、Firebase Crashlyticsにアップロードします。

平日深夜にCIで実行されます。

https://github.com/folio-sec/Fastfile/blob/master/bitrise.yml#L220-L234

lane : image_assets_tests, lane: folio_tests, lane: redux_tests, lane: notification_service_tests

https://github.com/folio-sec/Fastfile/blob/master/Fastfile#L146-L181

ユニットテストを実行します。実行時間の関係で、いくつかのテストは関係のあるファイルに変更があったときだけ実行できるように、テストの種類の応じてタスクを分割しています。

毎回のPRやmasterにマージされたタイミングで実行されます。

lane : folio_nightly_tests

毎回のPRで実行されるテストはすべてではないので、念のため、平日深夜にできるだけ多くのテストを実行するためのタスクです。

lane : upload_build_cache, lane: download_build_cache, lane: renew_build_cache

https://github.com/folio-sec/Fastfile/blob/master/Fastfile#L278-L303

Bitriseのキャッシュはブランチごと、PRでキャッシュは更新されないという仕様があるので、ライブラリの構成を変更するPRの場合、そのPRにコミットを追加する場合は基本的にライブラリのフルビルドが動いてしまう問題がありました。

現在のプロジェクトではCarthageとCocoaPodsでライブラリを管理していて、CocoaPodsについてもこちらの記事で紹介したようにビルド済みライブラリとして取り扱っているので、独自のキャッシュの仕組みを作ったのがこのタスクです。

Carthageのビルド済みライブラリと、CocoaPodsのビルド済みライブラリをtar.gzにアーカイブして独立したGitHubリポジトリのReleasesにアップロードします。

利用はReleasesからダウンロードして展開するだけです。

キャッシュのキーにはCarthage.resolvedとPodfile.lockのハッシュ値を使っています。ライブラリ構成に変更があったらこの値が変わるので新しいキャッシュが保存されます。

この仕組みにすることで、ライブラリの再ビルドは最初の1回だけ行われて、あとはキャッシュを利用できるようになり、PRのテストにかかる時間が大幅に短縮されました。

副次的なメリットとして、lane: download_cacheのタスクを手元で実行すると、CIでビルド済みのライブラリをダウンロードして利用できるようになりました。

このおかげで手元でライブラリをインストール、ビルドすることはほぼ不要になり、ライブラリ構成に変更があるようなPRをレビューする場合でもブランチを変更してlane: download_cacheを実行するだけでよくなり、待ち時間が減ってとても快適になりました。

(ネットワーク速度によるけど、オフィスでやる分には圧倒的にビルドするよりもダウンロードする方が速い)

この仕組みについては別の記事で詳しく書きたいと思います。

https://github.com/folio-sec/Fastfile/blob/master/bitrise.yml#L44-L60

Bitrise.yml

workflow: test

https://github.com/folio-sec/Fastfile/blob/master/bitrise.yml#L14-L25

ユニットテスト(スナップショットテストを含む)を実行します。

PRとPRのマージ(=masterへのPush)に実行されます。

次のリリースのためのブランチとしてv2.16.0のようなブランチができることがあるのでvで始まるブランチ名もmasterと同様に扱います。

workflow: deliver, workflow: deliver-external, workflow: deliver-internal

リリースビルドもしくはAd-HocビルドをしてAppStore ConnectかFabric Betaにアップロードします。

_release/*_testflight/*_fabric-beta/*という特定のPrefixで始まるブランチからPRが出されると動作する仕組みです。

Bitriseはこのようにブランチのネーミングルールでトリガーするワークフローをマッピングすると便利、ということに気づくと飛躍的に便利度が上がります。

リリースの際にはビルド番号をインクリメントするという退屈だけど正確に行わなければ失敗するタスクがあるので、私たちのチームではChatBotによりそれを自動化しています。

ChatBotはバージョンをインクリメントしてPRを出すところまでを担当し、PRが出るとCIでリリースビルドが作成される、という風にタスクのチェーンが繋がります。

ChatBotはこちら。

github.com