Celeste Engineer

Androidとか自転車とか

Robolectric の依存を事前に解決してテストにかかる時間を短縮する

起こったこと

Robolectric を使って JVM 上でテストを動かす場合、次のようなエラーログを目撃することが稀によくあります。

com.sample.SampleTest > sampleTestCase STANDARD_ERROR
    Downloading: org/robolectric/android-all/6.0.0_r1-robolectric-0/android-all-6.0.0_r1-robolectric-0.pom from repository sonatype at https://oss.sonatype.org/content/groups/public/

テストの実行時に依存を解決しに行くような挙動をしていることがわかります。CI の場合、最初に実行されるいくつかのテストでこのようなログが出てきます。 CI の特性上実行ごとに依存を解決することには問題がありませんが、テストの実行時に都度依存を解決するのではテストの実行時間に影響が出ます。昨今の CI as a Service を利用する場合は、タスクやステップの実行時間制限があるものもあり、気をつけないとテストの実行ステップがタイムアウトして CI が失敗してしまいます。 また、ダウンロードのたびに STANDARD_ERROR としてエラーログが吐き出されるのも精神衛生上あまりよろしくありません。

解決の方針

テストごとに必要な依存を解決するのではなく、テストステップの前にすべての依存をシュッと解決しておき、あとはテストを動かすだけの状態にできれば、純粋にテストステップはテストの実行時間だけになり、タイムアウトの可能性が減らせます。

解決策

ちょうど同じようなことを解決しようとしている issue が Robolectric のリポジトリに立っています。

github.com

この issue にあるコメントによると、プロジェクトルートにある build.gradle に次のコードを記述し、依存解決のタスクを追加します。

subprojects { project ->
    task downloadDependencies(type: Copy) {
        description "Downloads all dependencies."
        group "build"

        from {
            // Use of closure defers evaluation until execution time
            project.configurations
                .findAll { configuration -> configuration.canBeResolved }
                .collect { configuration -> configuration.resolvedConfiguration.lenientConfiguration.files }
        }
        into "$project.buildDir/dependencies"
    }
}

そして CI の設定ファイルに、追加した依存解決のタスクをテストステップの前にさしこみます。例えば WerckerCI であれば、次のように記述します。

build:
  steps:
    - script:
      name: "install dependencies"
      script: |
        ./gradlew downloadDependencies
    - script:
      name: "test"
      script: |
        ./gradlew testDebug

結果

CI のトータルの時間ではさほど変化がないか、微増となりますが、テストステップの実行時間は1分ほどの改善が見られました。

余談

最近 Wercker がかなり不安定になっていて世は大消耗時代です。