CIを導入してみた

FilteredHatebuにCIを導入してみました。

CI上での設定も必要なので、これだけで導入できるわけではありませんが、CI導入のための変更はこんな感じです。エミュレータが必要なEspressoでのテストがこけないよう、テストコードを修正した変更も含まれています。

そもそもこのアプリをpublicなリポジトリでやっているのは、実はCIを導入してみたかったからという理由がありました。

最初はとりあえずCIを導入してみようというだけで作業を始めたのですが、結局よく目にするTravisCI、CircleCI、Werckerの3つを試してみました。

CIを使うにあたっては、publicなリポジトリで運用する、無料でできる範囲でやる、あまりにややこしいことはやらない(できればそのCI単体で完結させる、ただしSlackへの通知は除く)という3点を念頭にやってみました。

各CIサービスを使ってみた感想

  • TravisCIは秘匿情報を比較的安全に運用しやすい
  • CircleCIはテストレポートを確認しやすい
  • Werckerは圧倒的にビルドが早い

publicなリポジトリでの運用という観点で、秘匿情報(署名ファイルなど)の取扱、テストレポートの確認手段、ビルド時間の長さ、導入難易度について私の主観でまとめてみるとこんな感じです。

CIサービス 秘匿情報 テストレポート ビルド時間 導入難易度
TravisCI × ×
CircleCI × ×
Wercker ×
秘匿情報については後述。

テストレポートは、./gradlew connectedAndroidTestなどを実行した後に生成されるHTML形式のレポートを確認できるかということです。CircleCIはCIサービス上でHTMLファイルにアクセスすることが可能です。Werckerは直接見れませんがファイルをダウンロードできます。Travisはそもそも見れません。どこか外部のストレージサービスでも使って、ファイルをアップロードするしかないようです。

ビルド時間はWerckerの圧勝です。TravisCIもCircleCIもSDKのアップデートに時間を食われるため、一度のビルドに18分くらいかかります。Werckerビルド環境をDockerで構築してしまえるので、SDKのアップデートが発生しません。初回を除けばだいたい6分くらいで済んでいます。圧倒的な早さです。

導入難易度については、TravisCIとCircleCIはどちらもあまり大差はないと思います。ドキュメントも日本語情報も充実しています。設定もYAMLで記述するだけですから、CIサービスごとの方言はあるもののそうハマるものでもありません(CircleCIは微妙にドキュメントが現状に追いついていない部分があって惑わされたりもしましたけど)。

一方でWerckerはドキュメントが他2つと比較して充実しているわけではありません(他2つと比較すると分かりにくいと感じました)。さらにDockerの知識が必要にもなるので、導入までにかかった時間は一番長かったです。(逆にDockerの知識があって、Androidの環境を構築するのが簡単にできてしまう人であれば、Wercker使うのが一番ラクかもしれません)

3つのサービスを使ってみましたが、それぞれ一長一短で、このサービスが最強といえないもどかしさがありました。ただprivateリポジトリで使うなら、Werckerが一番いい気がします(早さと設定の自由度が魅力)。

秘匿情報の取扱

困ったのは、署名ファイル(release.keystore)をCIでどう扱うのかという問題です。

どのCIサービスでも同じですが、release.keystoreなどの秘匿情報をCIでも扱えるようにするためには、そのファイルをリポジトリに含めるか、もしくはインターネット経由でアクセスできるどこかに別途公開しておくかしかありません。

前者の方法はpublicなリポジトリでは使えません。privateリポジトリであれば問題ないのでしょうが、私のケースでは採用できませんでした。

かといって後者のどこか別の場所に置くという方法も、適切な場所が思いつきませんでした。誰でもアクセスできるような場所に置くことはできませんし、適切なアクセス制限がかけられる置き場所となると、選択肢はそう多くはないと思います。

どのCIサービスでも、privateな情報についてはCI側で環境変数を利用することができます。publicなリポジトリで運用しても、パスワードなどが見えないように配慮することができます。TravisとWerckerは環境変数にセキュアな項目にする設定があるので、その点いくらか安心です。ただしCircleCIは、publicなプロジェクトではパスワードとか漏れたら困る情報を環境変数に入れるなと注意書きがありました。

しかし環境変数で扱えるのは文字列です。署名に使うrelease.keystoreというバイナリファイルを環境変数で扱うことはできません。

私が試した3つのサービスで、唯一秘匿情報ファイルの取扱がCIサービス単体で解決できるのはTravisだけです。解決できると言っても、暗号化してリポジトリに含めるという方法ですけれど。

他のサービスでは別途ストレージサービスを利用するなどして、そこからファイルをとってくるという手法を使わなければなりません。

エミュレータを使うテストは鬼門

今回CIを導入したプロジェクトでは、Espressoを使ったテストを行っています。手元の実機では安定していても、CIで実行すると失敗ばかりで非常に困りました。

Espressoテストレコーダーは万能ではない

ローカルの実機だと問題ないのにCI上だとエラーが起こりました。例えばこんなコード。

-        ViewInteraction appCompatTextView = onView(
-                allOf(withId(android.R.id.text1), withText("test.com/"),
-                      childAtPosition(
-                              allOf(withClassName(
-                                      is("com.android.internal.app.AlertController$RecycleListView")),
-                                    withParent(withClassName(is("android.widget.FrameLayout")))),
-                              0),
-                      isDisplayed()));
-        appCompatTextView.perform(click());
+        onData(anything())
+                .atPosition(0)
+                .perform(click());

onData~にコードを書き換えるとCI上でも問題なく動くようになりました。Espressoテストレコーダーは便利ですが、こればかりに頼る訳にはいかないという教訓です。

DataBindingを使っていると起こるエラー

java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation at android.databinding.DataBindingUtil.<clinit>(DataBindingUtil.java:31)という謎のエラーが発生しました。

最初に導入していたTravisでぶち当たった問題で、Android Studioでテストを実行する分には問題ないのに、TravisとWerckerではコケました。Werckerは利用するDockerイメージによるものだとは思います。CircleCIは特に問題になりませんでした。

https://code.google.com/p/android/issues/detail?id=182715

どういうエラーなんだかよく把握していないのですが、DataBindingを使っていて、かつテスト対象のActivityでDataBindingを使っていると発生するようです。

#31に回避策が書いてあります。

エミュレータの起動を待つ処理

CIでエミュレータを使ったテストを行うためには、エミュレータが起動するのを待たなければなりません。TravisCIにもCircleCIにも、エミュレータの起動を待つためのスクリプトが用意されています。Werckerを使う場合はそんな便利なコマンドは用意されていないので、自分でシェルスクリプトを書く必要があります。

しかしこれがまあ安定しない。

試行錯誤の結果、最終的にはkeyeventを送る前に10秒スリープ処理を挟むことで安定しました。(ネットで探し回っていたらsleep 30としている情報にあたり、それで安定するようになった。30秒は長すぎる気がして5秒にしたら時折コケるので10秒にした、という経緯があります)

CIを導入してみて

最初はデプロイまでやってやろうとはじめましたが、途中で方針転換しました。初めてやるなら簡単なところからやるべきだなと思います。

とりあえず最初はprivateなリポジトリ、publicでやるならせめてdebugビルドのみでの運用がおすすめです。publicかつリリースビルドもやって、デプロイまでやろうなんていうのは大変だと思います。

そしてやるにしてもユニットテストまでにしておいたほうが無難ではないかなと思います。UIテスト(Espressoなどエミュレータを使ってやるテスト)までやろうとすると、これまた大変です。(大変でした)

最終的に3つのCIサービスを設定してみましたが、それぞれのサービスごとの特色が見えて面白かったです。

Amazonのほしいものリストを公開しています。仕事で欲しいもの、単なる趣味としてほしいもの、リフレッシュのために欲しいものなどを登録しています。 寄贈いただけると泣いて喜びます。大したお礼はできませんが、よりよい情報発信へのモチベーションに繋がりますので、ご検討いただければ幸いです。