Upgrade to Pro — share decks privately, control downloads, hide ads and more …

AndroidX Test

Yuki Anzai
December 18, 2018

AndroidX Test

Yuki Anzai

December 18, 2018
Tweet

More Decks by Yuki Anzai

Other Decks in Technology

Transcript

  1. ͋Μ͍͟Ώ͖ • blog : Y.A.M の雑記帳 • y-anz-m.blogspot.com • twitter

    : @yanzm (やんざむ) • uPhyca Inc. (株式会社ウフィカ) • Google Developers Expert for Android
  2. 2種類のテスト • Local tests • test/ • JVM上 • Instrumented

    tests • androidTest/ • デバイス・エミュレータ上 https://developer.android.com/training/testing/unit-testing/
  3. AndroidX Test • minSdkVersion が 14, targetSdkVersion が 28 •

    androidx.core:core が追加 • androidx.test.ext:truth が追加 • androidx.test.ext.junit が追加
  4. AndroidX Test • Core • JUnit (AndroidJUnitRunner, JUnit Rules and

    JUnit extension) • Assertions (Truth extension) • Monitor • AndroidTestOrchestrator • Espresso
  5. AndroidJUnit4 • androidx.test.runner.AndroidJunit4 が deprecated にな り、代わりに androidx.test.ext.junit.runners.AndroidJUnit4 を使う import

    androidx.test.ext.junit.runners.AndroidJUnit4 @RunWith(AndroidJUnit4::class) class ExampleTest { "androidx.test.ext:junit:1.1.0"
  6. • Local tests → RobolectricTestRunner • Instrumented tests → AndroidJUnit4ClassRunner

    public final class AndroidJUnit4 extends Runner implements Filterable, Sortable { … private static String getRunnerClassName() { String runnerClassName = System.getProperty("android.junit.runner", null); if (runnerClassName == null) { // TODO: remove this logic when nitrogen is hooked up to always pass this property if (System.getProperty("java.runtime.name").toLowerCase().contains("android")) { return "androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner"; } else { return "org.robolectric.RobolectricTestRunner"; } } return runnerClassName; } AndroidJUnit4 の中で Runner を⾃動切り替え
  7. test/ @RunWith(AndroidJUnit4::class) class ExampleUnitTest { @Test fun test() { assertThat(TextUtils.isDigitsOnly("123")).isTrue()

    } } @RunWith(AndroidJUnit4::class) class ExampleInstrumentedUnitTest { @Test fun test() { assertThat(TextUtils.isDigitsOnly("123")).isTrue() } } androidTest/
  8. ApplicationProvider • テスト対象アプリのContext を取得する新しい⽅法 • 今まで Robolectric では • 今まで

    Instrumented tests では • テストコードの Context • テスト対象のアプリの Context "androidx.test:core:1.1.0" val context : Context = RuntimeEnvironment.application val context = InstrumentationRegistry.getContext() val context = InstrumentationRegistry.getTargetContext()
  9. ApplicationProvider • テスト対象アプリのContext を取得する新しい⽅法 • androidx.test.core.app.ApplicationProvider を使う • androidx.test.InstrumentationRegistry の

    getContext(), getTargetContext() は deprecated • Robolectric の RuntimeEnvironment.application は deprecated val appContext = ApplicationProvider.getApplicationContext<Context>() assertEquals("net.yanzm.jetpacksamples", appContext.packageName) "androidx.test:core:1.1.0"
  10. ActivityScenario • テストのために Activity のライフサイクルを進めることができる • onResume() の後 • onPause()

    の後かつ onStop() の前 • onStop() の後かつ onDestroy() の前 • onDestroy() の後 • Robolectric の ActivityController や Android Testing Support Library の ActivityTestRule の置き換え "androidx.test:core:1.1.0"
  11. "androidx.test:core:1.1.0" @RunWith(AndroidJUnit4::class) class MainActivityTest { @Test fun test() { val

    scenario = ActivityScenario.launch(MainActivity::class.java) scenario.onActivity { activity -> // activity は resumed : onResume() の後 assertThat(activity.getSomething()).isEqualTo("something") } scenario.moveToState(Lifecycle.State.STARTED) scenario.onActivity { activity -> // activity は paused : onPause() の後、 onStop() の前 } scenario.moveToState(Lifecycle.State.CREATED) scenario.onActivity { activity -> // activity は stopped : onStop() の後、 onDestroy() の前 } scenario.moveToState(Lifecycle.State.DESTROYED) // activity は destroyed and finished } } ライフサイクル状態の変更
  12. @RunWith(AndroidJUnit4::class) class LoginActivityTest { @Test fun test() { // GIVEN

    val username = "yanzm" val scenario = ActivityScenario.launch(LoginActivity::class.java) // WHEN onView(withId(R.id.usernameEditText)).perform(typeText(username)) scenario.recreate() // Activity を再⽣生成 // THEN onView(withId(R.id.usernameEditText)).check(matches(withText(username))) } } 再⽣成のテスト "androidx.test:core:1.1.0"
  13. import androidx.test.core.app.launchActivity val scenario = launchActivity<MainActivity>() "androidx.test:core-ktx:1.1.0" tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { kotlinOptions

    { jvmTarget = '1.8' } } cannot inline bytecode build with jvm target 1.7 into bytecode that is being build with jvm target 1.6 というエラーが出た場合、以下の設定が必要 core-ktx
  14. @RunWith(AndroidJUnit4::class) class MainActivityUnitTest { @Test fun test() { val scenario

    = launchActivity<MainActivity>() scenario.onActivity { activity -> // activity は resumed : onResume() の後 Truth.assertThat(activity.getSomething()).isEqualTo("something") } scenario.moveToState(Lifecycle.State.STARTED) scenario.onActivity { activity -> // activity は paused : onPause() の後、 onStop() の前 } scenario.moveToState(Lifecycle.State.CREATED) scenario.onActivity { activity -> // activity は stopped : onStop() の後、 onDestroy() の前 } scenario.moveToState(Lifecycle.State.DESTROYED) // activity は destroyed and finished } } JUnit (Robolectric) testImplementation "junit:junit:4.12" testImplementation "androidx.test:core-ktx:1.1.0" testImplementation "com.google.truth:truth:0.42" testImplementation "androidx.test.ext:truth:1.1.0" testImplementation "androidx.test.ext:junit-ktx:1.1.0" testImplementation "org.robolectric:robolectric:4.0.2" AndroidJUnit4 で OK
  15. @RunWith(AndroidJUnit4::class) class LoginActivityUnitTest { @Test fun test() { // GIVEN

    val username = "yanzm" val scenario = ActivityScenario.launch(LoginActivity::class.java) // WHEN onView(withId(R.id.usernameEditText)).perform(typeText(username)) scenario.recreate() // Activity を再⽣生成 // THEN onView(withId(R.id.usernameEditText)).check(matches(withText("yanzm"))) } } JUnit (Robolectric) testImplementation "junit:junit:4.12" testImplementation "androidx.test:core-ktx:1.1.0" testImplementation "com.google.truth:truth:0.42" testImplementation "androidx.test.ext:truth:1.1.0" testImplementation "androidx.test.ext:junit-ktx:1.1.0" testImplementation "org.robolectric:robolectric:4.0.2" testImplementation "androidx.test.espresso:espresso-core:3.1.1" AndroidJUnit4 で OK
  16. FragmentScenario • テストのために Fragment のライフサイクルを進めること ができる • ライフサイクルの状態の指定には Android Architecture

    Components の Lifecycle.State を利⽤ • DESTROYED に達したあとは Fragment を他の状態にでき ない • 再⽣成のテストには recreate() を使う "androidx.fragment:fragment-testing:1.1.0-alpha02"
  17. @RunWith(AndroidJUnit4::class) class MainFragmentTest { @Test fun test() { val scenario

    = FragmentScenario.launch(MainFragment::class.java) // or // val scenario = launchFragment<MainFragment>() scenario.onFragment { fragment -> // fragment は resumed : onResume() の後 assertThat(fragment.getSomething()).isEqualTo("something") } scenario.moveToState(Lifecycle.State.STARTED) scenario.onFragment { fragment -> // fragment は paused : onPause() の後、 onStop() の前 } scenario.moveToState(Lifecycle.State.CREATED) scenario.onFragment { fragment -> // fragment は stopped : onStop() の後、 onDestroy() の前 } scenario.moveToState(Lifecycle.State.DESTROYED) // fragment は destroyed and finished } }
  18. val args = bundleOf("name" to "yanzm") val scenario = FragmentScenario.launch(MainFragment::class.java,

    args) // or // val scenario = launchFragment<MainFragment>(args) scenario.onFragment { fragment -> BundleSubject.assertThat(fragment.arguments).string("name").isEqualTo("yanzm") } Launch with Bundle Launch with Container val scenario = FragmentScenario.launchInContainer(MainFragment::class.java) // or // val scenario = launchFragmentInContainer<MainFragment>() scenario.onFragment { fragment -> assertThat(fragment.id).isEqualTo(android.R.id.content) }
  19. Builder • ApplicationInfoBuilder, PackageInfoBuilder, MotionEventBuilder, PointerCoordsBuilder, PointerPropertiesBuilder val motionEvent =

    MotionEventBuilder.newBuilder() .setAction(MotionEvent.ACTION_DOWN) .setPointer(100f, 100f) .build() val applicationInfo = ApplicationInfoBuilder.newBuilder() .setName("Sample") .setPackageName("net.yanzm.sample") .build() val packageInfo = PackageInfoBuilder.newBuilder() .setPackageName("net.yanzm.sample") .setApplicationInfo(applicationInfo) .build() "androidx.test:core:1.1.0"
  20. Parcelables • Parcelable のテストのユーティリティ val person1 = Person("Android", 10) val

    person2 = Parcelables.forceParcel(person1, Person.CREATOR) assertThat(person1).isEqualTo(person2) "androidx.test:core:1.1.0" data class Person(val name: String, val age: Int) : Parcelable { … companion object CREATOR : Parcelable.Creator<Person> { … } }
  21. Truth • Google が開発している Java の Assertion ライブラリ http://google.github.io/truth/ "com.google.truth:truth:0.42"

    "androidx.test.ext:truth:1.1.0" import com.google.common.truth.Truth.assertThat import org.junit.Test class ExampleUnitTest { @Test fun test() { assertThat("123".isNotEmpty()).isTrue() assertThat("123").isEqualTo("123") assertThat("123").isNotEmpty() assertThat("123").contains("2") assertThat("123").startsWith("1") assertThat("123").endsWith("3") assertThat("123").hasLength(3) assertThat("123").isNotNull() } }
  22. Truth • フレームワークのクラス(Intent や Notification など)向 けの Assertions が android.test.ext:truth

    に⼊っている import androidx.test.ext.truth.content.IntentSubject.assertThat @RunWith(AndroidJUnit4::class) class ExampleUnitTest { @Test fun test() { val intent = Intent( Intent.ACTION_VIEW, Uri.parse("https://developer.android.com") ) assertThat(intent).hasAction(Intent.ACTION_VIEW) assertThat(intent).hasData(Uri.parse("https://developer.android.com")) } } "com.google.truth:truth:0.42" "androidx.test.ext:truth:1.1.0"
  23. NotificationActionSubject assertThat(Notification.Action action) NotificationSubject assertThat(Notification notification) PendingIntentSubject assertThat(PendingIntent intent) IntentSubject

    assertThat(Intent intent) BundleSubject assertThat(Bundle bundle) ParcelableSubject assertThat(Parcelable parcelable) MotionEventSubject assertThat(MotionEvent event) PointerCoordsSubject assertThat(MotionEvent.PointerCoords other) PointerPropertiesSubject assertThat(MotionEvent.PointerProperties other) Subject
  24. IntentCorrespondences val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://developer.android.com")) // action だけを⽐比較 assertThat(listOf(intent))

    .comparingElementsUsing(IntentCorrespondences.action()) .contains(Intent(Intent.ACTION_VIEW, Uri.parse("https://www.google.co.jp"))) // data だけを⽐比較 assertThat(listOf(intent)) .comparingElementsUsing(IntentCorrespondences.data()) .contains(Intent(Intent.ACTION_MAIN, Uri.parse("https://developer.android.com"))) // action と data を⽐比較 assertThat(listOf(intent)) .comparingElementsUsing( IntentCorrespondences.all( IntentCorrespondences.action(), IntentCorrespondences.data() ) ) .contains(Intent(Intent.ACTION_VIEW, Uri.parse("https://developer.android.com")))
  25. @RunWith(AndroidJUnit4::class) class ExampleUnitTest { @Test fun test() { assertThat(TextUtils.isDigitsOnly("123")).isTrue() }

    } Local unit tests testImplementation "junit:junit:4.12" testImplementation "androidx.test:core-ktx:1.1.0" // or "androidx.test:core:1.1.0" // AndroidJUnit4 に必要 testImplementation "androidx.test.ext:junit-ktx:1.1.0" // or "androidx.test.ext:junit:1.1.0" // AndroidJUnit4 経由での robolectric 実⾏行行に必要 testImplementation "org.robolectric:robolectric:4.0.2" // Truth 使うなら必要 testImplementation "com.google.truth:truth:0.42" testImplementation "androidx.test.ext:truth:1.1.0"
  26. @RunWith(AndroidJUnit4::class) class LoginActivityUnitTest { @Test fun test() { val scenario

    = launchActivity<LoginActivity>() onView(withId(R.id.usernameEditText)).perform(typeText("yanzm")) scenario.recreate() // Activity を再⽣生成 onView(withId(R.id.usernameEditText)).check(matches(withText("yanzm"))) } } Local Espresso tests testImplementation "junit:junit:4.12" testImplementation "androidx.test:core-ktx:1.1.0" // or "androidx.test:core:1.1.0" // AndroidJUnit4 に必要 testImplementation "androidx.test.ext:junit-ktx:1.1.0" // or "androidx.test.ext:junit:1.1.0" // AndroidJUnit4 経由での robolectric 実⾏行行に必要 testImplementation "org.robolectric:robolectric:4.0.2" testImplementation "androidx.test.espresso:espresso-core:3.1.1"
  27. @RunWith(AndroidJUnit4::class) class ExampleInstrumentedUnitTest { @Test fun test() { assertThat(TextUtils.isDigitsOnly("123")).isTrue() }

    } Instrumented unit tests androidTestImplementation "androidx.test:runner:1.1.1" androidTestImplementation "androidx.test:rules:1.1.1" // AndroidJUnit4 に必要 androidTestImplementation "androidx.test.ext:junit-ktx:1.1.0" // or "androidx.test.ext:junit:1.1.0" // Truth 使うなら必要 androidTestImplementation "com.google.truth:truth:0.42" androidTestImplementation "androidx.test.ext:truth:1.1.0"
  28. @RunWith(AndroidJUnit4::class) class LoginActivityTest { @Test fun test() { val scenario

    = ActivityScenario.launch(LoginActivity::class.java) onView(withId(R.id.usernameEditText)).perform(typeText("yanzm")) scenario.recreate() // Activity を再⽣生成 onView(withId(R.id.usernameEditText)).check(matches(withText("yanzm"))) } } Instrumented Espresso tests androidTestImplementation "androidx.test:runner:1.1.1" androidTestImplementation "androidx.test:rules:1.1.1" // AndroidJUnit4 に必要 androidTestImplementation "androidx.test.ext:junit-ktx:1.1.0" // or "androidx.test.ext:junit:1.1.0" androidTestImplementation "androidx.test.espresso:espresso-core:3.1.1"
  29. まとめ • AndroidJUnit4 クラスのパッケージが変更 • ApplicationProvider で Context を取得 •

    core の ActivityScenario, fragment-testing の FragmentScenario でライフサイクル状態を変更してテスト • Assertion ライブラリ Truth の拡張が androidx.test.ext:truth で提供 • Robolectric を使った Local tests と、on-device の Instrumented tests で同じテストが実⾏可能
  30. Core 最新 : Stable : 2018/12/13 androidx.test:core:1.1.0 1.1.0-beta01 - core-ktx

    artifact が追加 - Custom intents で Activity を起動するための 新しい ActivityScenario API - Activity の結果を受け取るための新しい ActivityScenario API - ActivityScenario が closeable になった androidx.test:core-ktx:1.1.0
  31. AndroidJUnitRunner and JUnit Rules 最新 : Stable : 2018/12/13 androidx.test:runner:1.1.1

    1.1.1 Rules - ActivityTestRule が deprecated になり、代わりに ActivityScenarioRule を使う androidx.test:rules:1.1.1
  32. Assertions 最新 : Stable : 2018/12/13 androidx.test.ext:truth:1.1.0 1.1.0 Truth -

    BundleSubject に bool(), parcelable(), parcelableAsType() が追加 JUnit - junit-ktx artifact が追加 androidx.test.ext:junit:1.1.0 androidx.test.ext:junit-ktx:1.1.0
  33. Espresso 最新 : Stable : 2018/12/13 "androidx.test.espresso:espresso-core:3.1.1" "androidx.test.espresso:espresso-contrib:3.1.1" "androidx.test.espresso:espresso-intents:3.1.1" "androidx.test.espresso:espresso-accessibility:3.1.1"

    "androidx.test.espresso:espresso-web:3.1.1" "androidx.test.espresso.idling:idling-concurrent:3.1.1" "androidx.test.espresso:espresso-idling-resource:3.1.1"
  34. MockitoJUnitRunner import android.content.Context import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith import

    org.mockito.Mock import org.mockito.Mockito.`when` import org.mockito.junit.MockitoJUnitRunner @RunWith(MockitoJUnitRunner::class) class ExampleUnitTest5 { @Mock private lateinit var mockContext: Context @Test fun test() { `when`(mockContext.getString(R.string.app_name)) .thenReturn("JetpackSamples") assertThat(mockContext.getString(R.string.app_name)) .isEqualTo("JetpackSamples") } } "org.mockito:mockito-core:2.19.0"