Groovy+Spock for Android app testing

## TL; DR
* AndroidアプリのテストにGroovy + [Spock][spock]を利用するとたのしいよ
  - power-assertっぽいメッセージが出るよ
  - ParameterizedTestしやすいよ
  - 標準添付のMockが高機能だよ
* Local unit testではSpockがそのまま使えるよ
* Instrumented unit testでは[spock-android][android-spock]が便利だよ

## Introduction
[Wantedly Advent Calendar 2015](http://qiita.com/advent-calendar/2015/wantedly) 2日目です．
Wanteldyでは主にRails・JS（AngularJS）・CSSあたりを触ってた@izumin5210がお送りします．
メインはサーバサイド・Webフロントエンドですが，今回はAndroidのテストのお話をします．
好きなテスティングフレームワークはRSpec，Mocha等のゆるふわspecです．よろしくお願いします．

本記事は[potatotips #22][potatotips]にて筆者が話をした『[**Groovy/Spock for Android app testing**][slide]』及び『[GroovyとSpockでテスト書く話してきた #potatotips - うさみみを生やせ][blog]』を補完する内容になります．

## Spock
[**Spock**][spock]はGroovy製のJava/Groovy向けテスティングフレームワークです．超ざっくりとした特徴だけあげると以下のような感じです:

* RSpecとかに影響受けてるらしい
* power-assertっぽい出力
  - むしろpower-assertがSpockの影響を受けてる
* めっちゃParameterized Testしやすい
* いいかんじのMock/Stub/Spyを標準搭載 

Spockの魅力については『[**JavaのユニットテストにSpockを適用する - Qiita**][qiita-spock]』という記事を読んでいただくか，[**Spock Web Console**][webconsole]で遊んでもらうと手っ取り早くわかっていただけるような気がします．
先の記事で紹介されてない特徴としてMockまわりのが高機能かつAPIが超柔軟というのがありますが，それはまたの機会に…（参考: [Mock](https://spockframework.github.io/spock/docs/1.0/interaction_based_testing.html#_mocking)，[Stub](https://spockframework.github.io/spock/docs/1.0/interaction_based_testing.html#_stubbing)，[Spy](https://spockframework.github.io/spock/docs/1.0/interaction_based_testing.html#Spies)）．

個人的には，メソッド呼び出しの順番の検証とかを`when` - `then`を交互に書き連ねてすごいバカっぽく書けるところが好きです（褒めてる）．意味不明なぐらい柔軟なMockまわりのAPIとかParameterized Testしやすいのもお気に入りポイントです（めっちゃ褒めてる）．

さて，次節からはAndroidアプリ開発におけるテストでSpockを利用する方法について紹介します．Androidのテストを大きく2種類に分けて説明していきます．

## Local unit test
『[Building Local Unit Tests | Android Developers](http://developer.android.com/intl/ja/training/testing/unit-testing/local-unit-tests.html)』にあるような，ローカルマシンで回す（= Android frameworkに依存しない）ユニットテストです．Utilsクラスや完全にAndroidから切り離されたビジネスロジックのテストに用いる，`src/test`以下に書いていくアレです．

### Dependencies
普通にSpockをdependenciesに追加します．SpockのMock/Stub/Spy等を利用する場合はcglibも一緒に追加しておきましょう．

```groovy
dependencies {
    // ...

    testCompile 'org.spockframework:spock-core:1.0-groovy-2.4'
    testCompile 'cglib:cglib-nodep:2.2'
}
```

### Usage
従来のJUnitなテストでは`src/test/java`以下にテストコードを記述していましたが，SpockはGroovyのtesting frameworkなので`src/test/groovy`以下に書いていくことになります．ファイルも`*.groovy`になります（本場では`*Spec.groovy`にすることが多いのかな？）．

例えば[**konashi SDK for Android**][konashi]では，『[Characteristicから取り出したバイト列を変換する](https://github.com/YUKAI/konashi-android-sdk/blob/628ed3a4b3447959637ac619bd1f2790883027f1/konashi-android-sdk/src/test/groovy/com/uxxu/konashi/lib/util/PwmUtilsSpec.groovy#L78-L88)』みたいなSpockお得意のParameterized Test案件等で利用されています．また，拙作のRedux for Androidなライブラリである[**Droidux**][droidux]においては，『[middlewareの呼び出し順](https://github.com/izumin5210/Droidux/blob/f53be2b9bb4f252f2c2d44e29df2f1a06be67ecd/middlewares/droidux-thunk/src/test/groovy/info/izumin/android/droidux/thunk/ThunkMiddlewareTest.groovy#L48-L87)』や『[undo時にHistoryの状態がちゃんと意図通りに遷移するか](https://github.com/izumin5210/Droidux/blob/f53be2b9bb4f252f2c2d44e29df2f1a06be67ecd/droidux/src/test/groovy/info/izumin/android/droidux/HistoryTest.groovy#L61-L81)』といった~~真面目にテストするのが激しくめんどうくさい~~箇所で利用しています．


## Instrumented unit test
『[Building Instrumented Unit Tests | Android Developers](http://developer.android.com/training/testing/unit-testing/instrumented-unit-tests.html)』にあるような，エミュレータや実機で回す（= Android frameworkに依存する）テストです．ActivityやContextが絡んでくるようなテスト，Android固有のクラスとかは使ってる場合はこっち，`src/androidTest`以下に書くやつです．

### Dependencies
こちらはSpock本体に加えて[**spock-android**][android-spock]とGroovyが，Mockの類を利用するにはcglibではなく**dexmaker 1.2**（[GitHubに移動する前のやつ](https://github.com/crittercism/dexmaker)）が必要です．[Espresso](http://developer.android.com/training/testing/ui-testing/espresso-testing.html)を利用したい人はお好みでどうぞ（一応，動いたのを確認しました）．

```groovy
android {
    defaultConfig {
        // ...
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    // ...
    packagingOptions {
        exclude 'META-INF/services/org.codehaus.groovy.transform.ASTTransformation'
        exclude 'LICENSE.txt'
    }
    // ...
}

dependencies {
    // ...

    androidTestCompile 'com.android.support.test:runner:0.4.1'
    androidTestCompile 'com.android.support.test:rules:0.4.1'
    androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.1'

    androidTestCompile 'org.codehaus.groovy:groovy:2.4.4:grooid'
    androidTestCompile('org.spockframework:spock-core:1.0-groovy-2.4') {
        exclude group: 'org.codehaus.groovy'
        exclude group: 'junit'
    }
    androidTestCompile 'com.andrewreitz:spock-android:1.2.2'
    androidTestCompile 'com.crittercism.dexmaker:dexmaker:1.4'
}
```

GitHub Repository名がandroid-spock，artifact名がspock-androidです．~~ややこしいわ．~~

### Usage
こちらも従来の`src/androidTest/java`ではなく`src/androidTest/groovy`以下の`*Spec.groovy`にテストコードを書いていくことになります．こちらに関しては自分で書いたpublicなコードがないので，[公式に添付されてるサンプルコード](https://github.com/pieces029/android-spock/tree/master/spock-android-sample/src/androidTest/groovy/com/example/spock)をどうぞ．

```groovy
// https://github.com/pieces029/android-spock/blob/master/spock-android-sample/src/androidTest/groovy/com/example/spock/MainActivitySpec.groovy
class MainActivitySpec extends Specification {

  @UseActivity(MainActivity)
  def activity

  def "test activity setup"() {
    expect:
    activity != null
    activity instanceof MainActivity
  }

  def "test layout"() {
    given:
    def button = activity.findViewById(R.id.main_button) as Button

    when:
    def buttonText = button.getText()

    then:
    buttonText == "Test"
  }
}
```

`@UseActivity`のようなアノテーションを付けることで，SpockのExcensionという仕組みによりActivityが"注入"されます（この表現が正しいかはわからない）．Mockを利用したいときはスーパークラスを`AndroidSpecification`に取り替えれば使えるようになります．

最近のテスト環境では`@Rule ActivityTestRule rule;`みたいな感じのを使うことでActivity起動のタイミングを制御できるっぽいですが，spock-androidだとちょっと厳しそうです．4日前ぐらいに出たspock-android 1.2.2で「SpecificationごとにActivity起動」「MethodごとにActivity起動」みたいなオプションが付いたみたいです，が，`ActivityTestRule`ほどの自由度はないような気がしています．

## Robospock?
AndroidでSpockというと[Robospock][robospock]を思い浮かべる方もいると思います．RobospockはJVM上で動かすやつなので，どちらかというとRobolectricに近いのかもしれません（実はよく知らない）．今回は取り扱いません．

## :warning: ハマりどころ :warning:
### groovy-gradle-android-plugin問題
Groovy及びSpockをAndroidアプリ開発で利用する際には[groovy-gradle-android-plugin][plugin]を利用します．このpluginですが，開発が停滞気味なのかAndroid Studio（正確には[Gradle向けAndroid Pluging](http://developer.android.com/tools/building/plugin-for-gradle.html)）のアップデートによりなんか01んだりします（関連issue: [#53](https://github.com/groovy/groovy-android-gradle-plugin/issues/53) [#61](https://github.com/groovy/groovy-android-gradle-plugin/issues/61)）．大体はわりとすぐに対応されますが，pluginの自体のバージョンアップはなかなかこないので気をつけましょう．最近ではSNAPSHOTSすら更新されてないとかいうおもしろ事態になってますが，そんなときはjitpack.ioから最新のmasterを取ってくればだいたいなんとかなります（関連issue: [#68](https://github.com/groovy/groovy-android-gradle-plugin/issues/68)）．12/2時点でAndroid Plugin 1.5とGroovy Plugin最新のmaster（1b77dd6763）の組み合わせでの動作を確認できています．

```groovy
buildscript {
    repositories {
        jcenter()
        maven { url "https://jitpack.io" }
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.5.0'
        classpath 'com.github.groovy:groovy-android-gradle-plugin:1b77dd6763'
    }
}

apply plugin: 'com.android.application'  // or 'com.android.library'
apply plugin: 'groovyx.grooid.groovy-android'
```

### Test Artifact問題
Android Studioを利用する場合，Build VariantsからTest Artifactを適切に設定しないと正しくテストが実行されません（CLIからだとだいじょうぶ）．
![image.png (19.0 kB)](https://img.esa.io/uploads/production/attachments/2063/2015/12/02/3140/8fabcdae-7b86-41ff-8d9e-a1dd98074e4c.png)

### Groovyのバージョン問題
`org.spockframework:spock-core:1.0-groovy-2.4`みたいなのをdependenciesに追加したと思いますが，このGroovyのバージョンを合わせないとテストが落ちます（androidTestのビルドで落ちる）．gradle-wapperを利用している場合，gradleのバージョンが古すぎるとGroovyの2.3とか入ってたりするので，`<PROJECT_ROOT>/grade/wrapper/gradle-wrapper.properties`にある`distributionUrl`を適切に設定しましょう（2.9ならとりあえず問題ないはず）．

```
distributionUrl=https\://services.gradle.org/distributions/gradle-2.9-all.zip
```

もしくはAndroid StudioのProject StructureからGradleのバージョンを弄れば大丈夫です．

![image.png (18.8 kB)](https://img.esa.io/uploads/production/attachments/2063/2015/12/02/3140/f5c3c486-bb94-4b9f-8842-f54a07c3d260.png)


### 動かないときは
とりあえず`clean`するか，`invalidate cache and restart`してみましょう．コード変換とかしている影響か，ライブラリ入れたり出したり試行錯誤している間はとくになんかおかしくなりやすいです（？）


## まとめ
既存プロジェクトのテストをすべて置き換えていくとかだとしんどいですが，たとえば導入の障壁が低いLocal unit testをSpockにしていくとかからはじめていくと楽かもしれません．いずれのテストも（クラス名が被ってなければ）JUnitとの共存も可能なので，チームメンバーの同意が得られるならばじわじわと布教していくのもいいですね（？）
[spock-android][android-spock]自体はまだまだ元気ですが，Android-Groovy界隈の動きが若干鈍いのが気になるかもしれません．

## References
* [Groovy/Spock for Android app testing // Speaker Deck][slide]
* [GroovyとSpockでテスト書く話してきた #potatotips - うさみみを生やせ][blog]
* [JavaのユニットテストにSpockを適用する - Qiita][qiita-spock]
* [pieces029/android-spock][android-spock]
* [Spock Web Console][webconsole]

[qiita-spock]: http://qiita.com/euno7/items/1e834d3d58da3e659f92
[robospock]: https://github.com/robospock/RoboSpock
[android-spock]: https://github.com/pieces029/android-spock
[droidux]: https://github.com/izumin5210/Droidux/
[potatotips]: http://connpass.com/event/20240/
[slide]: https://speakerdeck.com/izumin5210/spock-for-android-app-testing
[plugin]: https://github.com/groovy/groovy-android-gradle-plugin
[konashi]: https://github.com/YUKAI/konashi-android-sdk
[blog]: http://izumin.hateblo.jp/entry/2015/10/15/185233
[spock]: https://github.com/spockframework/spock
[webconsole]:http://meetspock.appspot.com/