システム情報ダイアログをUIテストする

## はじめに
業務でAndroidを初めて触っていて、テストコードを書くことになったのでコードを書く上で困った点の備忘録および日本語の記事が一切なかったので情報共有の記事になります。

## 対象読者
Androidエンジョイ勢
テストコードを書くことの素晴らしさを感じ始めた人
検索: Espresso Permission dialog とかしてる人

## 環境
- minSdkVersion 26
- targetSdkVersion 29
- gradle 4.0.0
- kotlin 1.3.30
- espresso 3.2.0
- uiautomator 2.2.0

## AndroidのUIテストについて
Androidのテストとしては、主にUI絡みのテスト(AndroidTest)と単体テスト(test)があります。
その中で、AndroidTestのやり方として2つあって

- Espresso
単一アプリのUIテストをする
- Uiautomator 複数アプリのUIテストをする

と別れていることが公式のドキュメントを見れば分かると思います。
この文面だけを見ると、他のアプリと関連なければEspressoで事足りるやん。と思いますが、公式のドキュメントには
> 複数のアプリにまたがるユーザー インタラクションを対象とするユーザー インターフェース（UI）テストでは、ユーザーフローに他のアプリやシステム UI が関与する場合にアプリが正しく動作することを検証できます。

との記載があります。そのため、正確には以下の理解をしなければ永遠にハマることになります。

- Espresso
単一アプリのUIテストをする
- Uiautomator 複数アプリのUIテストおよび**システムUIのテスト**

僕はドキュメントを読み込んでおらず、ながら見をして理解していたのでこの問題が解決するまで1日潰れました:sob:
社内ではテストコードに落とすことができなさそうだから手動で~
となっていたので、結果的には時間をかけて調べて良かったとは思っています。

## システム情報ダイアログの取得方法
御宅はいいから早くやり方教えろ。という人もいるとは思うので、解決方法を記載します。
やり方としては、Uiautomatorの方を使えばシステム情報ダイアログ(認可ダイアログ)のUIテストが可能になります。
また、システム情報ダイアログをUIテストの際に考慮する必要がなければGrantPermissionRuleでRuleを定義すれば解決します。
ここでのやりたいこととしては、例えば認可を否定した場合に独自処理を実装していて、それをテストコードで確認を行いたい場合の話になります。
以下、ソースコードの修正箇所になります。

### gradleの設定
UiAutomatorがプロジェクト内に入っていない方は、依存関係の参照を設定しましょう。

```kotlin:build.gradle
androidTestImplementation
"androidx.test.uiautomator:uiautomator:2.2.0"
```
### テストコードの作成
サンプルとしてテストコードには、システム情報ダイアログが表示されている状態で許可を押下してActivityの起動を待つという内容になっています

```kotlin:HogeTest.kt
public class HogeTest {
    private lateinit var device: UiDevice

    fun pushAllowDialog() {
        // UiDeviceクラスのインスタンス化
        device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
       // 認可ダイアログから許可するボタンのindexを指定してオブジェクトを取得
        val allowPermission = device.findObject(UiSelector()
                .clickable(true)
                .index(getAllowButtonIndex())
        // 取得したオブジェクトが画面上に存在すれば押下
        if (allowPermission.exists()) { allowPermission.click() }
        // Activityの表示まで待つ等あれば待機する
        device.wait(Until.hasObject(By.pkg("テストクラスのパッケージ名")
                .depth(0)), 3000)
        // 任意のテストコード
    }
     // 許可するボタンのindexを取得
     private fun getAllowButtonIndex() = if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) 0 else 1
}
※ 例外処理等は一切やっていないので、適宜修正してください
※ import文とアノテーションは省略
```
※ Android10より、認可ダイアログ自体に変更があったためresorceIdやindex順が違うのでAPIレベルでの条件判定が必要になります
※ Android10以下であれば、UiSelector().text("許可") でも取れますが上述の理由で条件判定が必要です
※ 許可しないボタンの場合は、index指定だけでは10以下だと今後表示しないが取れてしまうのでresourceIdも指定すると良いですよ

## やったこと
ここまで辿り着くのに意外と苦労したので、まとめました。

### RecordEspressoTestで自動生成してみる
#### 結果
自動生成したコードが動かなかった。。人生そんなに甘くないってことね。また、生成されたコードを自分が理解できなかったのでメンテナンスを考慮すると全くの無意味に近かった。
押したいボタンの名前がpermission_allow_buttonという事が分かったので調べる方向性を変えるくらいしか役に立たなかった。

### Espressoで本当にテストコードが書けないかめちゃくちゃ調べる
#### 結果
できない。ただ、stackoverflowでこうすれば出来るよ。と回答している外国の技術者の方法を実際にやってみてもできなかったので昔はできたのか??と自分の中で解釈した。
出来るかもしれないという情報がめちゃくちゃミスリードになった。

## おわりに
findObject()の指定方法がいろいろあり過ぎて、どう書けば現在表示されているUIオブジェクトを取得できるのかがちょっとわからなかった。
というのも、システム情報ダイアログの取得をindex指定で出来るよ~と言っている人もいて、いろんなやり方があるのかorどちらかが間違えているかという
判断ができなかったので辛かった。
なので、今後は自分なりのやりかたの確立をしていきたいなぁ。
あと、uiautomatorviewerをMacで起動するとエラーになるのどうにかしてくれ。。

(追記 2020/10/24 Mac環境でuiautomatorが使えない問題は、JDKのバージョンを下げるとできるらしいです。問題なく使えていたら、こんなに困ることはなかった気がする。。)


## 参考リンク
https://medium.com/exploring-android/handling-android-runtime-permissions-in-ui-tests-981f9dc11a4e
https://developer.android.com/training/testing/ui-testing/uiautomator-testing?hl=ja