Record Espresso Test でテストコードを自動生成してみる

# Record Espresso Test

Android Studio 2.2 が正式リリースされ、画面を操作する事で Espressoの テストコードを
自動生成してくれる機能が追加されたため、実際に試してみました。
（2.2現在、β版での提供となっているようです）
https://developer.android.com/studio/test/espresso-test-recorder.html

## 前準備
- Android Studio のバージョンを 2.2以降 にしておく。
- [Espresso を利用出来る状態](http://qiita.com/fujin/items/df97a97ef80491f16122)にしておく。

## テスト対象
今回は、Espresso のサンプルプロジェクトである BasicSample を使って、一連の動作
「文字列を入力 → ボタンクリック → TextVeiwに入力した文字列が表示される」を実行し、
確認を行うテストの作成を行います。
※サンプルプロジェクトは、GitHub: [BasicSample](https://github.com/googlesamples/android-testing/tree/master/ui/espresso/BasicSample) からクローン可能です。

## 手順

1. Android Studio のメニューから [Run] -> [Record Espresso Test] を選択
![RET_SC1.png](https://qiita-image-store.s3.amazonaws.com/0/134550/dcd1176f-89ae-b551-0778-5bbd3d697fa4.png)
<br>

2. 端末選択画面から任意の端末（orエミュレータ）を選択
![RET_SC2.png](https://qiita-image-store.s3.amazonaws.com/0/134550/2a7b7dbd-bdeb-a8cb-6df1-020762554841.png)
<br>

3. BasicSampleアプリが立ち上がるので、テストを実行したい操作を開始します。
まずは、テキスト入力欄に文字列 "123" を入力します。
![RET_SC3.png](https://qiita-image-store.s3.amazonaws.com/0/134550/80c38270-6e0c-5787-56f2-c1bdc97697d2.png)
<br>

4. 入力すると、Record Espresso Test の画面内に内容が反映されている事がわかります。
次に、"Change text"ボタンをクリックします。
![RET_SC4.png](https://qiita-image-store.s3.amazonaws.com/0/134550/46a3aa58-60f9-0842-b460-05fa9b5be1bc.png)
<br>

5. "Hello Espresso!" と表示されていた TextView が "123" に変わります。
今回は、このTextView の表示内容を確認するテストを作成します。
そのため、この状態で "Add Assertion" ボタンを選択します。
![RET_SC5.png](https://qiita-image-store.s3.amazonaws.com/0/134550/5e451673-8e91-437f-9105-27e7309b2c38.png)
<br>

6. キャプチャー画面が右に現れるので、確認したい箇所を選択します。
又、画面左下では、対象オブジェクトを階層情報から直接選択したり、
判定内容や判定値を設定可能となります。
ここでは、判定内容を "text is"、判定値を "123" に設定して "Save Assertion" を選択します。
![RET_SC6.png](https://qiita-image-store.s3.amazonaws.com/0/134550/9bbc00ee-0874-1d17-cdef-b11800099a91.png)
<br>

7. Assert が追加されている事を確認します。
今回はここまでのテストとするため、”Complete Recording” を選択し終了します。
![RET_SC7.png](https://qiita-image-store.s3.amazonaws.com/0/134550/9238fffd-ef44-7f14-1234-79d2a2466300.png)
<br>

8. 任意のテスト名を入力します。
![RET_SC8.png](https://qiita-image-store.s3.amazonaws.com/0/134550/fd100084-722a-4ad4-db9b-63eb3fdc308c.png)
<br>

9. プロジェクトのディレクトリに今回作成したテストが追加されます。
![RET_SC9.png](https://qiita-image-store.s3.amazonaws.com/0/134550/33adb7b4-513b-da19-5875-fc9b66259388.png)
<br>

10. テストファイルを開くと、以下のようなテストコードが記述されています。

```java
package com.example.android.testing.espresso.BasicSample;


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 android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;

import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
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.action.ViewActions.closeSoftKeyboard;
import static android.support.test.espresso.action.ViewActions.replaceText;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf;

@LargeTest
@RunWith(AndroidJUnit4.class)
public class SampleTest {

    @Rule
    public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<>(MainActivity.class);

    @Test
    public void sampleTest() {
        ViewInteraction editText = onView(
                allOf(withId(R.id.editTextUserInput), isDisplayed()));
        editText.perform(replaceText("123"), closeSoftKeyboard());

        ViewInteraction button = onView(
                allOf(withId(R.id.changeTextBt), withText("Change text"), isDisplayed()));
        button.perform(click());

        ViewInteraction textView = onView(
                allOf(withId(R.id.textToBeChanged), withText("123"),
                        childAtPosition(
                                childAtPosition(
                                        withId(android.R.id.content),
                                        0),
                                0),
                        isDisplayed()));
        textView.check(matches(withText("123")));

    }

    private static Matcher<View> childAtPosition(
            final Matcher<View> parentMatcher, final int position) {

        return new TypeSafeMatcher<View>() {
            @Override
            public void describeTo(Description description) {
                description.appendText("Child at position " + position + " in parent ");
                parentMatcher.describeTo(description);
            }

            @Override
            public boolean matchesSafely(View view) {
                ViewParent parent = view.getParent();
                return parent instanceof ViewGroup && parentMatcher.matches(parent)
                        && view.equals(((ViewGroup) parent).getChildAt(position));
            }
        };
    }
}
```

***

## テストコード修正

自動生成されたテストコードは完全ではないため修正を行います。

- 修正箇所１<br>
Button を検索する検索条件に withText("Change text") が自動で追加されていますが、
withId(R.id.changeTextBt) の検索条件のみで特定可能なため、以下のように除去します。

```java
(修正前)
ViewInteraction button = onView(
        allOf(withId(R.id.changeTextBt), withText("Change text"), isDisplayed()));
```
```java
(修正後)
ViewInteraction button = onView(
        allOf(withId(R.id.changeTextBt), isDisplayed()));
```

- 修正箇所２<br>
TextView を検索する検索条件に複数の条件が自動で追加されていますが、<br>
withId(R.id.textToBeChanged) の検索条件のみで特定可能なため、以下のように除去します。

```java
(修正前)
ViewInteraction textView = onView(
        allOf(withId(R.id.textToBeChanged), withText("123"),
                childAtPosition(
                        childAtPosition(
                                withId(android.R.id.content),
                                0),
                        0),
                isDisplayed()));
```
```java
(修正後)
ViewInteraction textView = onView(
        allOf(withId(R.id.textToBeChanged), isDisplayed()));
```

***

## その他、気付いた点
- ソフトウェアキーボードを閉じる際ににバックキーを押した場合、
キーボードクローズ動作の記録と共に、バックキーを押すテストコードも書かれてしまい、
テスト実行時に意図しない挙動となるため、削除する必要があった。

- WebView 内のコンテンツ操作では、正しいテストコードが生成されなかった。


以上、Record Espresso Test のテストコード自動生成機能について記述しました。
<br>