Androidが10年の節目の年ですが
去年からKotlin対応, Jetpack対応, AndroidX対応と怒涛の更新が続いています。
11月7-8日に開催されたAndroid Dev Summit '18の動画を見て、
Androidテストは来年やばいことになりそうだ!(←今年もやばかった (←今までも..))
と思ったので過去-現在-未来を一気に見れるようにまとめました。
2018年12月版です。
2018年までのAndroidテストまとめ
画像: 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ライブラリは使用できません。
JUnit4
Javaで開発された、プログラムにおいてUnitTestを行うフレームワークです。
AndroidStudioにはGUIのテストランナーがビルドインされています。
Androidライブラリに依存しないクラスのUnitTestに使用できます。
@RunWith(JUnit4.class) //省略可能
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
:
Mockito
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
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)
画像: Google I/O '17スライドよりInstrumentation test
と同義です。
Androidプロジェクトの androidTest
フォルダ内にあたります。
テスト対象のapk本体とテストコードapkをデバイスに転送してテストします。
(このためテスト実行は低速となります)
"Android Testing Support Library"とは本項の機能を指します。
AndroidJUnitRunner
画像: Google I/O '17スライドより
AndroidJUnitRunnerクラスは、後述のEspresso/UI Automatorの実行を含むJUnitテストランナーです。
クラスの前に @RunWith(AndroidJUnit4.class)
を付けて使用します。
AndroidJUnitRunnerを実行すると以下を行います。
- テスト対象のapkとテストapkをAndroidデバイスにロード
- テストを実行
- テスト結果をレポート
Android Test Orchestrator
Android Testを最適に実行するツールです。
Android Studio3.0+とFirebase Test Labにはプリインストール済み。
AndroidJUnitRunner 1.0+で使用可。以下の機能を提供します。
- Instrumentationの独自の呼び出し内で各アプリケーションのテストを実行できます。
-
clearPackageData
の設定で各テスト後にデバイスのCPUとメモリから状態削除可。 - クラッシュの隔離。 1つのテストがクラッシュしても他のテストは引き続き実行されます。
上記の通り、最近追加された機能ですがAndroidテストでは必須といえます。
Android Studio 3.0+では以下のように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テストを行うためのフレームワークです。
最も基本的なテスト方法は以下手順で行います。
-
ActivityTestRule
にてActivityを指定 - onView(matcher)でView指定
- perform(viewAction)でクリックやテキスト入力などのアクション設定
- 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テストを行うためのフレームワークです。
テスト対象のアプリ外で発生する、デバイス操作(回転やホームボタン)や他アプリでの操作
を含むテストを行うことができます。
@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
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
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
Robolectrics 4.0がandroidx.test1.0.0でのサポート対象となりました。
Robolectricsは公式サイトにリリースノートや機能のドキュメントがあります。
(= Androidドキュメント内にはありません)
4.0の特に大きな機能はAndroidテストのAndroidJUnitRunner, Espressoに対応したことです。
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
に切り替えるだけで
同じソースコードでテストできるようになりました。
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」にて今後は同じテストコードを複数環境で実行できるよう取り組んでいるそうです。
ロードマップも公開されており
Unified Testing Platformの公開予定は2019年となっているとのこと。
ビルドシステムに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
個人的に思うこと
来年の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