AndroidアプリにVisual Regression Test導入を目指す 第一回 Instrumented testで自動でスクショを撮る

この記事は  [Voicy Advent Calendar 2020](https://qiita.com/advent-calendar/2020/voicy) の17日目の記事です。
前日は @yamagenii さんの [Goでmainが実行される様子を追ってみる](https://medium.com/voicy-engineering/go%E3%81%A7main%E3%81%8C%E5%AE%9F%E8%A1%8C%E3%81%95%E3%82%8C%E3%82%8B%E6%A7%98%E5%AD%90%E3%82%92%E8%BF%BD%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%8B-d6d5a64b42d2?source=collection_home---5------0-----------------------) でした。
#はじめに
2日目に同じAndroidチームの ぬまさん（@moonum）が[Unit testの内容](https://numamyk.medium.com/mvvm-livedata-coroutine-%E3%82%A2%E3%83%BC%E3%82%AD%E3%83%86%E3%82%AF%E3%83%81%E3%83%A3%E3%81%ABspek2%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E8%B3%87%E7%94%A3%E4%BE%A1%E5%80%A4%E3%81%AE%E3%81%82%E3%82%8Bunittest%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%97%E3%81%A6%E3%81%84%E3%81%8F-c7ac99a6a471)を書いてくれたように、VoicyのAndroidチームでは「テストをしっかり書いていこう！」という空気感になりつつあります。
我々Androidチームは全員が8月9月にジョインしたばかりなのですが、いざプロジェクトファイルを覗いてみるとほとんどテストコードが無い…！！ということが発覚したのが発端です。
テストコードがほとんど無くともVoicyのアプリの品質が一定以上を維持されていたのは、ひとえにQAの方々の高品質なチェックによるものだったのです。
（事実、リリース前の動作確認では「本当に人力で見つけたのか？」と思うようなレアケースの不具合を検出していただくことも少なくありません）

どんなにテストをしっかり実装したところで最終的な人力チェックは確実に必要ではありますが、できるだけ自動化できるところは自動化して、手動でしか確認できないところに注力していただきたいところ。
そんなこんな思っているときに、先日ぬまさんにVisual Regression Testをちらっと教えてもらいました。
確かに自動でスクショ撮って修正前後で比較すればデグレチェックが一気に楽になる…！

というわけで今後VoicyのAndroidアプリでVisual Regression Testを導入するとなったときに先駆者となれるよう、お試しで動かしてみることにしました！

#今回やること
「Visual Regression Test」を導入するにあたって、大きく工程を分けると

+ 画面ごとのスクリーンショットの自動化
+ 画像比較の自動化

に分かれます。今回は前半戦ということで、スクショをテストコード上で取得していきますよ〜

先駆者の方がいらっしゃったので、その記事を見つつ試しました。

『Finnovalley Developers Blog - MoneyEasyのAndroidアプリでVisual Regression Testを始めた話（実装編）』
https://finnovalley.hatenablog.com/entry/2020/11/18/112425

基本的にはこちらの記事の内容を踏まえつつ、詰まったところをつけたそうと思います。

（ちなみにタイトルに第一回と書いたとおり、続きます…）


#手順
### ライブラリインポート
今回スクリーンショットを撮るにあたって、Firebase Test Lab利用者向け？っぽいライブラリ「cloudtestingscreenshotter」を使用します。
[Firebase公式](https://firebase.google.com/docs/test-lab/android/test-screenshots)に貼ってあるaarをプロジェクトに抱える形で参照します。

公式ドキュメントで書いてある通りにaarsディレクトリを作ってbuild.gradleを編集していくとなぜかうまいこと参照してくれなかったので、いつもやってる下記の方法を取りました。

1. File -> New -> New Module -> Import .JAR/.AAR Package -> aarsにおいたライブラリのaarファイルを選択
2. File -> Project Structure -> Dependencies から依存関係を追加

### パーミッション付与

個人的につまったポイントなのですが、このライブラリを使用すると端末内の`sdcard/screenshots`にスクショが保存されるはずが、全然保存されない…
[公式](https://firebase.google.com/docs/test-lab/android/test-screenshots)のとおりにパーミッション付与してだめだったのですが、どうやらTargetSdkVersionをいくつにするかによって対応が変わってくるようです。
当初Targetを30に設定していたのですが、29（Android 10）から外部ストレージの扱いが変わった影響でいろいろとややこしくなってました。

うまいことやらないとFileNotFoundExceptionが発生してスクショが保存されません。
そして私はその例外を処理中でキャッチしていなかったのでずっと例外に気づきませんでした…

* APIレベル28以下をTargetとする場合

公式に書いてある通り、マニフェストにパーミッションを記述すれば動きます。

```xml
<manifest ... >
   ...
   <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
   <!-- 公式にはINTERNETも書いてあるけど別になくても動く --/>
   ...
</manifest>
```

* APIレベル29以上をTargetにする場合

applicationタグ内に`android:requestLegacyExternalStorage`を追加する必要があるようです。
公式には全く書いてないので気づきませんでした…

```xml
<application
    ....
    ....
    android:requestLegacyExternalStorage="true" >
```

細かいことはこちらを読んでみてください
[Flutter で外部ストレージにアクセスするアプリを作るときは Android バージョンを考慮して『ひと手間』いるから注意してくれよな！](https://qiita.com/kurararara/items/968b2bc562a609a12f1e)

### InstrumentedTest実装
今回はアプリプロジェクト作成時のデフォルトのプロジェクトファイルを使用します。画面遷移したかったのでFragmentをもっているBase Activityを選択。
テストもデフォルトで作成されるテストファイルに追記しました。

```kotlin
import androidx.navigation.findNavController
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.android.libraries.cloudtesting.screenshots.ScreenShotter
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {

    @get:Rule
    var scenarioRule = ActivityScenarioRule(MainActivity::class.java)

    @Before
    fun navigateToConfirm() {
        scenarioRule.scenario.onActivity { activity ->
            // 画面遷移しておく場合
            activity.findNavController(R.id.nav_host_fragment).navigate(R.id.action_FirstFragment_to_SecondFragment)
        }
    }

    @Test
    fun test() {
        scenarioRule.scenario.onActivity { activity ->
            ScreenShotter.takeScreenshot("secondFragment", activity)
        }
    }

   ....
   ....
}
```

あとはこの遷移とtakeScreenshotをベースに、各画面の処理を繰り返し書いていきます。

###Test実行

AndroidStudio上で実行してもスクショは保存されますが、CIを構築することを考えてgradleコマンドで実行してみます。

```shell
./gradlew connectedAndroidTest
```

端末内の`sdcard/screenshots`にスクショが保存されます。
上記の実装だと`UnknownTestClass-unknownTestMethod-secondFragment-1.jpg`になりました。
察するにテストクラス名やメソッド名がファイル名になるのですが、取得できていないようですね…
ここら辺は追々明らかにしたいところです。

#感想
今回はアプリプロジェクト作成時にデフォルトでAndroid Architecture ComponentsのNavigationが設定されていたり、遷移時にIntentを渡す必要がなかったりするのですらっと書いてしまいましたが、実際に画面遷移も描くとなると結構ボリュームが出てきそうです。実際のアプリプロジェクトでもっと色々試してみようと思います。

本当は画像比較の自動化もひとつの記事内で取り上げたかったのですが、ボリュームがでてきたのと時間の都合で別記事に分けます！


次回は @somen440 さんです！