Androidテストハンズオン

これは[「DevFest Tokyo 2016 秋のコミュニティ祭り！」](http://gdg-tokyo.connpass.com/event/38927/)の発表資料です。


## 本ハンズオンの内容
DroidKaigi2016のAndroidアプリについてEspressoを使ってUI周りのテストコードを書いていきます。

![image](https://qiita-image-store.s3.amazonaws.com/0/55138/a2fadb66-4a57-eda4-39bc-5c060c4f594a.png)

https://play.google.com/store/apps/details?id=io.github.droidkaigi.confsched


## Step 0：準備編
### cloneする
https://github.com/cattaka/droidkaigi2016 をcloneします。

#### Shellからcloneする場合
```bash
$ cd <作業用のディレクトリ>
$ git clone https://github.com/cattaka/droidkaigi2016.git
```

### プロジェクトを開き、ビルドと実行できることを確認する
<img width="30%" src="https://qiita-image-store.s3.amazonaws.com/0/55138/bef23698-326c-7960-af40-31c08668c778.png">


### Step 1：テスト用のライブラリを確認する
使用するテスト用のライブラリは次のものです。

- JUnit
 - いわずとしれたJava用のユニットテストライブラリです。
- Mockito
 - テスト時に各クラスをMock可するためのライブラリです。
 - 具体的には通信処理のように、テスト中に本番の動きをされたら困るコンポーネントの動きを差し替えるために使用します。
- [Espresso](https://google.github.io/android-testing-support-library/docs/espresso/)
 - Android用のUIテスト用のライブラリです。
 - JUnitのテストコードの中で、Activityを起動したり、Viewを操作（クリックやスワイプなど）したり、表示内容を確認する機能があります。

#### app/build.gradle の設定（導入済み）
app/build.gradle の設定を確認してみましょう。

```groovy
dependencies {
    androidTestCompile('com.android.support.test:runner:0.4.1') {
        exclude group: 'com.android.support', module: 'support-annotations'
    }
    androidTestCompile('com.android.support.test:rules:0.4.1') {
        exclude group: 'com.android.support', module: 'support-annotations'
    }
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.1') {
        exclude group: 'com.android.support', module: 'support-annotations'
    }
    androidTestCompile('com.android.support.test.espresso:espresso-contrib:2.2.1') {
        exclude group: 'com.android.support', module: 'support-annotations'
        exclude group: 'com.android.support', module: 'support-v4'
        exclude group: 'com.android.support', module: 'recyclerview-v7'
    }
    androidTestCompile('com.android.support.test.espresso:espresso-intents:2.2.1') {
        exclude group: 'com.android.support', module: 'support-annotations'
    }
    androidTestCompile 'org.mockito:mockito-core:1.9.5'
    androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'
}
```

excludeの記述は実際のアプリで使用しているライブラリが、テスト用ライブラリがビルドされたバージョンと食い違っているとき、それに引きづられてバージョンが変わったり、ビルドに失敗するのを回避するためのものです。

### 参考：ライブラリの依存関係
```
$ ./gradlew :app:dependencies
## 省略 ##
androidTestCompile - Classpath for compiling the androidTest sources.
+--- com.android.support.test:runner:0.4.1
|    +--- com.android.support.test:exposed-instrumentation-api-publish:0.4.1
|    \--- junit:junit:4.12
|         \--- org.hamcrest:hamcrest-core:1.3
+--- com.android.support.test:rules:0.4.1
|    \--- com.android.support.test:runner:0.4.1 (*)
+--- com.android.support.test.espresso:espresso-core:2.2.1
|    +--- com.squareup:javawriter:2.1.1
|    +--- com.android.support.test:runner:0.4.1 (*)
|    +--- com.android.support.test:rules:0.4.1 (*)
|    +--- javax.inject:javax.inject:1
|    +--- org.hamcrest:hamcrest-library:1.3
|    |    \--- org.hamcrest:hamcrest-core:1.3
|    +--- org.hamcrest:hamcrest-integration:1.3
|    |    \--- org.hamcrest:hamcrest-library:1.3 (*)
|    +--- com.google.code.findbugs:jsr305:2.0.1
|    +--- javax.annotation:javax.annotation-api:1.2
|    \--- com.android.support.test.espresso:espresso-idling-resource:2.2.1
+--- com.android.support.test.espresso:espresso-contrib:2.2.1
|    +--- com.android.support.test.espresso:espresso-core:2.2.1 (*)
|    \--- com.google.android.apps.common.testing.accessibility.framework:accessibility-test-framework:2.0
|         \--- org.hamcrest:hamcrest-core:1.3
+--- com.android.support.test.espresso:espresso-intents:2.2.1
|    \--- com.android.support.test.espresso:espresso-core:2.2.1 (*)
+--- org.mockito:mockito-core:1.9.5
|    +--- org.hamcrest:hamcrest-core:1.1 -> 1.3
|    \--- org.objenesis:objenesis:1.0
\--- com.google.dexmaker:dexmaker-mockito:1.2
     +--- com.google.dexmaker:dexmaker:1.2
     \--- org.mockito:mockito-core:1.9.5 (*)
## 省略 ##
```


## Step 2：Activityを起動しよう
まずは単純にMainActivityを表示するだけテストを作りましょう。

### はじめかた
```bash
$ git checkout devfest/step_1
```

### テストクラスを作成する
app/src/androidTest/ の下にMainActivityTestというテスト用のクラスを作成します。
クラスに@RunWith(AndroidJUnit4.class)アノテーションを付加します。これはテストクラスをどのように実行するかを決めるものです。指定しなかった場合は、現在のAndroidの場合はAndroidJUnit4がデフォルトになっているので、それが使われます。

![範囲を選択_003.png](https://qiita-image-store.s3.amazonaws.com/0/55138/78ba34bb-56b7-7ce4-db5f-6a8f571faa07.png)

#### MainActivityTest.java
```java
@RunWith(AndroidJUnit4.class)
public class MainActivityTest {

}
```

### テストメソッドを作成する
テストメソッドを作成しましょう。
publicなメソッドに@Testアノテーションを付加することでテストメソッドになります。

```java
    @Test
    public void check_start_activity() {
        /* ここにテストする内容を書く */
    }
```

### MainActivityを起動させる
Activityを起動させるにはActivityTestRuleという、JUnit4のRuleを使用します。これは@Ruleアノテーションをつけてpublicなメンバ変数として、定義することで使用できるようになります。

```java
    @Rule
    public ActivityTestRule<MainActivity> activityTestRule = new ActivityTestRule<>(MainActivity.class, false, false);

    @Test
    public void check_start_activity() {
        MainActivity activity = activityTestRule.launchActivity(null);
        /* ここにテストする内容を書く */
    }
```

### 実行してMainActivityが起動できているか確認する
Activityが実行状態になれているかを確認しましょう。
確認にはActivity#isFinishing()がfalseであることで判断します。
判定にはJUnitの機能であるassertThatというstaticメソッドを使用します。

```java
    @Test
    public void check_start_activity() {
        MainActivity activity = activityTestRule.launchActivity(null);
        assertThat(
                "MainActivity is running",
                activity.isFinishing(),
                Matchers.is(false)
        );
    }
```

[Step 2の変更点](https://github.com/cattaka/droidkaigi2016/compare/devfest/step_1...devfest/step_2)


## Step 3：テスト時に環境を切り替えられるようにする

### はじめかた
```bash
$ git checkout devfest/step_2
```

テスト実行するときに実際にサーバーに通信していたり、通常実行時に保存されたデータ（SQLiteやSharedPreferences）にアクセスしてしまうと、データの状態で挙動が変わってしまい、正しくテストをすることができません。こういった問題を避けるために、テスト中はそれらのコンポーネントを差し替えましょう。

### 差し替えが必要なもの
DroidKaigi2016アプリで該当するものは次の３つです。
- データベース（OrmaDatabaseクラスが該当）
- SharedPreferences
- 通信処理（DroidKaigiClientクラスが該当）

![image](https://qiita-image-store.s3.amazonaws.com/0/55138/17b3cc61-44d1-5923-526b-a2720c028af8.png)


### 差し替えの手段
コンポーネントを差し替えるにはアプリ内で実行状態に応じて切り替えることになります。愚直にやるとif文などで都度切り替えることになりコードが煩雑になります。これを回避するにはDI(dependency injection)ライブラリを使います。
DroidKaigi2016アプリではDagger2が使用されており、これらのコンポーネントは全てDagger2を介してActivityやFragmentから扱うようになっています。このため、テスト時はDagger2の設定を切り替えることで、コンポーネントの差し替えが可能になります。

#### MainApplicationクラスのAppComponentを差し替えられるようにする
各コンポーネントはMainApplicationクラスのメンバ変数のAppComponentから提供されるようになっています。AppComponentはAppModuleクラスから生成されます。これをテスト時に差し替えられるようにするため、setterメソッドを追加します。

[MainApplication.java](https://github.com/cattaka/droidkaigi2016/blob/devfest/step_3/app/src/main/java/io/github/droidkaigi/confsched/MainApplication.java)

```java
public class MainApplication extends Application {
    /* 他は省略 */

    @VisibleForTesting
    public void setAppComponent(AppComponent appComponent) {
        this.appComponent = appComponent;
    }
}
```

#### テスト用のコンポーネントを生成する処理を作る
テスト時にAppComponentの代わりに各コンポーネントを提供するTestAppComponentクラスを作成します。

SharedPreferencesとOrmaDatabaseは本番実行時と異なる場所にデータを保存するインスタンスを返すようになっており、resetメソッドで初期化できるようになっています。
通信処理を司るコンポーネントであるDroidKaigiClientについては、テスト用に偽物のデータを返すように作られたTestDroidKaigiClientを返すようになっています。

app/src/androidTest/java/io/github/droidkaigi/test/TestAppModule.java

```java
public class TestAppModule extends AppModule {
    static final String TEST_SHARED_PREF_NAME = "test_preferences";

    private Context context;
    private RenamingDelegatingContext rdContext;

    public Tracker tracker;
    public SharedPreferences sharedPreferences;
    public OrmaDatabase ormaDatabase;
    public DroidKaigiClient droidKaigiClient;

    public TestAppModule(Application app) {
        super(app);
        context = app;
        rdContext = new RenamingDelegatingContext(context, "test_");

        GoogleAnalytics ga = GoogleAnalytics.getInstance(context);
        ga.setDryRun(true);
        tracker = ga.newTracker(BuildConfig.GA_TRACKING_ID);
        tracker.enableAdvertisingIdCollection(true);
        tracker.enableExceptionReporting(true);

        sharedPreferences = context.getSharedPreferences(TEST_SHARED_PREF_NAME, Context.MODE_PRIVATE);
        ormaDatabase = super.provideOrmaDatabase(rdContext);

        AssetManager assets = InstrumentationRegistry.getContext().getAssets();
        droidKaigiClient = new TestDroidKaigiClient(assets);
    }

    public void reset() {
        sharedPreferences.edit().clear().apply();
        rdContext.deleteDatabase(OrmaDatabaseBuilderBase.getDefaultDatabaseName(context));
    }

    public void shutdown() {
    }

    @Override
    public Tracker provideGoogleAnalyticsTracker(Context context) {
        return tracker;
    }

    @Override
    public SharedPreferences provideSharedPreferences(Context context) {
        return sharedPreferences;
    }

    @Override
    public OrmaDatabase provideOrmaDatabase(Context context) {
        return ormaDatabase;
    }

    @Provides
    public DroidKaigiClient provideDroidKaigiClient(OkHttpClient client) {
        return droidKaigiClient;
    }
}
```

#### TestDroidKaigiClientの実装について
DroidKaigiClient と TestDroidKaigiClient の構成の違いは次のようになります。

- DroidKaigiClientはそれぞれ本物のWebサーバーと通信します。
- TestDroidKaigiClient はテスト側のAssetsである app/src/androidTest/assets/ に入れたテストデータを擬似的な通信結果として返します。

![image](https://qiita-image-store.s3.amazonaws.com/0/55138/e19097ec-b457-47e1-844d-01c5ba9a18bd.png)

- [TestDroidKaigiClientの実装はこちら](https://github.com/cattaka/droidkaigi2016/blob/devfest/step_3/app/src/androidTest/java/io/github/droidkaigi/test/TestDroidKaigiClient.java)
- テスト用データ
 - [app/src/androidTest/assets/DroidKaigiService/sessions_ja.json](https://github.com/cattaka/droidkaigi2016/blob/devfest/step_3/app/src/androidTest/assets/DroidKaigiService/sessions_ja.json)
 - [app/src/androidTest/assets/GithubService/contributors.json](https://github.com/cattaka/droidkaigi2016/blob/devfest/step_3/app/src/androidTest/assets/GithubService/contributors.json)
- DroidKaigiClientクラスはデフォルトコンストラクタが無いため、@VisibleForTestingをつけたものを追加します

```java
public class DroidKaigiClient {
    /* 省略 */
    @VisibleForTesting
    public DroidKaigiClient() {
        service = null;
        googleFormService = null;
        githubService = null;
    }
    /* 省略 */
}
```

#### 差し替えるためのJUnit4のRuleを作る
JUnit4のRuleは作成してテストクラスに適用すると、各テストメソッドの実行の開始や終了に処理を行うことができます。これを利用してテスト時にMainApplicationのAppComponentを差し替えるようにします。

app/src/androidTest/java/io/github/droidkaigi/test/IsolateEnvRule.java

```java
public class IsolateEnvRule implements TestRule {
    public TestAppModule appModule;

    @Override
    public Statement apply(Statement base, Description description) {
        return statement(base, description);
    }

    private Statement statement(final Statement base, final Description desc) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                before();
                try {
                    base.evaluate();
                } finally {
                    after();
                }
            }
        };
    }
    private void before() {
        MainApplication application = (MainApplication) InstrumentationRegistry.getTargetContext().getApplicationContext();
        appModule = new TestAppModule(application);
        appModule.reset();
        AppComponent testAppComponent = DaggerAppComponent.builder()
                .appModule(appModule)
                .build();
        application.setAppComponent(testAppComponent);
    }
    private void after() {
        appModule.shutdown();
    }
}
```

[Step 3の変更点](https://github.com/cattaka/droidkaigi2016/compare/devfest/step_2...devfest/step_3)


## Step4：SessionFeedbackActivityでフィードバックを送信するテスト

### はじめかた
```bash
$ git checkout devfest/step_3
```
<img width="30%" src="https://qiita-image-store.s3.amazonaws.com/0/55138/d35fed42-6675-552f-f5a5-9e1e6e040449.png"/>

### SessionFeedbackActivityTestクラスを作る
- SessionFeedbackActivityTestクラスを作成する
- IsolateEnvRuleルールを適用する
- ActivityTestRuleルールを適用する
- テストメソッドを作る
 - SessionFeedbackActivityの起動に講演情報が必要なのでテストデータから取得する
 - Intentを生成する
 - SessionFeedbackActivity#createIntentメソッドをpublicにする
 - SessionFeedbackActivityを起動する

```java
public class SessionFeedbackActivityTest {
    @Rule
    public IsolateEnvRule isolateEnvRule = new IsolateEnvRule();

    public ActivityTestRule<SessionFeedbackActivity> activityTestRule = new ActivityTestRule<>(SessionFeedbackActivity.class, false, false);

    @Test
    public void check_request_on_click() {
        Session session;
        {   // 偽物の講演情報を取得する
            List<Session> sessions = isolateEnvRule.appModule.droidKaigiClient.getSessions("ja").toBlocking().first();
            session = sessions.get(0);
        }

        Context targetContext = InstrumentationRegistry.getTargetContext();
        Intent intent = SessionFeedbackActivity.createIntent(targetContext, session);
        SessionFeedbackActivity activity = activityTestRule.launchActivity(intent);
    }
}
```


### テキストを入力して送信ボタンを押す
EditTextに入力したり、ViewをクリックしたりといったUIの操作にはEspressoの機能を使用します。EspressoではonViewから始まるメソッドチェーンを用いて次のように操作を書くことができます。

![espresso_onview.png](https://qiita-image-store.s3.amazonaws.com/0/55138/c1605b54-b7ee-e514-0c6f-9dc56029081d.png)

#### EditTextまでスクロールして文字を入力する

<img width="30%" src="https://qiita-image-store.s3.amazonaws.com/0/55138/50e063d0-0f03-57e5-2939-e4c717ccd547.png"/>

SessionFeedbackActivityにはR.id.other_comments_feedback_textというEditTextが存在しています。このEditTextまでスクロールし、文字を入力させるには次のように書きます。

```java        
Espresso.onView(ViewMatchers.withId(R.id.other_comments_feedback_text))
                .perform(
                        ViewActions.scrollTo(),
                        ViewActions.replaceText("Hogehoge Fugafuga"));
```

補足：replaceTextによく似たtypeTextというものがありますが、記号などを入力した際に機種によって文字が化けることがあるため私は使用を避けています。

onViewやwithIdはテストコードを書いていると頻繁に使うため、いちいちクラス名をつけていると読みにくくなってしまいます。幸いEspressonやJUnitはほとんどがstaticメソッドで構成されているため、クラス名はJava言語のstatic importsの機能を使うことで省略すると読みやすくなります。

```java
        onView(withId(R.id.other_comments_feedback_text))
                .perform(
                        scrollTo(),
                        replaceText("Hogehoge Fugafuga"));
```


Android Studioではメソッドにカーソルを当ててIntention Actions（Alt+Enter）を実行すると簡単にstatic importsにすることができます。

![image](https://qiita-image-store.s3.amazonaws.com/0/55138/9638fbf4-1189-7846-79a2-113b93c51222.png)


#### 送信ボタンを押す
onViewを使って次のように書けます。

```java
        onView(withId(R.id.submit_feedback_button))
                .perform(scrollTo(), click());
```

#### ５段階評価の操作を行う
このアプリでは5段階評価のNumberRatingBarというSeekBarを継承したカスタムViewを使用しています。残念ながらEspressoの標準の機能ではこれを操作するViewActionは提供されていません。

しかし次のsetProgressメソッドのように、カスタムのViewActionを生成するものを作ることで簡単に対応することができます。

[app/src/androidTest/java/io/github/droidkaigi/test/CustomViewAction.java](https://github.com/cattaka/droidkaigi2016/blob/devfest/step_4/app/src/androidTest/java/io/github/droidkaigi/test/CustomViewAction.java)

```java
public class CustomViewAction {
    public static ViewAction setProgress(final int progress) {
        return new ViewAction() {
            @Override
            public void perform(UiController uiController, View view) {
                ((SeekBar) view).setProgress(progress);
                //or ((SeekBar) view).setProgress(progress);
            }

            @Override
            public String getDescription() {
                return "Set a progress";
            }

            @Override
            public Matcher<View> getConstraints() {
                return ViewMatchers.isAssignableFrom(SeekBar.class);
            }
        };
    }
}
```

これを使うと５段階評価の処理はonViewを使って次のように書けます。

```java
        onView(withId(R.id.relevant_feedback_bar))
                .perform(scrollTo(), setProgress(1));
        onView(withId(R.id.as_expected_feedback_bar))
                .perform(scrollTo(), setProgress(2));
        onView(withId(R.id.difficulty_feedback_bar))
                .perform(scrollTo(), setProgress(3));
        onView(withId(R.id.knowledgeable_feedback_bar))
                .perform(scrollTo(), setProgress(4));
```

### 送信しようとした内容を確認する
送信ボタンを押した時、アプリは通信しようとするので、それがUI操作に沿ったものであるか確認しましょう。このときにDroidKaigiClient#submitSessionFeedbackメソッドが叩かれるので、その引数をチェックします。

手順は次のようになります。
- IsolateEnvRuleが持つdroidKaigiClientを、Mockitoの機能を使し、引数を後から確認できるようにする
- UI操作後、IsolateEnvRuleが持つdroidKaigiClientから渡された引数を取り出す
- 取り出した引数の中身を確認する

#### droidKaigiClientの引数を後から確認できるようにする
```java
        DroidKaigiClient droidKaigiClient = Mockito.mock(
                DroidKaigiClient.class,
                new ForwardsInvocations(isolateEnvRule.appModule.droidKaigiClient)
        );
        isolateEnvRule.appModule.droidKaigiClient = droidKaigiClient;
```

#### 渡された引数を取り出す
```java
        ArgumentCaptor<SessionFeedback> captor = ArgumentCaptor.forClass(SessionFeedback.class);
        Mockito.verify(droidKaigiClient)
                .submitSessionFeedback(captor.capture());
        SessionFeedback value = captor.getValue();
```

#### 取り出した引数の中身を確認する
```java
        assertThat(value.relevancy, Matchers.is(1));
        assertThat(value.asExpected, Matchers.is(2));
        assertThat(value.difficulty, Matchers.is(3));
        assertThat(value.knowledgeable, Matchers.is(4));
        assertThat(value.comment, Matchers.is("Hogehoge Fugafuga"));
```

[Step 4の変更点](https://github.com/cattaka/droidkaigi2016/compare/devfest/step_3...devfest/step_4)


## Step 5：MainActivityから講演情報を表示する操作のテスト
MainActivityには講演のリストがあり、それをクリックするとSessionDetailActivityに遷移します。これをテストするにはActivityMonitorを使用することで確認することができます。

### はじめかた
```bash
$ git checkout devfest/step_4
```

<img width="60%" src="https://qiita-image-store.s3.amazonaws.com/0/55138/4529fcde-258c-def1-078d-fd8b2d3ca146.png"/>


### RecyclerViewの１つをクリックする
onViewを使って書けますが、２つ課題があります。

- クリックしたいのはRecyclerViewの１つの項目なので、クリックしたいViewをwithIdで指定できない
- MainActivity上にはR.id.recycler_viewが複数あるため、「どの」RecyclerViewなのかをwithIdで指定できない

#### RecyclerViewの１つの項目をクリックするには
EspressoでRecyclerViewを操作するのに、そのままでは泥臭いことをしなければならないのですが、幸いそのためのespresso-contribという追加ライブラリが提供されています。

このライブラリを使うとRecyclerViewの特定の要素にスクロールしたり、クリックすることが次のように書けます。

```java
            onView(<ここにRecyclerViewを指定する>)
                    .perform(RecyclerViewActions.scrollToPosition(5));
            onView(<ここにRecyclerViewを指定する>)
                    .perform(RecyclerViewActions.actionOnItemAtPosition(5, click()));
```

関連情報：[RecyclerViewの１つの項目の内部のViewへのperformやcheckを行う方法](http://qiita.com/cattaka/items/b13368561b8ad43e014c)

#### 特定のRecyclerViewを指定する
目的のRecyclerViewのインスタンスが取得できれば、withIdの代わりにMatchers.equalToが利用できます。

これを使うとRecyclerViewの特定の要素にスクロールしたり、クリックすることが次のように書けます。

```java
        View recyclerView;
        {   // 少々強引だがRecyclerViewのインスタンスを取得する
            ViewPager viewPager = (ViewPager) activity.findViewById(R.id.view_pager);
            FragmentStatePagerAdapter adapter = (FragmentStatePagerAdapter) viewPager.getAdapter();
            Fragment sessionsTabFragment = adapter.getItem(0);
            recyclerView = sessionsTabFragment.getView();
        }

        onView(Matchers.equalTo(recyclerView))
                    .perform(RecyclerViewActions.scrollToPosition(5));
        onView(Matchers.equalTo(recyclerView))
                    .perform(RecyclerViewActions.actionOnItemAtPosition(5, click()));
```

### SessionDetailActivityが起動したことを確認する
Activityが起動したか否かはActivityMonitorを使うことで確認できます。
対象となるActivity用のActivityMonitorを作成し、Instrumentation#addMonitor()で登録すると、それより後に起動したActivityのインスタンスを取得できるようになります。
ActivityMonitorの作成と登録は次のように書けます。

```java
        Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(SessionDetailActivity.class.getCanonicalName(), null, false);
        InstrumentationRegistry.getInstrumentation().addMonitor(monitor);
```

そしてUIの操作後に起動したかどうかは次のように書けます。

```java
            Activity nextActivity = monitor.waitForActivityWithTimeout(5000);
            assertThat(nextActivity, Matchers.is(Matchers.notNullValue()));
            nextActivity.finish();
```

これらを繋げると次のようになります。

```java
        Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(SessionDetailActivity.class.getCanonicalName(), null, false);
        InstrumentationRegistry.getInstrumentation().addMonitor(monitor);
        try {
            onView(Matchers.equalTo(recyclerView))
                    .perform(RecyclerViewActions.scrollToPosition(5));
            onView(Matchers.equalTo(recyclerView))
                    .perform(RecyclerViewActions.actionOnItemAtPosition(5, click()));

            Activity nextActivity = monitor.waitForActivityWithTimeout(5000);
            assertThat(nextActivity, Matchers.is(Matchers.notNullValue()));
            nextActivity.finish();
        } finally {
            InstrumentationRegistry.getInstrumentation().removeMonitor(monitor);
        }
```


[Step 5の変更点](https://github.com/cattaka/droidkaigi2016/compare/devfest/step_4...devfest/step_5)



## Step 6：gradleからテストを実行する

### はじめかた
```bash
$ git checkout devfest/step_5
```

### コンソールからテストを実行する
コンソールからテストを実行するには「$ ./gradlew \connected[BuildVariant名]AndroidTest」を実行します。
今回はBuildVariantに「DevelopDebug」を使用しますので次のようになります。

```bash
$ ./gradlew connectedDevelopDebugAndroidTest
```

これを実行すると、テスト結果のレポートのxmlやhtmlが生成されます。

- app/build/outputs/androidTest-results/connected/flavors/DEVELOP/TEST-Nexus 4 - 5.1.1-app-DEVELOP.xml
- app/build/reports/androidTests/connected/flavors/DEVELOP/index.html
 - ![image](https://qiita-image-store.s3.amazonaws.com/0/55138/a3febfaf-aa83-def5-c0cd-f329d0199a9a.png)

### コードカバレッジを生成する
テスト時のコードカバレッジを計測するにはapp/build.gradleに次のオプションを追加します。

app/build.gradle

```groovy
android {
    /* 省略 */
    buildTypes {
        debug {
            /* 省略 */
            testCoverageEnabled true
        }
        /* 省略 */
    }
    /* 省略 */
}
```

そして「./gradlew create[BuildVariant名]CoverageReport」を実行します。

```bash
$ ./gradlew createDevelopDebugCoverageReport
```

これを実行すると、コードカバレッジのレポートのecファイルやhtmlが生成されます。

- app/build/outputs/code-coverage/connected/flavors/DEVELOP
- app/build/reports/coverage/develop/debug/index.html
 - ![image](https://qiita-image-store.s3.amazonaws.com/0/55138/9832b25f-cb88-2734-3574-c7c8f1c713ff.png)
 - ![image](https://qiita-image-store.s3.amazonaws.com/0/55138/221297b5-c599-8d48-670e-3c761d36e787.png)


## まとめ
- テストを書くにはアプリの状態を作り出すことが大事
 - 内部的な状態：データベース、SharedPreferences 
 - 外部に依存する状態：通信処理
- Dagger2を使えばコンポーネントを差し替えられる
- Espressoを使うとUI操作の処理が簡潔に書ける

## 関連書籍

### アンドロイドアカデミア - TechBooster
<a href="https://booth.pm/ja/items/299593">
<img width="20%" src="https://qiita-image-store.s3.amazonaws.com/0/55138/1a68f0b3-81b0-7bd7-12a5-7a656ac7be36.png">
</a>

- 「Androidテストレシピ」にAndroidのテストの書き方のノウハウをまとめました。

### Android改善プログラミング - TechBooster
<a href="https://booth.pm/ja/items/126671">
<img width="20%" src="https://qiita-image-store.s3.amazonaws.com/0/55138/aebf0488-fc01-093a-3dc3-ae8a3aa319cf.png">
</a>
- 「Espressoはアプリが長生きする秘訣」にEspressoの使い方や機能についてまとめました。