2018年までのAndroidテスト総まとめ - 今年の変更と来年の対策

Androidが10年の節目の年ですが
去年からKotlin対応, Jetpack対応, AndroidX対応と怒涛の更新が続いています。

11月7-8日に開催された[Android  Dev Summit '18](https://developer.android.com/dev-summit/)の動画を見て、
Androidテストは来年やばいことになりそうだ！(←今年もやばかった (←今までも..))
と思ったので過去-現在-未来を一気に見れるようにまとめました。
2018年12月版です。

# 2018年までのAndroidテストまとめ

<img width="800" alt="0.png" src="https://qiita-image-store.s3.amazonaws.com/0/19692/23d22f29-8f66-b17a-392d-7f369be2bed9.png">
画像: Android Dev Summit '18スライドより

Androidテスト周辺は毎年いろいろなものが出たりなくなったり、正直分かりづらいです。

公式の「Fundamentals of Testing」やGoogle I/Oの以下動画に
しっかりまとまっているので、まだ見ていない方を見ることを強くおすすめします。

Fundamentals of Testing
https://developer.android.com/training/testing/fundamentals

YouTube: TDD on Android with the Android Testing Support Library (Google I/O '17)
https://youtu.be/pK7W5npkhho

YouTube: Frictionless Android testing: write once, run everywhere (Google I/O '18) 0:00 〜 19:00
https://youtu.be/wYMIadv9iF8


今知りたい方は以下をどうぞ！

## UnitTest (on JVM)

Androidプロジェクトの `test` フォルダ内にあたります。
JVM上で実行するので高速ですがAndroidライブラリは使用できません。

https://developer.android.com/training/testing/unit-testing/local-unit-tests

### JUnit4

https://junit.org/junit4/

Javaで開発された、プログラムにおいてUnitTestを行うフレームワークです。
AndroidStudioにはGUIのテストランナーがビルドインされています。

Androidライブラリに依存しないクラスのUnitTestに使用できます。

```java
@RunWith(JUnit4.class) //省略可能
public class ExampleUnitTest {
    @Test
    public void addition_isCorrect() {
        assertEquals(4, 2 + 2);
    }
:
```

### Mockito

https://site.mockito.org/

Javaのモックフレームワークです。
Android自体ではサポートしていないものの、
サンプルコードやドキュメントなどでは使用が推奨されています。

指定した値を返すモッククラスを作成することにより、他クラスに依存しないUnitTestにします。

```kotlin
private const val FAKE_STRING = "HELLO_WORLD"

@RunWith(MockitoJUnitRunner::class)
class UnitTestSample {

    @Mock
    private lateinit var mockContext: Context

    @Test
    fun readStringFromContext_LocalizedString() {
        `when`(mockContext.getString(R.string.hello_word))
                .thenReturn(FAKE_STRING)
        val myObjectUnderTest = ClassUnderTest(mockContext)
        val result: String = myObjectUnderTest.getHelloWorldString()
        assertEquals(result, FAKE_STRING)
    }
}
```
### Robolectrics

http://robolectric.org

RobolectricsはUnitTestのためにJVM上でAndroidをSimulateする仕組みです。
下記問題を解決します。

- JVM上でAndroidライブラリをすべてMockitoでモック化しようとすると大変
- Android Testは実際にデバイスを立ち上げて行うため低速

後述に超進化したRobolectrics 4.0 (10/25リリース)の説明があるので詳細はそちらで。
下記サンプルは参考程度に。

```kotlin
@RunWith(RobolectricTestRunner::class)
class RobolectricTest {
  @Test fun clickingOnTitle_shouldLaunchEditAction() {
    val activity = Robolectric.setupActivity(NoteListActivity::class.java)
    ShadowView.clickOn(activity.findViewById(R.id.title));
    assertThat(ShadowApplication.getInstance().peekNextStartedActivity().action)
            .isEqualTo("android.intent.action.EDIT")
  }
}
```

## Android Test (on Device/Emulator)

<img width="800" alt="1.png" src="https://qiita-image-store.s3.amazonaws.com/0/19692/85762827-b365-d346-3298-a8448003bea4.png">
画像: Google I/O '17スライドより

`Instrumentation test`と同義です。
Androidプロジェクトの `androidTest` フォルダ内にあたります。
テスト対象のapk本体とテストコードapkをデバイスに転送してテストします。
(このためテスト実行は低速となります)
"Android Testing Support Library"とは本項の機能を指します。

https://developer.android.com/training/testing/unit-testing/instrumented-unit-tests

### AndroidJUnitRunner

<img width="800" alt="2.png" src="https://qiita-image-store.s3.amazonaws.com/0/19692/285242e6-09b5-51d5-cec0-17b9cffbdbe1.png">
画像: Google I/O '17スライドより

https://developer.android.com/training/testing/junit-runner

AndroidJUnitRunnerクラスは、後述のEspresso/UI Automatorの実行を含むJUnitテストランナーです。
クラスの前に `@RunWith(AndroidJUnit4.class)`を付けて使用します。
AndroidJUnitRunnerを実行すると以下を行います。 

1. テスト対象のapkとテストapkをAndroidデバイスにロード
2. テストを実行
3. テスト結果をレポート

#### Android Test Orchestrator

https://developer.android.com/training/testing/junit-runner#using-android-test-orchestrator

Android Testを最適に実行するツールです。
Android Studio3.0+とFirebase Test Labにはプリインストール済み。
AndroidJUnitRunner 1.0+で使用可。以下の機能を提供します。

- Instrumentationの独自の呼び出し内で各アプリケーションのテストを実行できます。
- `clearPackageData` の設定で各テスト後にデバイスのCPUとメモリから状態削除可。
- クラッシュの隔離。 1つのテストがクラッシュしても他のテストは引き続き実行されます。

上記の通り、最近追加された機能ですがAndroidテストでは必須といえます。
Android Studio 3.0+では以下のようにgradleに追記して有効にします。
[コマンドラインの設定](https://developer.android.com/training/testing/junit-runner#ato-command-line)で有効にすることも可能です。


```build.gradle
android {
  defaultConfig {
   ...
   testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

   // The following argument makes the Android Test Orchestrator run its
   // "pm clear" command after each test invocation. This command ensures
   // that the app's state is completely cleared between tests.
   testInstrumentationRunnerArguments clearPackageData: 'true'
 }

  testOptions {
    execution 'ANDROIDX_TEST_ORCHESTRATOR'
  }
}

dependencies {
  androidTestImplementation 'androidx.test:runner:1.1.0'
  androidTestUtil 'androidx.test:orchestrator:1.1.0'
}
```

### Espresso

アプリ内で完結するUIテストを行うためのフレームワークです。

https://developer.android.com/training/testing/ui-testing/espresso-testing


最も基本的なテスト方法は以下手順で行います。

1.  `ActivityTestRule` にてActivityを指定
2. onView(matcher)でView指定
2. perform(viewAction)でクリックやテキスト入力などのアクション設定
3. check(viewAssertion)で状態や値を確認

※ `ActivityTestRule` はかつて `ActivityInstrumentationTestCase2` だったものです。2.1で廃止。


また、IntentやLifecycleなど新機能を含めたサンプルは [こちら](https://developer.android.com/training/testing/samples) にあります

```kotlin
@RunWith(AndroidJUnit4::class)
class OnDeviceTest {
  @get:Rule val rule = ActivityTestRule(NoteListActivity::class.java)

  @Test
  fun changeText_sameActivity() {
    // Type text and then press the button.
    onView(withId(R.id.editTextUserInput).perform(typeText(stringToBetyped), closeSoftKeyboard())
    onView(withId(R.id.changeTextBt)).perform(click())

    // Check that the text was changed.
    onView(withId(R.id.textToBeChanged)).check(matches(withText(stringToBetyped)))
  }

  @Test fun clickingOnTitle_shouldLaunchEditAction() {
    onView(withId(R.id.button)).perform(click())
    intended(hasAction(equalTo("android.intent.action.EDIT")))
  }
}
```

### UI Automator

アプリ外のUIテストを行うためのフレームワークです。

テスト対象のアプリ外で発生する、デバイス操作(回転やホームボタン)や他アプリでの操作
を含むテストを行うことができます。

https://developer.android.com/training/testing/ui-testing/uiautomator-testing

```kotlin

@RunWith(AndroidJUnit4.class)
@SdkSuppress(minSdkVersion = 18)
public class ChangeTextBehaviorTest {
    private UiDevice mDevice;

    @Before
    public void startMainActivityFromHomeScreen() {
        // Initialize UiDevice instance
        mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());

        // Start from the home screen
        mDevice.pressHome();

        // Wait for launcher
        final String launcherPackage = mDevice.getLauncherPackageName();
        assertThat(launcherPackage, notNullValue());
        mDevice.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)), 5000);
:
```

## 2018年までのAndroidテスト一覧

ざっくりこれまで(かつ現役の)フレームワーク・ツールを一覧にしました。
単語と概要が頭の中で結びついていただけたら本望です。

|名称|概要|
|:--|:--|
|JUnit4|Javaのテストフレームワーク(Assertion, Runner)|
|Mockito|Javaのモック作成|
|Robolectrics|JVM上でAndroidをSimulate|
|AndroidJUnitRunner|Androidテスト実行|
|Android Test Orchestrator| Androidテスト実行の最適化ツール|
|Espresso|アプリ内のUIテスト|
|UI Automator|アプリ外のUIテスト|

# 2018年の変更 - AndroidX Testing Library

2018年11月、AndroidDevSummitにて「Testing Rebooted (with AndroidX Test)」という発表がありました。

Testing Rebooted (with AndroidX Test) (Android Dev Summit '18)
https://youtu.be/4m2yYSTdvIg

AndroidX Testing Libraryが1.0になり、
Core(Application, Activity, Fragment)のテスト方法や、
新たにサポートされるライブラリ「Truth」「Robolectrics 4.0」について紹介されました。

セットアップ方法は以下資料になります。
androidxパッケージとなりました。

参考) Set up project for AndroidX Test
https://developer.android.com/training/testing/set-up-project

``` app.gradle
dependencies {
  // Core library
  androidTestImplementation 'androidx.test:core:1.0.0'

  // AndroidJUnitRunner and JUnit Rules
  androidTestImplementation 'androidx.test:runner:1.1.0'
  androidTestImplementation 'androidx.test:rules:1.1.0'

  // Assertions
  androidTestImplementation 'androidx.test.ext:junit:1.0.0'
  androidTestImplementation 'androidx.test.ext:truth:1.0.0'
  androidTestImplementation 'com.google.truth:truth:0.42'

  // Espresso dependencies
  androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
  androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.1.0'
  androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.0'
  androidTestImplementation 'androidx.test.espresso:espresso-accessibility:3.1.0'
  androidTestImplementation 'androidx.test.espresso:espresso-web:3.1.0'
  androidTestImplementation 'androidx.test.espresso.idling:idling-concurrent:3.1.0'

  // The following Espresso dependency can be either "implementation"
  // or "androidTestImplementation", depending on whether you want the
  // dependency to appear on your APK's compile classpath or the test APK
  // classpath.
  androidTestImplementation 'androidx.test.espresso:espresso-idling-resource:3.1.0'
}
```

「Truth」「Robolectrics 4.0」の概要は以下となります。

### Truth
http://google.github.io/truth/

google発、比較文を書きやすくするassertionライブラリです。

素のassertEqualsと比較してアサートメッセージが追加できたり、
どれが実行結果でどれが期待値か、可読性が上るメリットがあります。
IDEの予測変換で比較文がでるのもありがたいです。

```kotlin
val string = "awesome";
assertThat(string).startsWith("awe");
assertWithMessage("Without me, it's just aweso").that(string).contains("me");
```

また、Truthでは各Objectの比較が容易になっており、
例えばStringでは`isEmpty()`や `startsWith(string)`が使用できます。
これは型別にSubjectという比較方法の提供で実現しており、
例えばStringでは以下StringSubjectがあります。
http://google.github.io/truth/api/latest/com/google/common/truth/StringSubject.html

今回、AndroidXではTruthのExtensionにて、IntentやMotionEventのSubjectをサポートしています。
使用できるSubjectについては以下に掲載されています。
https://developer.android.com/training/testing/fundamentals#assertions

```kotlin
assertThat(object).hasFlags(FLAGS)
assertThat(object).doesNotHaveFlags(FLAGS)
assertThat(intent).hasData(URI)
assertThat(extras).string(string_key).equals(EXPECTED)
```
### Robolectrics 4.0

http://robolectric.org

Robolectrics 4.0がandroidx.test1.0.0でのサポート対象となりました。
Robolectricsは公式サイトにリリースノートや機能のドキュメントがあります。
(= Androidドキュメント内にはありません)

4.0の特に大きな機能はAndroidテストのAndroidJUnitRunner, Espressoに対応したことです。

http://robolectric.org/blog/2018/10/25/robolectric-4-0/

> With today’s release of Robolectric 4.0 and androidx.test 1.0.0, both testing environments are converging on a set of common test APIs. Robolectric now supports the AndroidJUnit4 test runner, ActivityTestRule, and Espresso for interacting with UI components.

これによって、`app.gradle`の
`androidTestImplementation` を `testImplementation` に切り替えるだけで
同じソースコードでテストできるようになりました。

``` app.gradle
dependencies {
  // Core library
  testImplementation 'androidx.test:core:1.0.0'

  // AndroidJUnitRunner and JUnit Rules
  testImplementation 'androidx.test:runner:1.1.0'
  testImplementation 'androidx.test:rules:1.1.0'

  // Assertions
  testImplementation 'androidx.test.ext:junit:1.0.0'
  testImplementation 'androidx.test.ext:truth:1.0.0'
  testImplementation 'com.google.truth:truth:0.42'

  // Robolectric
  testImplementation 'org.robolectric:robolectric:4.0.2'

  // Espresso dependencies
  testImplementation 'androidx.test.espresso:espresso-core:3.1.0'
:
```

```kotlin

@RunWith(AndroidJUnit4::class)
class OnDeviceTest {
  @get:Rule val rule = ActivityTestRule(NoteListActivity::class.java)

  @Test
  fun changeText_sameActivity() {
    // Type text and then press the button.
    onView(withId(R.id.editTextUserInput).perform(typeText(stringToBetyped), closeSoftKeyboard())
    onView(withId(R.id.changeTextBt)).perform(click())

    // Check that the text was changed.
    onView(withId(R.id.textToBeChanged)).check(matches(withText(stringToBetyped)))
  }

  @Test fun clickingOnTitle_shouldLaunchEditAction() {
    onView(withId(R.id.button)).perform(click())
    intended(hasAction(equalTo("android.intent.action.EDIT")))
  }
}
```
「同じソースコードでテストできる」は重要なキーワードです！
ぜひ、このまま次の「2019年に起こること - Nitrogen Project」まで読み進めてください。

# 2019年に起こること - Nitrogen Project

以下動画でNitrogen Projectについて説明されています。

Frictionless Android testing: write once, run everywhere (Google I/O '18) 19:01 〜
https://youtu.be/wYMIadv9iF8?t=1141

Testing Android Apps at Scale with Nitrogen (Android Dev Summit '18)
https://youtu.be/-_kZC29sWAo

Androidテストは現状、実行環境(JVM, 実機, Firebaseなどのクラウド端末)を意識してコードを区別する必要がありました。
上記のEspressoとRobolectrics 4.0のサンプルコードが同じになったように、
「Nitrogen Project」にて今後は同じテストコードを複数環境で実行できるよう取り組んでいるそうです。

<img width="800" alt="スクリーンショット 2018-12-11 5.10.12.png" src="https://qiita-image-store.s3.amazonaws.com/0/19692/0a0f89c6-2f2d-28f0-4cc1-2783856aa9de.png">
画像: Google I/O '18スライドより

ロードマップも公開されており
Unified Testing Platformの公開予定は2019年となっているとのこと。

<img width="800" alt="スクリーンショット 2018-12-12 7.25.59.png" src="https://qiita-image-store.s3.amazonaws.com/0/19692/0dc00126-7f17-f48d-f570-11d6cd04a009.png">
画像: Android Dev Summit '18スライドより

ビルドシステムにGradle, Bazelを使うとのことなので覚えておいたほうが良さげです。
また、Firebase Test LabもNitrogen Projectに統合予定とのこと。

### Bazel
https://docs.bazel.build/versions/master/bazel-overview.html
https://bazel.build/roadmaps/android.html
### Firebase Test Lab
https://developer.android.com/studio/test/

# 個人的に思うこと

来年のAndroidテスト関連、個人的にこんなことを思いました。

- test/androidTestフォルダがどっちでもよくなりつつある
  - 今後はどちらか消えるのでは?
  - Robolectricsをエンジニアが意識することはなくなりそう?
- MockitoのKotlin版サポートとかしないかな
- Spek公式サポートまだかな
- Robolectricsのアイコン、もこもこしてるとけど電気羊の夢..?

# 参考にしました

mixi developers - Android Testは"Write once, run everywhere."の夢を見るか
https://medium.com/mixi-developers/android-test-%E3%81%AF-write-once-run-everywhere-%E3%81%AE%E5%A4%A2%E3%82%92%E8%A6%8B%E3%82%8B%E3%81%8B-4b6e179928f6

AndroidX Test / yanzm
https://speakerdeck.com/yanzm/androidx-test