AndroidのCIに纏わる諸々の問題

Sat Dec 14, 2013

この投稿はAndroid Advent Calendar 2013の14日目の記事です。

昨日、Android Test Casual Talks #1というイベントがありました。 僕もテストについて何か話す予定だったのですが、残念ながら体調不良のため行けませんでした。 それで、ハッシュタグを追って見ていたのですが、現場に入ったときにAndroidにテストを書く文化がなくて驚いたという話がありましたが、今やその状況もだいぶ変わってきていて、会場にいた人の80%の人がJenkinsを導入しているというのはちょっとすごいなと思いました。

さて、そんなJenkinsですが、弊社では運用についていくつかの悩みを抱えています。 その悩みと考えている解決策を公開することによって、意見交換したり、何かの参考になればいいなと思います。

Jenkins、ビルドの設定

弊社のAndroidのJenkinsはawsでここの1スレーブとして動いています。

ビルドにはgradleを使っていて、以下の設定で種類ごとにbuildを行なっています。

buildTypes {
    debug {
        debuggable true
        runProguard false
        ...
    }
    beta {
        debuggable true
        runProguard true
        ...
    }
    release {
        debuggable false
        runProguard true
        ...
    }
}

productFlavors {
    staging {}
    product {}
}

また、gradleはdaemon起動しています。 ライブラリはs3に認証付きでホストしています。署名の情報もそうですが、キーとシークレットはリポジトリに含められないので、Google Driveに置いて開発者に権限を付与してスクリプトで取得するようにしています。

repositories {
    mavenCentral()
    maven {
        url "https://${project.s3Bucket}.s3.amazonaws.com/release"
        credentials {
            username project.s3Key
            password project.s3Secret
        }
    }
}

実際のジョブの内容は

  1. StagingBetaをassembleする
  2. StagingBetaをconnectedInstrumentTestする
  3. Lintを走らせる
  4. ProductBetaをassembleする
  5. DeployGateでProductBetaを配信する
  6. HipChatにビルド結果を通知する

というようになっています。

悩み1. Android Emulatorが不安定

プロセスが突然クラッシュして、続くビルドでemulatorがタイムアウトしてしまうことがあります。

Tests on Full Android on x86 Emulator - 2.3.7 failed: Instrumentation run failed due to 'Process crashed.'
[android] Timed-out after waiting 180 seconds for emulator
Finished: NOT_BUILT

テストが悪くて起こることもあるのですが、確率的になることもあり原因がよく分かっていません。 また、こちらも原因不明なのですが adb -s localhost:47245 shell input keyevent 82 のメッセージを出したまま永遠に返ってこなくなることもあります。 この前は8時間固まっていてジョブが詰まっていました(その後Build-timeout Pluginでタイムアウト設定をするようにしました)。

悩み2. ビルドに時間がかかる

確か最初の頃は一回3分とかで終わっていたと思うのですが、プロジェクトが進んでテストもだんだん増えていって6分くらいになりました。 Jenkinsではandroid-18(4.3)で、手元ではGenymotionの4.3で動作確認をしていたのですが、ある日Android 2系でアプリを起動するとcompatのコードがinjection周りでクラッシュすることに気付きました。 開発するときに手元で2系と4系の両方で確認するのは大変なので、Jenkinsでマトリックスビルドするようにしました。

android-10 && armeabi + android-18 && armeabi-v7a という構成です。 ところがこの構成にした途端に、ビルド時間が40分を超えるようになりました。

{% oembed https://twitter.com/rejasupotaro/status/397977669062500352 %}

そこで構成を android-10 && x86 + android-18 && x86 に変更しました。

それで今36分くらいです。 どちらにせよ長すぎるのでなんとかしたいです。

ビルドを安定させる案

Android Emulator Plugin以外のもの使う場合を考えます。

Robolectricを使う

実行環境がDalvikじゃないので信頼できるのかというところですが、楽天では4ヶ月使ってみてRobolectric由来の問題にあたったことがないとのことです。 この手の問題ですが、たとえばDexFileを弄るとか、Androidの環境に依存するようなコードは、ActiveAndroidのようにRobolectricのためのフォールバックの処理を入れる必要があります。

if (sourcePath != null && !(new File(sourcePath).isDirectory())) {
    DexFile dexfile = new DexFile(sourcePath);
    Enumeration<String> entries = dexfile.entries();

    while (entries.hasMoreElements()) {
        paths.add(entries.nextElement());
    }
}
// Robolectric fallback
else {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    Enumeration<URL> resources = classLoader.getResources("");

    while (resources.hasMoreElements()) {
        String path = resources.nextElement().getFile();
        if (path.contains("bin") || path.contains("classes")) {
            paths.add(path);
        }
    }
}

すべてのライブラリがRobolectric対応しているとも限りませんし、うまく動かなかったら自分で直そうという雰囲気を感じます。

UIのテストをすることになったら実機かEmulatorが必要になるので、Robolectricを使うというのはビルドの安定化というより高速化の方かもしれないと思いました。

Genymotionを使う

Genymotionの開発元が今のところヘッドレスでは動かないと言ってます。

ところで弊社のiOS開発はどうしているかというと、社内のmac miniでXcode 5からの新機能のBotsを使ってCIしています。 なので、Genymotionを使うならawsからmac miniに移す感じになるかなと思います。

実機を使う

Genymotionを使うのとほぼ一緒です。 mac miniに直接実機を繋ぎます。

ついでに会社の使ってない検証機を繋いでおいて常時テストが走るみたいにしておくと便利かもしれません。

ジョブの実行時間の短縮させる案

ジョブを分割する

2つのジョブを続けて実行するのに比べるとマトリックスビルドは明らかにオーバーヘッドが大きい(6分 * 2で12分のはずが35分かかってる)ので、マトリックスビルドをやめて環境ごとにジョブを増やします。 ジョブを二重で管理しないといけなくなるという問題が発生しますが、スクリプトはgitで取ってきたり、ジョブ間の設定の差分を見れるようにするなどすればいいかなと思います。

ビルドを速くする

FacebookのBuckとxctool によると、Buckはmavenの10倍ビルドが速いらしいので、たとえばBuckに移行するとか(今のところ考えていませんが)、あるいはGradleがもっと速くなるようにコミットするとかです。

ただし、全体の実行時間のうちビルド時間が占める割合は小さいので、エミュレータの起動と接続、あるいはテストの高速化にリソースを割いた方が大きな成果が得られると思います。

テストを速くする

性能の高いインスタンスに乗り換える、Robolectricを使う、Genymotionを使うなどの方法があります。 そういえばEspressoってUIテストを並列化しているそうですね:Google Espresso: Android UI のクラウド型高速自動化テスト ビジネスロジックはRobolectricでUIはEspressoで、みたいになるのでしょうか。

まとめ

弊社のCIの悩みと今考えている解決策を書きました。 安定化に関しても高速化に関してもGenymotionを入れるとひとまず解決する気がするので、awsにいたJenkinsをmac miniに移そうかと思います。



  « Previous: Next: »