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

AndroidX Test

Avatar for Yuki Anzai Yuki Anzai
December 10, 2018

AndroidX Test

Android Dev Summit 2018報告会
https://connpass.com/event/109464/

Avatar for Yuki Anzai

Yuki Anzai

December 10, 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 ΍ Noti fi cation ͳͲʣ޲ ͚ͷ 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. Noti fi cationActionSubject assertThat(Noti fi cation.Action action) Noti fi cationSubject

    assertThat(Noti fi cation noti fi cation) 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"