Infinito Nirone 7

白羽の矢を刺すスタイル

Robolectric を offline mode で動作させる

Robolectric でテストを実行すると、初めの方のテストでなにかを Maven Central からダウンロードしているログが出力されることがあります。これは Robolectric がテスト実行時に必要な依存をダウンロードしているもので、Android SDK のなかのクラスを JVM で実行可能にしている jar が入っています。

ローカルでテストを動かす分には初めてテストを実行するときにしかお目にかかることはありませんが、CI のような環境の場合、CI が走るたびに Robolectric の依存解決が動きます。 また、Robolectric はどの SDK version でテストを動かすかを動的に指定できるので、@Config(sdk = [21]) とか @Config(sdk = [25]) とかが付いているテストケースがあるたびに、必要な jar を適宜取りに行くようになっています。 そうすると、@Config アノテーションの数だけ jar のダウンロード時間が必要になるので、CI だと結構な時間が依存解決に費やされてしまいます。

そこで Robolectric には offline mode というのがあって、あらかじめ jar をローカルに保存しそのパスを教えてあげると、テスト実行時には Maven Central へアクセスせずともテストが実行できるようになっています。 これで時間の節約ができるというわけです。

事前にダウンロードする Gradle スクリプト

medium.com

実はすでにこれを実現するスクリプトが公開されています。

問題点

概ね offline mode の設定と jar の場所の設定はこのスクリプトで問題ありませんが、複数の jar が必要なときにうまく動きません。

上記の記事で書かれたスクリプトの dependencies を次のように書いたとします。

configurations {
    robolectricRuntime
}

dependencies {
    robolectricRuntime "org.robolectric:android-all:4.1.2_r1-robolectric-r1"
    robolectricRuntime "org.robolectric:android-all:4.3_r2-robolectric-r1"
    robolectricRuntime "org.robolectric:android-all:4.4_r1-robolectric-r2"
    robolectricRuntime "org.robolectric:android-all:5.0.2_r3-robolectric-r0"
    robolectricRuntime "org.robolectric:android-all:7.1.0_r7-robolectric-r1"
    robolectricRuntime "org.robolectric:android-all:8.1.0-robolectric-4611349"
}

テストでは ICS, JB, K, L, N, O のそれぞれの android-all について jar が必要な状態です。 複数必要なので dependencies の指定も複数並べることになりますが、robolectricRuntime のどの dependency も GroupId と Artifact が同じで Version のみが異なるため、Gradle はこの列挙された dependencies の中から最新のものしか解決しません。 結果、jar を放り込むディレクトリには O の android-all の jar ファイルしかいないことになります。

どう解決するか

Configuration ごとに dependency の解決が行われることを考えると、必要な android-all の jar ごとに Configuration を作れば良いことになります。

configurations {
    robolectricRuntimeICS
    robolectricRuntimeJB
    robolectricRuntimeK
    robolectricRuntimeL
    robolectricRuntimeN
    robolectricRuntimeO
}

dependencies {
    robolectricRuntimeICS "org.robolectric:android-all:4.1.2_r1-robolectric-r1"
    robolectricRuntimeJB "org.robolectric:android-all:4.3_r2-robolectric-r1"
    robolectricRuntimeK "org.robolectric:android-all:4.4_r1-robolectric-r2"
    robolectricRuntimeL "org.robolectric:android-all:5.0.2_r3-robolectric-r0"
    robolectricRuntimeN "org.robolectric:android-all:7.1.0_r7-robolectric-r1"
    robolectricRuntimeO "org.robolectric:android-all:8.1.0-robolectric-4611349"
}

そして jar を copy するタスクの中身を次のように書き換えます。

rootProject.task(type: Copy, overwrite: true, "downloadRobolectricDependencies") {
    configurations.all { configuration ->
        if (configuration.name.startsWith("robolectricRuntime")) {
            from configuration
        }
    }
    into robolectricDependenciesFolder
}

これで Configuration ごとに別の android-all の jar をダウンロードしてローカルにコピーするまでが実現できました。

あとは ./gradlew downloadRobolectricDependencies とするだけですべての jar がダウンロードできます。