GitLab CI + Docker で Android のインストゥルメント化単体テストを実行する

[GitLab Runner]: https://docs.gitlab.com/runner/
[Docker]: https://www.docker.com/
[Android Emulator Container Scripts]: https://github.com/google/android-emulator-container-scripts
[Docker for Android SDK 30]: https://hub.docker.com/r/androidsdk/android-30

[^1]: [インストゥルメント化単体テストを作成する  |  Android デベロッパー  |  Android Developers](https://developer.android.com/training/testing/unit-testing/instrumented-unit-tests?hl=ja)
[^2]: [Start containers automatically | Docker Documentation](https://docs.docker.com/config/containers/start-containers-automatically/)
[^3]: [コマンドラインからのエミュレータの起動  |  Android デベロッパー  |  Android Developers](https://developer.android.com/studio/run/emulator-commandline?hl=ja)
[^4]: [Dockerのコンテナの中からホストOS上のプロセスと通信する方法 - Qiita](https://qiita.com/ijufumi/items/badde64d530e6bade382)

インストゥルメント化単体テストとは、実機やエミュレータで実行する単体テストのことです。このテストでは、Android フレームワーク API や、AndroidX Test などのサポート API を利用できます。[^1]
本記事では、実機を用いず、 Docker 上の Android エミュレータで単体テストを実行する方法を記載します。単体テストは GitLab CI によって自動実行されます。

## 利用するもの & 動作確認したバージョン

* Ubuntu 20.04.1 LTS
    * [Docker] version 19.03.8 
        * [Android Emulator Container Scripts]
            * Android emulator version 30.1.2.0
        * [Docker for Android SDK 30]
    * [GitLab Runner] 13.6.0
* GitLab 13.6.3 (Community Edition)
    
## 事前準備

1. Ubuntu PC に [Docker] をインストール
2. [Docker] を root 以外のユーザーでも実行できるようにする<br>手順 : https://docs.docker.com/engine/install/linux-postinstall/
2. Ubuntu PC に [GitLab Runner] をインストール
3. [GitLab Runner] を GitLab へ登録する<br>`Enter the registration token:` で入力するトークンは、GitLab の管理者エリア `http://<GitLab アドレス>/admin/runners` で確認できます。<br>`Enter an executor:` では `docker` を入力する

## Docker 上で Android エミュレータで実行する

[Android Emulator Container Scripts] を利用します。

### Docker コンテナの起動

```sh
docker run \
  -d \
  --restart=always \
  -e ADBKEY="$(cat ~/.android/adbkey)" \
  -e EMULATOR_PARAMS="-wipe-data" \
  --device /dev/kvm \
  --publish 8554:8554/tcp \
  --publish 5555:5555/tcp \
  --name android-emulator \
  us-docker.pkg.dev/android-emulator-268719/images/30-google-x64:30.1.2
```


オプション `--restart=always` を指定することで、下記の場合に自動で Android エミュレータを起動します。[^2]

* Ubuntu PC が再起動した場合
* Ubuntu PC 上の Docker デーモンが再起動した場合
* Docker コンテナ上の Android エミュレータが終了した場合

環境変数 `EMULATOR_PARAMS="-wipe-data"` を指定することで、 Android エミュレータを起動する際に、エミュレータ上のユーザーデータを削除します。[^3]
例えば、 `adb reboot -p` コマンドで Android エミュレータを終了すれば、ユーザーデータが削除されたエミュレータが起動します。単体テストを繰り返すことで、共有ストレージなどに不要なファイルを残してしまうかもしれません。それらが消去されます。もちろん、他の単体テスト結果に影響しないように、そのようなファイルは残さない単体テストにするべきです。

### Docker コンテナの削除

前述の手順で、 Docker コンテナ上の Android エミュレータが常時起動されます。
Android エミュレータが不要になった場合は、下記コマンドでコンテナを削除します。

```sh
docker rm -f android-emulator
```


## GitLab 上の Android プロジェクトに CI/CD を設定する

リポジトリのルートディレクトリに `.gitlab-ci.yml` ファイルを作成する。

```yml:.gitlab-ci.yml
image: androidsdk/android-30:latest

cache:
  paths:
    - .gradle/wrapper
    - .gradle/caches

before_script:
  - export GRADLE_USER_HOME="${PWD}/.gradle"
  - chmod +x ./gradlew

stages:
  - check
  - connectedCheck

check:
  interruptible: true
  stage: check
  script:
    - ./gradlew -Pci --console=plain assembleDebug lintDebug testDebugUnitTest
  artifacts:
    expire_in: 1 week
    paths:
      - app/build/outputs/
      - app/build/reports/

connectedCheck:
  interruptible: true
  stage: connectedCheck
  before_script:
    - adb connect 172.17.0.1:5555
  script:
    - ./gradlew -Pci --console=plain connectedDebugAndroidTest
  artifacts:
    expire_in: 1 week
    paths:
    - app/build/reports/androidTests/
```

`172.17.0.1` は Docker コンテナからみたホスト PC の IP アドレスです。
環境や Docker のネットワーク設定によっては、これと異なる可能性があります。
Docker Desktop for Mac や Docker Desktop for Windows の場合は、
IP アドレスの代わりに `host.docker.internal` でもアクセスできるそうです。[^4]