Espresso Test Recorderを試してみた

はじめまして、2016新卒Androidエンジニア索手です。

先日、AndroidStudio 2.2 Preview3にてEspresso Test Recorderの機能が公開されました。早速試してみたので、簡単に紹介します。

Espresso Test Recorderとは

Espresso Test Recorderは、UIテストコードを自動生成するための機能です。エミュレーターや実機上での操作を記録し、そこからテストコードを自動で生成してくれます。

実際に使ってみる

前準備

2016/6/9時点では、Android Studio 2.2 Preview3が必要です。Android Studio 2.2からJDKのバージョンとして1.8が要求されるので、そこだけ注意が必要です。

build.gradleのdependenciesについて、必要な変更はありません。機能の名前からEspressoの依存関係の追加が必要に思えますが、自動でbuild.gradleに記述を追加してくれます。これについては、以降で詳細に触れます。

使い方

「Run」→ 「Record Espresso Test」 からEspresso Test Recorderを使用できます。

Menu

「Record Espresso Test」を選択すると、アプリケーションの起動、デバッガーのアタッチが行われ、以下のようなダイアログが表示されます。このとき起動されるアプリケーションは「Run ‘app'」 の「'app'」 にあたるアプリケーションになります。

Dialog

このダイアログが表示された時点で、既に操作の記録は開始しています。この状態で端末(or エミュレーター)を操作すると、以下のように操作が記録されていきます。

Demo

記録後、「Complete Recording」を押すことでテストコードを生成できます。ボタンを押すとまず、生成するテストクラスの名前を聞いてきます。

Dialog classname

ここで入力した名前のテストクラスがデフォルトパッケージ以下に作られます。先ほどのgifの操作では、以下のようなコードが生成されました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.example.nawateippei.espressotestrecordersample;
import android.support.test.espresso.ViewInteraction;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.LargeTest;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.matcher.ViewMatchers.*;
import static org.hamcrest.Matchers.*;
@LargeTest
@RunWith(AndroidJUnit4.class)
public class MainActivityTest5 {
    @Rule
    public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<>(MainActivity.class);
    @Test
    public void mainActivityTest5() {
        ViewInteraction appCompatButton = onView(
                allOf(withId(R.id.button1), withText("launch ShowDialogActivity"),
                        withParent(allOf(withId(R.id.activity_main),
                                withParent(withId(android.R.id.content)))),
                        isDisplayed()));
        appCompatButton.perform(click());
        ViewInteraction appCompatButton2 = onView(
                allOf(withId(R.id.button), withText("show dialog"), isDisplayed()));
        appCompatButton2.perform(click());
        ViewInteraction appCompatButton3 = onView(
                allOf(withId(android.R.id.button2), withText("Cancel"),
                        withParent(allOf(withClassName(is("com.android.internal.widget.ButtonBarLayout")),
                                withParent(withClassName(is("android.widget.LinearLayout"))))),
                        isDisplayed()));
        appCompatButton3.perform(click());
        ViewInteraction appCompatButton4 = onView(
                allOf(withId(R.id.button), withText("show dialog"), isDisplayed()));
        appCompatButton4.perform(click());
        ViewInteraction appCompatButton5 = onView(
                allOf(withId(android.R.id.button1), withText("OK"),
                        withParent(allOf(withClassName(is("com.android.internal.widget.ButtonBarLayout")),
                                withParent(withClassName(is("android.widget.LinearLayout"))))),
                        isDisplayed()));
        appCompatButton5.perform(click());
    }
}

あとは、実際にテストを実行するだけです! これがEspresso Test Recorderの基本的な使い方になります。

Assertion

Espresso Test Recorderには記録中に、ViewのAssertionを定義することができます。以下は、「Launch ShowDialogActivity」と書かれたボタンをクリックした際に、「Show Dialog」と書かれたボタンが表示されていることを確認し、画面が遷移したことを確認する例です。

Demo assertion

この操作では、以下のようなソースコードが生成されます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import android.support.test.espresso.ViewInteraction;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.LargeTest;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.*;
import static org.hamcrest.Matchers.allOf;
@LargeTest
@RunWith(AndroidJUnit4.class)
public class MainActivityTest6 {
    @Rule
    public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<>(MainActivity.class);
    @Test
    public void mainActivityTest6() {
        ViewInteraction appCompatButton = onView(
                allOf(withId(R.id.button1), withText("launch ShowDialogActivity"),
                        withParent(allOf(withId(R.id.activity_main),
                                withParent(withId(android.R.id.content)))),
                        isDisplayed()));
        appCompatButton.perform(click());
        ViewInteraction button = onView(
                allOf(withId(R.id.button), withText("show dialog"), isDisplayed()));
        button.check(matches(isDisplayed()));
    }
}

テストメソッドの最後で、matches(isDisplayed()) が呼ばれ、表示が確認されていることがわかります。

自動テスト設定

build.gradle にテストの実行に必要な記述がない場合、「Complete Recording」を押した時点で、以下のようなダイアログが表示されます。

Dialog dependency

ここで「YES」を選択すると、build.gradle に以下の設定が追加されます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
android {
    // ...
    defaultConfig {
        // ...
        testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner'
    }
    // ...
}
dependencies {
    //...
    compile 'com.android.support.test.espresso:espresso-core:2.2.2'
    compile 'com.android.support.test:runner:0.5'
}

注意点

使っていて、少し注意すべき点があったので簡単に紹介します。

「Run」→「Record Espresso Test」がインアクティブになることがある

前述したように、「Run」→「Record Espresso Test」によって起動されるアプリケーションは、 「Run 'app'」の「'app'」にあたるアプリケーションになります。そのため、最後に起動したものがテストクラスやメソッドだと、「Run」→「Record Espresso Test」の部分がグレーアウトし、選択できなくなります。少しわかりづらいので、そこだけ注意が必要です。

既存のクラスにテストケースを追加することはできない

生成するテストクラスの名前として、既存のクラス名を入力すると以下のような警告を受けます。

Dialog classname error

サンプルコード

また、今回デモで見せたサンプルを(簡単なものですが)GitHub上で公開しているので、参考にしていただければ幸いです。

所感

率直に、とても簡単にテストコードを生成できて感動しました。

自分は、UIテストを書く際に「どうやってこのViewにアクセスすればいいんだ……。」と思うことが多いのですが、そこはまず考える必要がなくなります。
特にgifで紹介した例では、ダイアログのボタンをクリックしています。テストコードを書くときぐらいしか意識しないようなViewのアクセスも簡単にできてしまうので、テストコードを書く負担は大きく減るのではないかと思います。

とはいえ、生成されたコードのクオリティはお世辞にも高くなく、コードの細かな修正、コメントの追加など人の手が不要とは言い切れません。そのため、メンテナンスコストを考えると、UIの変更がほとんど起きない画面以外でのテスト導入は未だ難しいのではないかと思いました。