Spoonで実現する、UIテストのスクリーンショット事始め

皆さん、UIテスト書いていますか？

ユニットテストなら何とかという人でも、UIテストとなるとUIの更新頻度や費用対効果的な側面から、きっちりやっているというのは少ないのではないでしょうか。

そんな折、下記の素晴らしい記事を拝見し、スクリーンショットを撮るだけでもかなり効果があるのではないかと感じました。

AndroidアプリのゆるいUIテストをSpoonで実現する
https://qiita.com/oxsoft/items/d5774cc9c6016c3c28ec

で、いざSpoonでスクリーンショットを撮るところまでやってみようとしたものの、見事に躓いてしまったので、一旦 **UIテストのスクリーンショットを撮る** までを実現するところまでを記事にしたいと思います。

※ Spoon 
https://github.com/square/spoon

# Gradle Spoon Pluginを使用する
GitHubのページからjarをダウンロードしてゴニョゴニョする、ということも出来るようですが、楽をするためにGradleのプラグインを使用します。

(1). https://github.com/stanfy/spoon-gradle-plugin
(2). https://github.com/jaredsburrows/gradle-spoon-plugin

2つほどあるようですが、(1)に関しては最終更新日が1年以上前のようで、Android Gradle Pluginもver. 2までとのことで(2)のほうを使うことにしました。

## 導入

基本的にはREADMEに書いてあるものをコピペで問題ありませんでした。

```build.gradle
buildscript {
  repositories {
    google()
    jcenter()
    maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
  }

  dependencies {
    classpath "com.jaredsburrows:gradle-spoon-plugin:1.4.0"
  }
}
```

```app/build.gradle
repositories {
  maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
}

apply plugin: "com.android.application"
apply plugin: "com.jaredsburrows.spoon"

dependencies {
  androidTestCompile "com.squareup.spoon:spoon-client:2.0.0-SNAPSHOT"
}
```

## テストクラスを作成
サンプルとして `MainActivity` をテストするクラスを作成します。
スクリーンショットを撮る必要などがあるため、 `androidTest` のディレクトリに作成することになります。

下記コードは、MainActivityを起動してスクリーンショットを撮る、というコードになります。

```kotlin:app/src/androidTest/java/test/spoon/MainActivityTest.kt
import android.content.Intent
import android.support.test.rule.ActivityTestRule
import android.support.test.runner.AndroidJUnit4
import com.squareup.spoon.SpoonRule
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class MainActivityTest {
    @Rule
    @JvmField
    val activityRule = ActivityTestRule(MainActivity::class.java, false, false)

    @Rule
    @JvmField
    val spoon = SpoonRule()

    @Test
    fun screenshotMainActivity() {
        val mainActivity = activityRule.launchActivity(Intent())
        spoon.screenshot(mainActivity, "MainActivity")
    }
}
```

**ポイント**
Spoon ver.2 からなのかわかりませんが、GitHubのサンプルコードでは `Spoon.screenshot` というメソッドでスクリーンショットを撮れるようですが、それが出来ず、代わりに `@Rule` アノテーションを用いて `SpoonRule` というクラスを使うことでスクリーンショットを撮ることが出来るようでした。

# パーミッションを追加
これでOKと思い ` ./gradlew spoon` を実行すると下記のようなエラーが発生しました。

```
> Task :app:spoonDebugAndroidTest
2018-04-12 20:52:39 [SR.runTests] Executing instrumentation suite on 1 device(s).
2018-04-12 20:52:44 [SDR.handleFiles] Found class name dirs: []


FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app:spoonDebugAndroidTest'.
> Tests failed! See /Users/user_name/Spoon/app/build/spoon-output/debug/index.html
```

Spoonでは、テストの結果がHTMLとなって生成されるのでそれを見てみます。
<img width="1075" alt="a60de35a983802c51c9acbd2b0b31e4c.png" src="https://qiita-image-store.s3.amazonaws.com/0/995/891feaa5-ff92-2a3d-4658-af547a0dddb8.png">

ディレクトリを作れないエラーのようです。

**ポイント**
Spoonではスクリーンショットを保存したりする関係上、外部ストレージへのパーミッションが必要となります。 （`WRITE_EXTERNAL_STORAGE`）

そのままパーミッションを追加してもいいですが、アプリとして `WRITE_EXTERNAL_STORAGE` が不要な場合は、debug用のAndroidManifest.xmlを作り、そちらだけに定義するのもいいかもしれません。

```xml:debug/AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="test.spoon">

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

</manifest>

```

これで準備は完了で、 `./gradlew spoon` でスクリーンショットが保存されるようになります🚀 （※ Runtime Permission用の設定をしてないのにWRITE_EXTERNAL_STORAGEが使えているのはよくわかりません。）

<img width="1044" alt="9f889bf1e83cdfb4768acac454173a3c.png" src="https://qiita-image-store.s3.amazonaws.com/0/995/d59ca0a8-74f9-67e2-241d-cfde4c8b15af.png">

`app/build/spoon-output` というフォルダにスクリーンショットの画像などが生成されています。

![82624340f0159803874478cd5e89c4ab.png](https://qiita-image-store.s3.amazonaws.com/0/995/dcbb1112-a1de-8de1-8431-1310e621c4a3.png)


# まとめ
Spoonを用い、UIテストのスクリーンショットを撮るところまで説明しました。躓いた点はあるものの、意外とすんなりと導入することが出来たかと思います。

きっちりしたUIテストを書くのはしんどいかもしれませんが、ゆるいUIテストとしてスクリーンショットを撮っていくのはなんとかやっていけそうな感があったので、機会があれば実践していきたいところです。

ちなみにスクリーンショットを撮って、差分をチェックとかをするのがいいと思いますが、冒頭に紹介した投稿でも言及されているように、スクリーンショットにタイムスタンプが入っていたり、運用していくためにはもう少しカスタマイズしていく必要がありそうです。