この記事では、Androidエミュレーターを使ったUIテスト(Espresso)を分割・並列実行することによって、実行時間を短縮する方法についてわかりやすくご紹介します。

はじめに

Android アプリケーション開発において、品質を継続的に向上させるために、自動テスト・CircleCI などのCI/CDツールの導入は、もはや欠かせないものとなりました。

そして、自動テスト・CI/CDを1回導入するだけでなく、継続的に改善していくことが重要です。

例えば、アプリケーション規模や自動テストの数、開発規模が大きくなっていくと、CI/CD におけるビルド・テストの実行時間は長くなってしまい、結果として開発スピードを低下させてしまいます。

特に 今回紹介する Espresso などのUIテストでは、実際に Android 実機や Android エミュレーターを動かしてテストを実行する必要があるため、実行時間が長くなりがちです。

CircleCI では Android アプリケーション開発で、実行時間を短縮するためのさまざまな機能が揃っています。

今回は CircleCI のテスト分割・並列実行を活用して、UIテスト(Espresso)の実行時間を短縮する方法について紹介します。


まずは、CircleCIに無料で登録してこのチュートリアルを実行して行きましょう。

無料で新規登録


CircleCI を使って UIテスト(Espresso)を分割・並列実行する方法(概要)

こちらが、今回紹介する Android アプリケーションのサンプルコードです。

GitHub - tadashi0713/circleci-demo-android

複数の UIテスト(Espresso)が用意されており、実行しているテスト内容は一緒ですが、Thread.sleep() を入れることによって実行時間を変えています。

@HiltAndroidTest
class GardenActivity10Test {

    private val hiltRule = HiltAndroidRule(this)
    private val activityTestRule = ActivityTestRule(GardenActivity::class.java)

    @get:Rule
    val rule = RuleChain
        .outerRule(hiltRule)
        .around(activityTestRule)

    @Test fun clickAddPlant_OpensPlantList() {
        // Given that no Plants are added to the user's garden

        // When the "Add Plant" button is clicked
        onView(withId(R.id.add_plant)).perform(click())

        Thread.sleep(100000)

        // Then the ViewPager should change to the Plant List page
        onView(withId(R.id.plant_list)).check(matches(isDisplayed()))
    }
}

CircleCI でこのテストを実行する際には、Android Orb を使うことによって、下記のように簡潔にパイプラインを作ることが可能です。

CircleCI Developer Hub - circleci/android

integration_test:
  executor:
    name: android/android-machine
    resource-class: xlarge
    tag: 2023.07.1
  steps:
    - checkout
    - android/start-emulator-and-run-tests
    - store_test_results:
        path: ./app/build/outputs/androidTest-results/connected

android/start-emulator-and-run-tests には以下が含まれています。

  • AVD(Android仮想デバイス)の作成・Androidエミュレーターの起動
  • Gradle のキャッシュを利用
  • UIテストを実行するための事前ビルド(./gradlew assembleDebugAndroidTest)
  • Android エミュレーターが起動するまで待機
  • UIテスト(Espresso)の実行(./gradlew connectedDebugAndroidTest)

この複数ある UIテスト を分割・並列実行する手順としては、以下になります。

  1. UIテストを実行するための事前ビルド(./gradlew assembleDebugAndroidTest)を行う
  2. 複数のLinux VM・ Android エミュレーターを起動する
  3. (実行時間に応じて) テストを分割する
  4. 分割されたテストを並列実行する
  5. 実行時間が含まれるテスト結果をアップロードする

UIテストを実行するための事前ビルドを行う

Espresso を含め、Android アプリケーションでテストを実行する際には、実行前にアプリケーションのビルドが必要になります。

今回は、後ほど複数のAndroidエミュレーターで並列でテストを実行させるために、ビルドの部分のみ(./gradlew assembleDebugAndroidTest)を事前に行います。

CircleCI のジョブ(build_for_integration_test)は以下になります。

build_for_integration_test:
  executor:
    name: android/android-machine
    resource-class: xlarge
    tag: 2023.07.1
  steps:
    - checkout
    - android/restore-gradle-cache
    - run: ./gradlew assembleDebugAndroidTest
    - android/save-gradle-cache
    - persist_to_workspace:
        root: ~/
        paths: .

以下の手順を行っています。

  • Gradle のキャッシュを利用(Android Orb を利用)
  • 事前ビルドの実行(./gradlew assembleDebugAndroidTest)
  • 事前ビルドの成果物を次のジョブで利用できるようにする(persist_to_workspace)

UIテストを分割・並列実行する

事前ビルドのジョブ(build_for_integration_test)が完了したら、実際にテストを分割・並列実行するジョブ(integration_test_parallel)を実行します。

integration_test_parallel:
  parallelism: 6
  executor:
    name: android/android-machine
    resource-class: xlarge
    tag: 2023.07.1
  steps:
    - checkout
    - attach_workspace:
        at: ~/
    - run:
        name: Split Espresso tests
        command: |
          cd app/src/androidTest/java
          CLASSNAMES=$(circleci tests glob "**/*Test.kt" \
            | sed 's@/@.@g' \
            | sed 's/.kt//' \
            | circleci tests split --split-by=timings --timings-type=classname)
          echo "export GRADLE_ARGS='-Pandroid.testInstrumentationRunnerArguments.class=$(echo $CLASSNAMES | sed -z "s/\n//g; s/ /,/g")'" >> $BASH_ENV
    - android/create-avd:
        avd-name: test
        install: true
        system-image: "system-images;android-29;default;x86"
    - android/start-emulator:
        avd-name: test
        post-emulator-launch-assemble-command: ""
    - run:
        name: Run Espresso tests
        command: ./gradlew connectedDebugAndroidTest $GRADLE_ARGS
    - store_test_results:
        path: ./app/build/outputs/androidTest-results/connected

まず、並列でテストを実行するために、複数の Linux VM を立ち上げます。

parallelism を指定することで、並列で実行する Linux VM の数を増減することが可能です。

次に以下の手順を実行しています。

  • 事前ビルド成果物を利用(attach_workspace)
  • AVD(Android仮想デバイス)の作成・Androidエミュレーターの起動(Android Orb を利用)
  • (実行時間に応じて) テストを分割、Gradle コマンドに渡すパラメーターを作成
  • 分割されたテストを並列実行
  • 実行時間が含まれるテスト結果をアップロード(store_test_results)

テスト分割、並列実行の部分を詳しく解説します。

UIテスト(Espresso)を実行するGradleコマンド(./gradlew connectedDebugAndroidTest)はデフォルトで全てのテストを実行します。

特定のテストを実行したい場合には、クラス名を使って以下のようなパラメーターを指定する必要があります。

./gradlew connectedAndroidTest 
-Pandroid.testInstrumentationRunnerArguments.class=com.google.samples.apps.sunflower.GardenActivity1Test,com.google.samples.apps.sunflower.GardenActivity2Test

実際にテストファイルを分割して、上記のパラメーターを作成している部分が以下になります。

cd app/src/androidTest/java
          CLASSNAMES=$(circleci tests glob "**/*Test.kt" \
            | sed 's@/@.@g' \
            | sed 's/.kt//' \
            | circleci tests split --split-by=timings --timings-type=classname)
          echo "export GRADLE_ARGS='-Pandroid.testInstrumentationRunnerArguments.class=$(echo $CLASSNAMES | sed -z "s/\n//g; s/ /,/g")'" >> $BASH_ENV

CircleCI CLI である、circleci tests glob によって対象となるテストクラスを取得し、circleci tests split によって分割しています。

circleci tests split には --split-by=timings フラグを付けています。

後の store_test_results で JUnit 形式のテストレポートをアップロードしているのが確認できると思います。

--split-by=timings フラグを有効にすることによって、テストレポートに含まれるタイミングデータを利用して均等にテストを分割しようとします。

これによって、より全体のテスト実行時間を短縮させることが可能です。

テスト実行時間を短縮

実際の並列化されたテストの実行時間については、CircleCI の UI (TIMING タブ)から見ていただくことが可能です。

CircleCI の UI

実行時間の異なるテストを6並列で実行していますが、テスト実行時間にばらつきが少ないことが確認できると思います。

おわりに

この記事では、Androidエミュレーターを使ったUIテスト(Espresso)を分割・並列実行することによって、実行時間を短縮する方法について紹介しました。

今回紹介したソリューションには以下の特徴があります。

  • 各テストの実行時間に応じてテストを分割・並列実行することができるため、より短時間でテスト実行を終了させることができる
  • parallelism を増やすだけで、CircleCI のクラウド上で簡単に並列数をスケールさせることができる

CircleCI では、並列数による課金ではなく、1分毎の Linux VM の使用量(クレジット)によって課金されます。

今回のような複数の Linux VM を並列で実行した場合でも、コストパフォーマンスが高い形でご利用していただくことが可能です。

料金プラン情報 - CircleCI

今後より Android アプリケーション開発のパフォーマンスを上げたい方は、是非参考にしていただければと思います。

関連記事