okuzawatsの日記

モバイルアプリケーション開発の沼💀

[Android] GitHub ActionsでRoom / Realmの自動テスト

書いている人

モバイルアプリケーションアーキテクトとして働いています。モバイルアプリケーションのアーキテクチャ、自動テスト、CI/CDに興味があります。


この記事はフラー株式会社 Advent Calendar 2021の25日目の記事です。

24日目の記事は @su8 さんによる「コンテナ遠洋航海 ~そこらへんの草でも食わせておけ プロセス分離の旅路 ~ - Qiita」でした。

Androidのローカルデータベースアクセスの自動テスト

2021年現在、Androidアプリ開発でローカルデータベース(SQLite)にデータを保存する場合は、Roomを使用することが多いと思います。数年前はRealmが流行っていたので、今もRealmを使用しているというプロジェクトも多いと思います。この記事では、RoomとRealmを用いたローカルデータベースアクセスの自動テストをGitHub Actionsで行う方法について書きます。

Roomのテスト

Roomのテストは、Dao単位で行うことを想定します。Roomのインメモリデータベースを用いてテストを行います。エミュレータテストになるので、 androidTest にテストを書きます。

本記事の例では、Contextの取得に androidx.test.core.app.ApplicationProvider を、テストランナーとして androidx.test.ext.junit.runners.AndroidJUnit4 を使用します。

setuptearDown までは以下のように書けます。ここで、AwesomeDaoはRoomのDao、DatabaseはRoomDatabaseのサブクラスです。 setup でインメモリデータベースをビルドした後、テストのtargetとなるDaoを作ります。 tearDown では、データベースをcloseしています。

@RunWith(AndroidJUnit4::class)
class AwesomeDaoTest {

  private lateinit var target: AwesomeDao
  private lateinit var database: Database

  @Before
  fun setup() {
    val context = ApplicationProvider.getApplicationContext<Context>()
    database = Room
      .inMemoryDatabaseBuilder(context, Database::class.java)
      .setTransactionExecutor(StandardTestDispatcher().asExecutor())
      .setQueryExecutor(StandardTestDispatcher().asExecutor())
      .build()
    target = database.awesomeDao()
  }

  @After
  fun tearDown() {
    database.close()
  }
}

ここまでの準備ができたらあとはテストケースを書けば良いのですが、特にどうということもないので省略します。テストは gradlew connectedAndroidTest などのコマンドから実行することができます。実際には、Build Variantsを適切に設定してください。

Realmのテスト

Roomと同様に、RealmのテストもRealmの提供するインメモリデータベースを用いてテストを行います。Realmのインメモリデータベースのインスタンスは、以下のように作成することができます。

RealmConfiguration.Builder()
  .inMemory()
  .name("your_name_here")
  .build()

データベースアクセスを行うクラスにこのRealmのインスタンスをDIできるようにしておけば、Realmを用いたデータベースアクセスのテストを書くことができます。やっていることはRoomのテストと同様です。

class AwesomeDataSourceTest {

  private lateinit var target: AwesomeDataSource
  private var realm: Realm? = null

  @Before
  fun setup() {
    val config = RealmConfiguration.Builder()
                  .inMemory()
                  .name("your_name_here")
                  .build()
    realm = Realm.getInstance(config)
    target = AwesomeDataSource(requireNotNull(realm))
  }

  @After
  fun tearDown() {
    realm?.close()
    realm = null
  }
}

GitHub Actions

ここまでできれば、あとはGitHub Actionsでトリガーが引かれた時にテストを実行するだけです。Instrumented Testにする必要がありますが、GitHub Actions上でAndroidのエミュレータをセットアップするための素晴らしいアクション malinskiy/action-android が存在しますので、こちらを使えば苦労はいりません。

なお、エミュレータテストのために、OSとしてMacを選択する必要があることに注意してください(Ubuntuを使用する場合に比べて10倍の速度でお金が溶けます)。

※ 2024年3月29日追記:2024年1月22日にUbuntu対応が入ったので、macOSのインスタンスを使わなくても動くようになりました🎉 => Release 0.1.6 · Malinskiy/action-android

以下は簡単なGitHub Actionsのワークフローのサンプルです。参考のため、Build Variantsを DevDebug に設定しています。

jobs:
  test:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v2
      - name: Set Up JDK 11
        uses: actions/setup-java@v2
        with:
          distribution: 'zulu'
          java-version: '11'
      - uses: actions/cache@v2
        with:
          path: |
            ~/.gradle/caches
            ~/.gradle/wrapper            
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
          restore-keys: |
            ${{ runner.os }}-gradle-            
      - name: Install Android SDK
        uses: malinskiy/action-android/install-sdk@release/0.1.2
      - name: Instrumented Test
        uses: malinskiy/action-android/emulator-run-cmd@release/0.1.2
        with:
          cmd: ./gradlew connectedDevDebugAndroidTest
          api: 28
          tag: default
          abi: x86

以上で、Room / Realmの自動テストをGitHub Actions上で実行することができました。

まとめ

2019年、2020年に続き、2021年もフラー Advent Calendarの最終日を務めさせていただきました。来年も最終日を狙って参ります。よろしくお願いいたします。

Related