Edited at

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

Androidが10年の節目の年ですが

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

11月7-8日に開催されたAndroid Dev Summit '18の動画を見て、

Androidテストは来年やばいことになりそうだ!(←今年もやばかった (←今までも..))

と思ったので過去-現在-未来を一気に見れるようにまとめました。

2018年12月版です。


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

0.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に使用できます。

@RunWith(JUnit4.class) //省略可能

public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
:


Mockito

https://site.mockito.org/

Javaのモックフレームワークです。

Android自体ではサポートしていないものの、

サンプルコードやドキュメントなどでは使用が推奨されています。

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

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リリース)の説明があるので詳細はそちらで。

下記サンプルは参考程度に。

@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)

1.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

2.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に追記して有効にします。

コマンドラインの設定で有効にすることも可能です。


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指定

  3. perform(viewAction)でクリックやテキスト入力などのアクション設定

  4. check(viewAssertion)で状態や値を確認

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

また、IntentやLifecycleなど新機能を含めたサンプルは こちら にあります

@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


@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の予測変換で比較文がでるのもありがたいです。

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

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

androidTestImplementationtestImplementation に切り替えるだけで

同じソースコードでテストできるようになりました。


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'
:



@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」にて今後は同じテストコードを複数環境で実行できるよう取り組んでいるそうです。

スクリーンショット 2018-12-11 5.10.12.png

画像: Google I/O '18スライドより

ロードマップも公開されており

Unified Testing Platformの公開予定は2019年となっているとのこと。

スクリーンショット 2018-12-12 7.25.59.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