TL; DR
- AndroidアプリのテストにGroovy + Spockを利用するとたのしいよ
- power-assertっぽいメッセージが出るよ
- ParameterizedTestしやすいよ
- 標準添付のMockが高機能だよ
- Local unit testではSpockがそのまま使えるよ
- Instrumented unit testではspock-androidが便利だよ
Introduction
Wantedly Advent Calendar 2015 2日目です.
Wanteldyでは主にRails・JS(AngularJS)・CSSあたりを触ってた@izumin5210がお送りします.
メインはサーバサイド・Webフロントエンドですが,今回はAndroidのテストのお話をします.
好きなテスティングフレームワークはRSpec,Mocha等のゆるふわspecです.よろしくお願いします.
本記事はpotatotips #22にて筆者が話をした『Groovy/Spock for Android app testing』及び『GroovyとSpockでテスト書く話してきた #potatotips - うさみみを生やせ』を補完する内容になります.
Spock
SpockはGroovy製のJava/Groovy向けテスティングフレームワークです.超ざっくりとした特徴だけあげると以下のような感じです:
- RSpecとかに影響受けてるらしい
- power-assertっぽい出力
- むしろpower-assertがSpockの影響を受けてる
- めっちゃParameterized Testしやすい
- いいかんじのMock/Stub/Spyを標準搭載
Spockの魅力については『JavaのユニットテストにSpockを適用する - Qiita』という記事を読んでいただくか,Spock Web Consoleで遊んでもらうと手っ取り早くわかっていただけるような気がします.
先の記事で紹介されてない特徴としてMockまわりのが高機能かつAPIが超柔軟というのがありますが,それはまたの機会に…(参考: Mock,Stub,Spy).
個人的には,メソッド呼び出しの順番の検証とかをwhen
- then
を交互に書き連ねてすごいバカっぽく書けるところが好きです(褒めてる).意味不明なぐらい柔軟なMockまわりのAPIとかParameterized Testしやすいのもお気に入りポイントです(めっちゃ褒めてる).
さて,次節からはAndroidアプリ開発におけるテストでSpockを利用する方法について紹介します.Androidのテストを大きく2種類に分けて説明していきます.
Local unit test
『Building Local Unit Tests | Android Developers』にあるような,ローカルマシンで回す(= Android frameworkに依存しない)ユニットテストです.Utilsクラスや完全にAndroidから切り離されたビジネスロジックのテストに用いる,src/test
以下に書いていくアレです.
Dependencies
普通にSpockをdependenciesに追加します.SpockのMock/Stub/Spy等を利用する場合はcglibも一緒に追加しておきましょう.
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では,『Characteristicから取り出したバイト列を変換する』みたいなSpockお得意のParameterized Test案件等で利用されています.また,拙作のRedux for AndroidなライブラリであるDroiduxにおいては,『middlewareの呼び出し順』や『undo時にHistoryの状態がちゃんと意図通りに遷移するか』といった真面目にテストするのが激しくめんどうくさい箇所で利用しています.
Instrumented unit test
『Building Instrumented Unit Tests | Android Developers』にあるような,エミュレータや実機で回す(= Android frameworkに依存する)テストです.ActivityやContextが絡んでくるようなテスト,Android固有のクラスとかは使ってる場合はこっち,src/androidTest
以下に書くやつです.
Dependencies
こちらはSpock本体に加えてspock-androidとGroovyが,Mockの類を利用するにはcglibではなくdexmaker 1.2(GitHubに移動する前のやつ)が必要です.Espressoを利用したい人はお好みでどうぞ(一応,動いたのを確認しました).
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/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はJVM上で動かすやつなので,どちらかというとRobolectricに近いのかもしれません(実はよく知らない).今回は取り扱いません.
ハマりどころ
groovy-gradle-android-plugin問題
Groovy及びSpockをAndroidアプリ開発で利用する際にはgroovy-gradle-android-pluginを利用します.このpluginですが,開発が停滞気味なのかAndroid Studio(正確にはGradle向けAndroid Pluging)のアップデートによりなんか01んだりします(関連issue: #53 #61).大体はわりとすぐに対応されますが,pluginの自体のバージョンアップはなかなかこないので気をつけましょう.最近ではSNAPSHOTSすら更新されてないとかいうおもしろ事態になってますが,そんなときはjitpack.ioから最新のmasterを取ってくればだいたいなんとかなります(関連issue: #68).12/2時点でAndroid Plugin 1.5とGroovy Plugin最新のmaster(1b77dd6763)の組み合わせでの動作を確認できています.
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からだとだいじょうぶ).
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のバージョンを弄れば大丈夫です.
動かないときは
とりあえずclean
するか,invalidate cache and restart
してみましょう.コード変換とかしている影響か,ライブラリ入れたり出したり試行錯誤している間はとくになんかおかしくなりやすいです(?)
まとめ
既存プロジェクトのテストをすべて置き換えていくとかだとしんどいですが,たとえば導入の障壁が低いLocal unit testをSpockにしていくとかからはじめていくと楽かもしれません.いずれのテストも(クラス名が被ってなければ)JUnitとの共存も可能なので,チームメンバーの同意が得られるならばじわじわと布教していくのもいいですね(?)
spock-android自体はまだまだ元気ですが,Android-Groovy界隈の動きが若干鈍いのが気になるかもしれません.