Xcode
iOS
Swift
Bitrise

Bitrise とかの CI 上だけ一部のテストをスキップさせる方法

例えばの話、API 通信するテストを CI 上ではスキップさせたい。

もちろんそもそもの話、実際の API 通信はアプリ側で保証すべき項目ではなく、基本ローカルで完結するモックとかでテストすればいいですが、たまには手元で実際の通信の確認をすぐにしたい、なのでそのためのテストコードがあればとても便利な場面も多々あります。マウスのワンクリックですぐに結果が確認できますので。

しかしそのテストコードをそのまま上げると CI 上では不都合な場合もよくあります。例えばサーバが IP アドレスで弾いたり、そうでなくても実際のサーバと CI との接続が遅くて予想外のエラーになってしまったり。それはアプリの問題でもないのに CI が通らないので PR がマージできなかったりするのでとても辛い。なのでこれらのテストを CI 上だけスキップさせたい。

Xcode 8 から、Swift のビルド時フラグによるコードの切り替えは SWIFT_ACTIVE_COMPILATION_CONDITIONS(Xcode 上表示名 Active Compilation Conditions)で制御するようになりました。Build Configuration ごとにこれを変えられるので、Debug ビルドに DEBUG というフラグがデフォルトで入っており、これにより #if DEBUG を書いておけばこの部分のコードは Debug ビルドにだけ有効になります。なので例えば BUILT_ON_CI という「CI 上でのビルドだよ」のフラグがあって、#if !BUILT_ON_CI を書いておけば、その部分のコードは CI 環境でスキップできるようになるはずです。問題はどうやってこのフラグを CI でビルドするときだけ立てるかです。

ここでよくある方法は一つ新しい Build Configuration と Scheme を切って、その Build Configuration に BUILT_ON_CI のフラグを立てることです。そうすれば CI 上ではこの Scheme をテストするように作れば OK です。問題は筆者の場合はただでさえ Debug ビルドと Release ビルドに加えて、バックエンド環境に応じて Dev 環境、Stg 環境と本番環境がありますので、すでに 2×3=6 通りの Build Configuration があるので、これ以上増やすとビルド時フラグの管理が非常に大変になるのでもう増やしたくないのです。ではどうすればいいのか。
スクリーンショット 2018-10-12 18.57.39.png

CI 上でのテストは基本 Terminal で xcodebuild test コマンド叩くテストです。このコマンドは Scheme やカバレージ出力など、様々な指定を引数で渡せるのですが、残念ながらここで追加の SWIFT_ACTIVE_COMPILATION_CONDITIONS を渡す引数がないのです。そして直接 VARIABLE=FOO のように、強制的に環境変数 VARIABLEFOO で代入することも可能ですが、ここで無理やり SWIFT_ACTIVE_COMPILATION_CONDITIONS=BUILT_ON_CI を渡してしまうと Build Configuration に設定されているフラグが全て無視され、間違ったバイナリーが生成されてしまいます。

ところで、環境変数を渡せるということは、逆に言ってしまえば SWIFT_ACTIVE_COMPILATION_CONDITIONS に更に別の環境変数を渡してしまえば、その環境変数を設定することによってコマンドラインで必要なフラグを渡せるようになるということです。なのでここでは Xcode のプロジェクト設定の Build Settings に行って、更に User Defined として EXTRA_SWIFT_ACTIVE_COMPILATION_CONDITIONS という変数を追加し、SWIFT_ACTIVE_COMPILATION_CONDITIONS の最後にこの変数を設定します。
スクリーンショット 2018-10-12 19.29.25.png

画像の中の + を押せば、Add User-Defined Setting の選択肢が出てくるので、これを押せば最後の User-Defined のセクションに新しい項目が追加されます;ここで項目の名前だけ EXTRA_SWIFT_ACTIVE_COMPILATION_CONDITIONS にし、値は全て空のままで大丈夫です。そして元々の Active Compilation Conditions の中に、各 Build Configuration の設定に $EXTRA_SWIFT_ACTIVE_COMPILATION_CONDITIONS を追加すればいいです。

これで CI 側だけ特殊のフラグを入れる準備が整えたので、あとは CI 側の設定だけです。ここは筆者が使っている Bitrise を例に説明します。まずは該当の Workflow の Xcode Test の項目を選択します。
スクリーンショット 2018-10-12 19.39.05.png

次にその項目の最後に Debug というセクションがあるので、それをクリックすると展開され、中には更に Additional options for `xcodebuild build test` call の項目があります。ここにさっき追加した変数を代入する EXTRA_SWIFT_ACTIVE_COMPILATION_CONDITIONS=BUILT_ON_CI を追加すれば完成です。他の CI も設定する場所は違うと思いますが、同じ理屈で設定できると思います。
スクリーンショット 2018-10-12 19.40.58.png

以上の設定によって、#if !BUILT_ON_CI の部分のコードは、CI 環境だけスキップするようにできました。めでたしめでたし。