やったこと
RoboSpockというAndroid用のテストフレームワークが流行っているという噂を聞きました。なので自分でちょっと動かして見ました。しかし動かすのにそこそこ手間取ったので備忘録的に記録を残します。
Qiitaを使い始めたばかりで画像のアップロードの仕方がよくわからず画像を使えていません。。(可能なら画像追加します。)
環境
自分が試した環境は以下の通りです。
- Mac OSX 10.9.5
- JDK 1.7.0_71
- Android Studio 1.0.1
手順
buld.gradleの記述
build.gradleに以下の設定を追加してください。
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.0.0'
classpath 'se.centril.robospock:gradle-plugin-robospock:1.0.0'
}
}
repositories {
mavenCentral()
}
dependencies {
androidTestCompile 'org.codehaus.groovy:groovy-all:1.8.6'
androidTestCompile 'org.robospock:robospock:0.5.0'
androidTestCompile 'junit:junit:4.+'
}
※ jcenter()はbuildscriptの中のrepositoriesに追加してください。そうしないとrobospockのjarが見つからないです。
※ robospock-pluginを使いたい場合は'se.centril.robospock:gradle-plugin-robospock:1.0.0'を'org.robospock:robospock-plugin:0.4.0'に変更してください。最近はもう更新されていないそうですけど。
※ robospock0.5.0は内部でrobolectricの2.3を使っているぽいです。
テストコードの作成
テストコードを作成したいクラスのファイルを開き、クラス名をクリックすると電球マークが出て来ます。
電球マークをクリックするとメニューが出てくるので「Create Test」を選んでください。そうするとテストコードを生成するためのダイアログが出て来ます。
Testing libraryは「Spock」を選択し、Superclassは「pl.polidea.robospock.RoboSpecification」を入力してください。後は任意で大丈夫です。「OK」を押下するとテストコードのひな形が生成されます。
(テストコードは「Create Test」メニューから生成せずに通常のファイル作成の手順でも作成できます。)
今回は下記のActivityとそれに対するテストコードを作りました。import文やテストの内容と関係が無い部分は省略しています。
Activityのコード
public class MainActivity extends Activity {
public final int MORNING = 0;
public final int EVENING = 1;
public final int NIGHT = 2;
/*
* 挨拶を返すメソッド
*
* @param mode
*/
public String getGreeting(int mode) {
String greeting = null;
switch (mode) {
case MORNING:
greeting = "Good Morning";
break;
case EVENING:
greeting = "Good Evening";
break;
case NIGHT:
// 意図的にNIGHTの場合を書き忘れます
default:
}
return greeting;
}
以下省略
テストコード
@Config(manifest = "./app/src/main/AndroidManifest.xml", emulateSdk = 18,reportSdk = 18)
class MainActivityTest extends RoboSpecification {
def activity
void setup() {
activity = Robolectric.buildActivity(MainActivity.class).create().get()
}
def "getGreeting_午前中の挨拶"() {
when:
def result = activity.getGreeting(activity.MORNING)
then:
result == "Good Morning"
}
def "getGreeting_午後の挨拶"() {
when:
def result = activity.getGreeting(activity.EVENING)
then:
result == "Good Evening"
}
def "getGreeting_夜の挨拶"() {
when:
def result = activity.getGreeting(activity.NIGHT)
then:
result == "Good Night"
}
}
意図的にNIGHTのケースを書き忘れているので"getGreeting_夜の挨拶"() は失敗します。
Roborectricの2.3はまだKitkatやLollipopに対応していないので、@configに指定している「emulateSdk = 18」は必須です。API LEVEL 18として動かすという設定です。
テストコードの実行
テストしたいクラスを選択した状態でctrl + クリックでメニューを表示し、「Run 'hogehoge'」を選べば実行されます。
しかし・・・
classpathの変更
おそらく次のエラーが表示されると思います。
/Library/Java/JavaVirtualMachines/jdk1.7.0_71.jdk/Contents/Home/bin/java ...
!!! JUnit version 3.8 or later expected:
java.lang.RuntimeException: Stub!
at junit.runner.BaseTestRunner.<init>(BaseTestRunner.java:5)
at junit.textui.TestRunner.<init>(TestRunner.java:54)
at junit.textui.TestRunner.<init>(TestRunner.java:48)
at junit.textui.TestRunner.<init>(TestRunner.java:41)
at
以下省略
dependenciesでjunit4を指定していても、Andorid Studioのデフォルトであるjunit3が呼ばれてしまうために実行に失敗してしまいます。
junit4を呼び出すために実行時のclasspathを変更する必要があります。
上記のエラーに実行されたコマンドが記述されているのでclasspathを採取します。
/Library/Java/JavaVirtualMachines/jdk1.7.0_71.jdk/Contents/Home/bin/java ...
この...以降に-classpathで指定されたながーいclasspathが書かれているのですべてコピーします。
適当なメモに貼付けて、junit4のパスを切り取ってからclasspathのjunit-rt.jarのパスの後ろに移動します。
「Edit Configurations...」メニューからテスト実行時のVM optionsに、作成したclasspathを-classpathオプションと共に追加します。
何言ってるかよくわからないですよね・・
こちらのページで丁寧に説明されているのでご覧ください。
Android StudioからRobolectricのテストを直接実行する
classファイルの妥当性チェックを外す
classpathの設定を終えて実行すれば動くはず。。ですがJDK1.7以上の場合はまだダメです。
おそらくこんなエラーが出ます。
java.lang.VerifyError: Expecting a stackmap frame at branch target 47
Exception Details:
Location:
以下省略
Robolectricがclassファイルのバイトコードをいじっているらしいのですが、それがJavaVM実行時のclassファイルの妥当性チェックにひっかかっているとのことです。
また「Edit Configurations...」からVM Optionsに次の妥当性チェックを無視するオプションを追加してください。
JDK1.7の場合
-XX:-UseSplitVerifier
JDK1.8の場合
-noverify
テストコードの実行結果
これでようやくテストコードが実行できるはずです。
わざと間違えたテストで失敗が通知されます。
上記のテストコードだとこんな感じになります。
ondition not satisfied:
result == "Good Night"
| |
null false
<Click to see difference>
at com.example.hoge.fugapplication.MainActivityTest.getGreeting_夜の挨拶(MainActivityTest.groovy:41)
Process finished with exit code 255
このテスト結果の表示がJUnitのものより見やすいというのがSpockの人気の秘密らしいです。
注意点
Robolectric2.3はKitkat以上のAPI LEVELにまだ対応していないので最高でもAPI LEVEL 18で動かす必要があります。その場合はActionBarやappcompat-v7まわりでエラーが出てしまうようです。
みなさんお困りのようですね。
Lolipop - Unable to test ActionBarActivity ( needed for backporting material design)
Robolectricの最新の安定版は2.4ですが2.4でもKitkat以上には対応していません。3.0では対応されるようですがまだ3.0のSNAPSHOTはちゃんと動かないようです。
Robolectric 3.0-SNAPSHOT problem
Robolectricの3.0が安定版になったとしても今度はRobospockが3.0に対応するのを待たなければいけません。
Robospockが魅力的なのはわかりますが動かすのにこれだけ手間がかかると本格的に導入するのは躊躇してしまいます。
ついつい惹かれてしまうけど周りを振り回すサークルクラッシャー的な女みたいですね。。
参考
Android StudioからRobolectricのテストを直接実行する
Android Studio 1.0でrobospock-pluginが軒並み動かないので直して使う(差分はPR済)。
Lolipop - Unable to test ActionBarActivity ( needed for backporting material design)
Robolectric 3.0-SNAPSHOT problem