Github ActionsでFirebase Test Labを使ってAndroidアプリの自動テストを行う環境の構築

Androidアプリの開発において [Github Actionsにて自動的にビルドする環境作成](https://qiita.com/taptappun/items/92ec63b7e89e53b68efa) について紹介しました。
またビルドが完了した後に[Firebase App Distribution](https://firebase.google.com/docs/app-distribution)にアプリを配布する方法については[Github Actionsで自動的にAndroidアプリをビルドした後にFirebase App Distributionに配布する](https://qiita.com/taptappun/items/50555bbe66e4135ece15)にて紹介しています。
今回は[Firebase Test Lab](https://firebase.google.com/products/test-lab) を使用して、開発したAndroidアプリのテストを自動的に行う環境(CI環境)の構築について紹介していきます。
実際に自動テストを行なっている様子は以下のプロジェクトにて公開しています。
[github-actions-examples](https://github.com/TakuKobayashi/github-actions-examples)
Github Actionsで自動テストを行なっている実行フローのレシピはこちらに記述しています。

* [android-firebase-test.yml](https://github.com/TakuKobayashi/github-actions-examples/blob/master/.github/workflows/android-firebase-test.yml)

# モチベーション

ネイティブアプリにおける自動テストの導入を行いたい。しかしネイティブアプリの自動テストを行うには実機またはエミュレーターが必要なためGithub Actions内でのみで解決することは難しいです。そのため[Firebase Test Lab](https://firebase.google.com/products/test-lab)などのSaaSと組み合わせることによって、この問題の解決ができそうなため、実装してみました。

# 実行フロー

Github Actionsで実際に実行されるフローの様子について以下に図示します

```plantuml
@startuml
actor 開発者
database Github
database Firebase

開発者 -> Github: git push
Github -> Github: Github Actions起動、Androidビルド開始
group 自動テストの実行
  Github -> Firebase: ビルド済みapk、ビルド済みtest用apkのアップロード
  Firebase -> Firebase: テストを実行
  Firebase -> Github: テストの実行結果
end
Github -> Firebase: ビルド済みapkアップロード
Firebase -> 開発者: apkアップロード完了通知

@enduml
```

今回、紹介するフローは上記の「自動テストの実行」の部分について紹介していきます

# Androidアプリのテストについて

Androidアプリにおいて自動テストを行う場合以下の2種類のテストが存在します

* ローカル単体テスト(別称: ユニットテスト) (以降 UnitTestと表記)
* インストゥルメント化単体テスト(別称: UIテストなど) (以降 InstrumentationTestと表記)

## UnitTest

AndroidにおけるUnitTestはAndroid SDKの機能を用いない状態で実行されるテストのことをいいます。
つまり実装の中で以下のようなものが `import` されていないテストがUnitTestになります。

```kotlin
import android.*
import androidx.appcompat.*
```

テスト処理の記述についてはAndroidプロジェクト内の `app/src/test/java/` 以下に処理の内容を記述します。
こちらのサンプルのようにテストを記述しています。
[ExampleUnitTest.kt](https://github.com/TakuKobayashi/github-actions-examples/blob/master/Android/app/src/test/java/net/taptappun/taku/kobayashi/githubactionsexamples/ExampleUnitTest.kt)

より詳しいUnitTestを記述する環境の構築についてはこちらを参考にしてください。

* [ローカル単体テストを作成する](https://developer.android.com/training/testing/unit-testing/local-unit-tests)

## InstrumentationTest

InstrumentationTestとはAndroid SDKの機能を用いた上で実行されるテストのことをいいます。
つまり実装の中で以下のようなものが`import` されているテストがInstrumentationTestにあたります。

```kotlin
import android.*
import androidx.appcompat.*
```

Android SDKの機能を用いているため、実機のAndroid端末またはAndroidエミュレーター上でのみテストを実行させることができます。
Androidでテストを記述する場合、ほぼ全てのテストがInstrumentationTestとして記述します。
テスト処理の記述については Androidプロジェクト内の `app/src/androidTest/java/` 以下に処理の内容を記述します。
こちらのサンプルのようにテストを記述しています。
[ExampleInstrumentedTest.kt](https://github.com/TakuKobayashi/github-actions-examples/blob/master/Android/app/src/androidTest/java/net/taptappun/taku/kobayashi/githubactionsexamples/ExampleInstrumentedTest.kt)

より詳しいInstrumentationTestを記述する環境の構築についてはこちらを参考にしてください。

* [インストゥルメント化単体テストを作成する](https://developer.android.com/training/testing/unit-testing/instrumented-unit-tests)

# [Firebase Test Lab](https://firebase.google.com/products/test-lab)

## [Firebase Test Lab](https://firebase.google.com/products/test-lab) とは?

[Firebase Test Lab](https://firebase.google.com/products/test-lab) はFirebaseの中の機能の一つです。
[Firebase Test Lab](https://firebase.google.com/products/test-lab) を使用することによってUnitTestだけでなく、実機やエミュレーターを使用してのInstrumentationTestの実行も可能です。
[Firebase Test Lab](https://firebase.google.com/products/test-lab) でのテストの実行は仮想デバイステストの場合は1日10回まで、実機テストの場合は1日5回まで無料で実行できます。詳しくは [こちら](https://firebase.google.com/pricing) を参照してください。
[Firebase Test Lab](https://firebase.google.com/products/test-lab) で実行できるテストには以下の2種類があります。

* ロボテスト(RoboTest)
* インストルメンテーションテスト(InstrumentationTest)

RoboTestはAPKのみで実行可能なテストで主にアプリを起動し、自動的に適当に動かしてみてクラッシュしたりしないかどうか確認するテストです。
InstrumentationTestは上記のInstrumentationTestと同様のものを実行するテストです。

## Firebaseプロジェクトにアプリを登録する

[Github Actionsで自動的にAndroidアプリをビルドした後にFirebase App Distributionに配布する](https://qiita.com/taptappun/items/50555bbe66e4135ece15) にて紹介している[Firebaseプロジェクトにアプリを登録する](https://qiita.com/taptappun/items/50555bbe66e4135ece15#firebase%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AB%E3%82%A2%E3%83%97%E3%83%AA%E3%82%92%E7%99%BB%E9%8C%B2%E3%81%99%E3%82%8B)と同様のことを行いますのでそちらを参照してください。

## [Firebase Test Lab](https://firebase.google.com/products/test-lab) でテストを実行する端末の確認

[Firebase Test Lab](https://firebase.google.com/products/test-lab) にて実行することが可能なAndroid端末、エミュレーターやOSバージョンを確認します。(後でテストを実行するときに指定します)

1. FirebaseのプロジェクトからTest Labを選択してコンソールを開きデバイスカタログを選択します。(またここではこれまでのテストを実行した結果の一覧も確認することができます)

![firebase-testlab-execute-result-list.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72596/b84f3f87-4a04-0a88-c81c-f8a9ea624eda.png)

2. デバイスの一覧よりテストで使用する端末とOSのバージョン(APIレベル)を確認する

![firebase-testlab-device-list.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72596/1ae9a038-5656-4e0d-e3d4-7efa7061e2a5.png)

使用したいデバイスの情報を後述の実行する時のコマンドにて指定することでテストの実行が可能になります。

また、CLI経由でも端末やOSのラインナップの情報を確認することができます。
後述のgcloudの設定が完了している状態で

* 以下のコマンドを実行することで利用可能なデバイスの一覧を確認できます

```
gcloud firebase test android models list
```

* 以下のコマンドを実行することで利用可能なOSバージョンのリストを確認することができます。

```
gcloud firebase test android versions list
```

* 以下のコマンドを実行することで利用可能なロケールのリストを確認することができます。

```
gcloud firebase test android locales list
```

## [Firebase Test Lab](https://firebase.google.com/products/test-lab) でテストを実行する

Firebaseプロジェクトにアプリを登録することができたら各種設定を行なった上で[Firebase Test Lab](https://firebase.google.com/products/test-lab)にてテストの実行することができるようになります。
以下の手順をそれぞれ実行していきます。

1. `google-service.json` ファイルを設置する
アプリを登録したときに案内にでてきたもの使用します。
「google-service.jsonをダウンロード」を選択して `google-service.json` ファイルをダウンロードし、案内にあるようにAndroidプロジェクトの `app/google-service.json` の場所に設置する

![firebase-google-services-json.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72596/b3e7e6f0-ffa4-d442-ed94-bd4330cc80e9.png)

2. [gcloud CLI をインストールする](https://cloud.google.com/sdk/docs/install) ← リンク先を参考にgcloud CLIを実行できるようにインストールする

3. gcloud CLI を使用して設定する(以降のコマンドを順次実行していきます)

* インストールが最新であることを確認する

```
gcloud components update
```

* Googleアカウントにgcloud CLIでログインします

```
gcloud auth login
```

* gcloud で Firebase プロジェクトを設定します。

```
gcloud config set project PROJECT_ID
```

この時の `PROJECT_ID` はFirebaseプロジェクトの「プロジェクトの設定」を開き

![firebase-project-settings.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72596/5c9289c4-3aaf-3707-a0a9-e4d7c77c2657.png)

「プロジェクトID」の確認し、その値を指定することで設定することができます。

![firebase-project-id.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72596/445083ce-7fae-dbb2-c89e-54eee47ecb4f.png)

詳しくは [gcloudCLIでテストを開始します](https://firebase.google.com/docs/test-lab/android/command-line) を参照してください。

4. 通常のAndroid Buildをコマンドで行う

```
./gradlew assembleDebug
```

5. 加えて以下のコマンドを実行して、Androidのテスト実行用のAPKをビルドして生成していきます

```
./gradlew assembleAndroidTest -DtestBuildType=debug
```

6. コマンドを実行して、[Firebase Test Lab](https://firebase.google.com/products/test-lab) にてRoboTestを実行する

```
gcloud firebase test android run --type robo --app [APKファイルのパス] --device "model=[テストを行うデバイスのモデル名(端末名)],version=[テストするOSのバージョン],locale=[テストする端末の言語設定]"
```

7. コマンドを実行して、[Firebase Test Lab](https://firebase.google.com/products/test-lab) にてInstrumentationTestを実行する

```
gcloud firebase test android run --type instrumentation --app [APKファイルのパス] --test [テスト実行するAPKが格納されているディレクトリのパス] --device "model=[テストを行うデバイスのモデル名(端末名)],version=[テストするOSのバージョン],locale=[テストする端末の言語設定]"
```

## [Firebase Test Lab](https://firebase.google.com/products/test-lab) でテストを実行するための権限設定

上記のCLIの設定手順ではローカルでの実行は可能ですがGithub Actionsなどの環境においては実行を試みるも失敗してしまいます。
そこでGithub Actionsで実行可能にするために
1. 専用のサービスアカウントの情報を取得する
2. サービスアカウントに「編集」の権限を加える
をそれぞれ行う必要があります。

### 専用のサービスアカウントの情報を取得する

1. Firebaseプロジェクト内のサービスアカウントからGoogle Cloudの「IAMと管理」の画面を開く

![check-to-firebase-service-account.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72596/b4e6768c-51bd-dbb3-8014-a4714d33d8af.png)

2. サービスアカウントを選択して、すでに作成されているfirebaseプロジェクト専用のサービスアカウントを確認する。

![firebase-service-account-list.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72596/057d428c-a35f-43f8-eea7-cdc9e80e283b.png)

3. サービスアカウントの詳細を開く

![check-service-account.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72596/b4cb6e39-d421-602d-97a5-d7f15b2deeef.png)

4. キーを選択して新しい鍵を作成する

![generate-service-account-key.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72596/d65f356a-d413-318e-dd0b-74a32e019b25.png)

5. JSONを選択して作成を選択する

![service-account-key-json.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72596/5f4733e5-2058-61da-c53b-6ac487536a94.png)

サービスアカウントのキーを作成するとJSONファイルのダウンロードが行われる。このJSONファイルの中身は後述するGithub Actionsの実行で使用するためにGithub Secretsに登録する(`${{ secrets.FIREBASE_CREDENTIALS_JSON }}` で呼び出しているものがここで登録したJSONファイルの中身に相当する)

### サービスアカウントに「編集」の権限を加える

1. IAMを選択して、firebaseプロジェクトが作成されていれば専用のサービスアカウントのロールを確認する。このサービスアカウントのロールに「編集者」の権限を追加する

![check-google-service-account-list.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72596/21f1ea38-1ddb-51d1-6384-6e76ad7d7b35.png)

2. 「編集者」の権限を追加されているのを確認して、保存を選択する

![add-permission-to-service-account.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72596/709f5a47-062a-ff36-7a49-4e80488cfddb.png)

※ 今回、「編集」の権限を付与することによって[Firebase Test Lab](https://firebase.google.com/products/test-lab)の実行を可能にしていますがこの権限は余分な権限を多く付与してしまっています。ここではどの権限を付与するのが適切なのか判別がつかなかったため、もしご存知の方がいましたら教えていただけますと幸いです。

## Github Actionsの中に組み込む

Github Actionsでは `gcloud` のセットアップは独自に行うのではなく、提供されているものを利用する(自前でセットアップする場合にはGoogleアカウントにgcloud CLIでログインする認証を通すのが結構難しい)
レシピの中の `steps` の部分にて以下のように[gcloud CLI をインストールする](https://cloud.google.com/sdk/docs/install) こととサービスアカウントで認証を通しています。
```yml
  steps:
    - uses: 'google-github-actions/auth@v0'
      with:
        credentials_json: '${{ secrets.FIREBASE_CREDENTIALS_JSON }}'
    - name: 'Set up Cloud SDK'
      uses: 'google-github-actions/setup-gcloud@v0'
```

このとき、`${{ secrets.FIREBASE_CREDENTIALS_JSON }}` には上記権限を持たせたサービスアカウントの情報が記述されているJSONファイルの中身が代入されます。

gcloudの設定が完了したらあとは以下の手順をそれぞれレシピに追記していき、それぞれ実行されるように設定していきます。

* `google-services.json` の中身をこれからビルドするAndroidプロジェクトに設置する

```yml
  steps:
    - name: setup google-services.json
      run: |
        echo "${{ secrets.GOOGLE_SERVICES_JSON }}" >> ./app/google-services.json
```

* APKをビルドするコマンド

```yml
  steps:
    - name: Android Debug Apk Build
      run: ./gradlew assembleDebug
```

* テスト用APKをビルドするコマンド

```yml
  steps:
    - name: Android Debug Test Apk Build
      run: ./gradlew assembleAndroidTest -DtestBuildType=debug
```

* testを実行するFirebase プロジェクトIDを設定する

```yml
  steps:
    - name: Set execute project id
      run: gcloud config set project ${{ secrets.FIREBASE_PROJECT_ID }}
```

* [Firebase Test Lab](https://firebase.google.com/products/test-lab) にてRoboTestを実行する

```yml
  steps:
    - name: Execute firebase robo test
      run: gcloud firebase test android run --type robo --app app/build/outputs/apk/debug/app-debug.apk --device "model=Pixel3,version=30,locale=ja_JP"
```

* [Firebase Test Lab](https://firebase.google.com/products/test-lab) にてInstrumentationTestを実行する

```yml
  steps:
    - name: Execute firebase instrumentation test
      run: gcloud firebase test android run --type instrumentation --app app/build/outputs/apk/debug/app-debug.apk --test app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk --device "model=Pixel3,version=30,locale=ja_JP"
```

この時、権限を持たせたサービスアカウントの情報(`${{ secrets.FIREBASE_CREDENTIALS_JSON }}`)と`google-services.json` の中身の情報(`${{ secrets.GOOGLE_SERVICES_JSON }}`)、(ついでにFirebaseのプロジェクトIDの情報(`secrets.FIREBASE_PROJECT_ID`))は公開したくない情報であるためGithub Secretsに登録して、登録した値が参照されるように設定します。

![github-actions-secrets.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72596/4cafb14b-b0dc-cf27-f073-ab1afda32101.png)

![github-actions-add-secrets.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72596/eb250a35-ac30-9f5d-eefa-085b498c237c.png)

# 課題点

Github Actionsにて[Firebase Test Lab](https://firebase.google.com/products/test-lab)を実行する環境を構築し、運用していくにあたり以下の課題があります。

* [Firebase Test Lab](https://firebase.google.com/products/test-lab) を実行した場合、結構な時間がかかる(数十分) → Github Actionsでの実行時間によるコストが増大する
* [Firebase Test Lab](https://firebase.google.com/products/test-lab) には1日5回までの実行の無料枠はあるものの、基本的に実行するランニングコストが高い

上記のような課題があるため、[Firebase Test Lab](https://firebase.google.com/products/test-lab)を実行して自動テストが行うブランチには制限をかけて運用していくことをおすすめします。(例えば`master`や`main`ブランチにmergeされたときにのみ実行されるなど)

# まとめ

Github ActionsでFirebase Test Labを使ってAndroidアプリの自動テストを行う環境の構築について紹介しました。
少し癖があり、環境の構築のハードルは高いですが、Androidアプリの開発におけるCI環境の導入に役立ててみてください。