Android アプリを GitHubActions でビルド、単体テストして FirebaseTestLab で UI テストを実行する

まずは yml です。

以下内容で `PROJECT_ROOT/.github/workflows/main.yml` を作成してプッシュすれば GitHubActions の Workflows に追加されます。

```yml:main.yml
name: CI

on:
  push:
    branches: [main]
    paths-ignore:
      - '**.md'

jobs:
  build:
    runs-on: ubuntu-20.04

    steps:

    - name: Checkout
      uses: actions/checkout@v2

    - name: Set up JDK 1.8
      uses: actions/setup-java@v1
      with:
        java-version: 1.8

    - name: Restore gradle cache
      uses: actions/cache@v2
      with:
        path: ~/.gradle/caches
        key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
        restore-keys: |
          ${{ runner.os }}-gradle-

    - name: Make gradle.properties
      run:
        KEYSTORE_PASSWORD=${{ secrets.KEYSTORE_PASSWORD }}
        echo -e "org.gradle.daemon=trueorg.gradle.daemon=true\norg.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=4096m -XX:+HeapDumpOnOutOfMemoryError\norg.gradle.parallel=true\norg.gradle.configureondemand=true\nKEYSTORE_PASSWORD=$KEYSTORE_PASSWORD\nandroid.enableD8 = true\nandroid.enableJetifier=true\nandroid.useAndroidX=true" >> gradle.properties

    - name: Run Compile
      run: ./gradlew assembleDebug

    - name: Run unit tests
      run: ./gradlew testDebugUnitTest --stacktrace

    - name: Set up Cloud SDK
      uses: google-github-actions/setup-gcloud@master
      with:
        project_id: ${{ secrets.GCP_PROJECT_ID }}
        service_account_key: ${{ secrets.GCP_SA_KEY }}
        export_default_credentials: true

    - name: Assemble Android Instrumentation Tests
      run: ./gradlew assembleDebugAndroidTest

    - name: Set APP_VERSION
      id: app_version
      run: |
        VERSION=$(grep -Eo 'versionName ".*"' ./app/build.gradle | grep -Eo '[0-9]+.[0-9]+.[0-9]+')
        echo "::set-output name=version::$VERSION"

    - name: Run Instrumentation Tests in Firebase Test Lab
      run: |
        echo "y" |
        gcloud firebase test android run \
          --timeout 5m \
          --type instrumentation \
          --app app/build/outputs/apk/debug/app-${{ steps.app_version.outputs.version }}-debug.apk \
          --test app/build/outputs/apk/androidTest/debug/app-${{ steps.app_version.outputs.version }}-debug-androidTest.apk \
          --device model=Pixel2,version=28,locale=ja_JP,orientation=portrait
```

ひとつずつみていきます。

```yml
name: CI
```

この Workflow の名前です。

```yml
on:
  push:
    branches: [main]
    paths-ignore:
      - '**.md'
```

この Workflow が動く条件です。
「`main` ブランチに push されたとき」という条件を指定しています。
ただし `'**.md'` の push は除外しています。

```yml
jobs:
  build:
    runs-on: ubuntu-20.04

    steps:
```

Workflow 内の Job です。
`build` という名前の Job で `ubuntu-20.04` 上で実行すると定義しています。
Job の中身は `steps` 内に記述していきます。

runs-on に指定できるラベルは [GitHub Docs-GitHub Actions-jobs.\<job_id\>.runs-on](https://docs.github.com/ja/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on) を参照してください。

Linux、macOS、Windows で料金が変わってくるので注意してください。
詳しくは [GitHub Docs-GitHub Actionsの支払いについて](https://docs.github.com/ja/free-pro-team@latest/github/setting-up-and-managing-billing-and-payments-on-github/about-billing-for-github-actions#github-actions%E3%81%AE%E6%94%AF%E6%89%95%E3%81%84%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6) を参照してください。

```yml
    - name: Checkout
      uses: actions/checkout@v2
```

リポジトリをチェックアウトします。

```yml
    - name: Set up JDK 1.8
      uses: actions/setup-java@v1
      with:
        java-version: 1.8
```

JDK をセットアップします。

```yml
    - name: Restore gradle cache
      uses: actions/cache@v2
      with:
        path: ~/.gradle/caches
        key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
        restore-keys: |
          ${{ runner.os }}-gradle-
```

Gradle の依存関係をキャッシュします。
詳しくは [GitHub Docs-GitHub Actions-依存関係のキャッシング](https://docs.github.com/ja/free-pro-team@latest/actions/guides/building-and-testing-java-with-gradle#%E4%BE%9D%E5%AD%98%E9%96%A2%E4%BF%82%E3%81%AE%E3%82%AD%E3%83%A3%E3%83%83%E3%82%B7%E3%83%B3%E3%82%B0) を参照してください。

```yml
    - name: Make gradle.properties
      run:
        KEYSTORE_PASSWORD=${{ secrets.KEYSTORE_PASSWORD }}
        echo -e "org.gradle.daemon=trueorg.gradle.daemon=true\norg.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=4096m -XX:+HeapDumpOnOutOfMemoryError\norg.gradle.parallel=true\norg.gradle.configureondemand=true\nKEYSTORE_PASSWORD=$KEYSTORE_PASSWORD\nandroid.enableD8 = true\nandroid.enableJetifier=true\nandroid.useAndroidX=true" >> gradle.properties
```

今回のアプリではビルドするために `gradle.properties` が必要なのでここで作成しています。
ファイル内に秘匿情報も含まれているため、 GitHub の Secrets に設定して `secrets.KEYSTORE_PASSWORD` で参照しています。
詳しくは [GitHub Docs-GitHub Actions-暗号化されたシークレット](https://docs.github.com/ja/free-pro-team@latest/actions/reference/encrypted-secrets) を参照してください。

```yml
    - name: Run Compile
      run: ./gradlew assembleDebug
```

デバッグ APK をビルドします。

```yml
    - name: Run unit tests
      run: ./gradlew testDebugUnitTest --stacktrace
```

単体テストを実行します。

```yml
    - name: Set up Cloud SDK
      uses: google-github-actions/setup-gcloud@master
      with:
        project_id: ${{ secrets.GCP_PROJECT_ID }}
        service_account_key: ${{ secrets.GCP_SA_KEY }}
        export_default_credentials: true
```

FirebaseTestLab と連携するために GCP のセットアップをおこないます。

`project_id` Firebase の プロジェクト ID です。
`service_account_key` GCP のサービスアカウントの鍵を base64化 した値です。

どちらも GitHub の Secrets に設定します。

[コチラのサイト](https://dev.classmethod.jp/articles/github-actions-firebase-testlab/#toc-5)が画像付きでわかりやすかったです :pray: 

```yml
    - name: Assemble Android Instrumentation Tests
      run: ./gradlew assembleDebugAndroidTest
```

UI テスト用の APK をビルドします。

```yml
    - name: Set APP_VERSION
      id: app_version
      run: |
        VERSION=$(grep -Eo 'versionName ".*"' ./app/build.gradle | grep -Eo '[0-9]+.[0-9]+.[0-9]+')
        echo "::set-output name=version::$VERSION"
```

今回のアプリでは APK のファイル名にバージョン名が含まれるため build.gradle ファイルの
`versionName 1.0.0` の `1.0.0` だけを抽出し環境変数にセットしています。

```yml
    - name: Run Instrumentation Tests in Firebase Test Lab
      run: |
        echo "y" |
        gcloud firebase test android run \
          --timeout 5m \
          --type instrumentation \
          --app app/build/outputs/apk/debug/app-${{ steps.app_version.outputs.version }}-debug.apk \
          --test app/build/outputs/apk/androidTest/debug/app-${{ steps.app_version.outputs.version }}-debug-androidTest.apk \
          --device model=Pixel2,version=28,locale=ja_JP,orientation=portrait
```

FirebaseTestLab に APK をアップロードし UI テストを実行しています。
先ほど環境変数にセットした versionName は `${{ steps.app_version.outputs.version }}` で参照しています。

`gcloud firebase test android run` のオプションは [gcloud リファレンス-gcloud firebase test android run](https://cloud.google.com/sdk/gcloud/reference/firebase/test/android/run) を参照してください。

`echo "y"` は 初めて実行するときに以下のような確認が入るため記述しています。
（2回目以降は不要そう）

```
API [toolresults.googleapis.com] not enabled on project.
Would you like to enable and retry (this will take a few minutes)? (y/N)?  
```

あとはファイルを main ブランチにプッシュすれば GitHub Actions が動いて Firebase の Test Lab 上でテストが動くことが確認できると思います。

以上です。

参考
[GitHub Docs-GitHub Actions](https://docs.github.com/ja/free-pro-team@latest/actions)
[[GithubActions] AndroidアプリをFirebaseTestLabに連携しRoboテストを実行するまでの手順](https://dev.classmethod.jp/articles/github-actions-firebase-testlab)
[gcloud リファレンス-gcloud firebase test android run](https://cloud.google.com/sdk/gcloud/reference/firebase/test/android/run)