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

Robolectricの限界を理解してUIテストを高速に実行しよう / Let's run UI Test faster with understanding limit of Robolectric

Robolectricの限界を理解してUIテストを高速に実行しよう / Let's run UI Test faster with understanding limit of Robolectric

DroidKaigi 2020で発表予定だった資料「Robolectricの限界を理解してUIテストを高速に実行しよう」のスライドです。
DeNA.apk #2【カンファレンス中止でも登壇はしたい】Day2」でライブ配信します。

サンプルコードURL: https://github.com/sumio/robolectric-espresso-samples

TOYAMA Sumio

March 16, 2020
Tweet

More Decks by TOYAMA Sumio

Other Decks in Programming

Transcript

  1. Robolectric
    UI

    2020.03.17 (DeNA.apk #2 Day 2)
    (sumio_tym)

    View Slide

  2. $
    2
    p : # (TOYAMA Sumio)
    @sumio_tym (Twitter) / @sumio (GitHub)
    p : DeNA SWET
    (Software Engineer in Test)
    p :

    p : Android
    "!
    https://peaks.cc/sumio_tym/android_testing

    View Slide

  3. )

    3
    Espresso-
    Robolectric'

    /%

    p Robolectric!'*
    p Robolectric.+1$0 "#&
    p Robolectric'(,'(,
    p Robolectric' UI

    View Slide

  4. %)
    4
    p .+
    p Robolectric

    p Espresso&
    Local Test
    p Espresso- "* (
    p ,$'#Android Jetpack Components
    p ,$'#Espresso API
    p Robolectric!UI

    View Slide

  5. %)
    5
    p .+
    p Robolectric

    p Espresso&
    Local Test
    p Espresso- "* (
    p ,$'#Android Jetpack Components
    p ,$'#Espresso API
    p Robolectric!UI

    View Slide



  6. 6
    This image is reproduced from work (https://d.android.com/training/testing/fundamentals) created and shared by the Android Open
    Source Project (https://d.android.com/license) and used according to terms described in the Creative Commons 2.5 Attribution License
    (https://creativecommons.org/licenses/by/2.5/).

    View Slide



  7. 7
    This image is reproduced from work (https://d.android.com/training/testing/fundamentals) created and shared by the Android Open
    Source Project (https://d.android.com/license) and used according to terms described in the Creative Commons 2.5 Attribution License
    (https://creativecommons.org/licenses/by/2.5/).
    Local Test
    PC
    Instrumented Test
    Android




    UI Tests

    Integration Tests
    Unit Tests

    View Slide

  8. Local TestInstrumented Test
    8
    Local Test Instrumented Test

    PC (JVM) Android


    Android API
    (Espresso API)

    src/test src/androidTest

    View Slide

  9. Local Test
    Instrumented Test

    9
    Local Test Instrumented Test
    PC (JVM) Android


    Android API
    (Espresso API)

    src/test src/androidTest

    View Slide

  10. +25-Robolectric
    10
    p JVM# Android!',
    (http://robolectric.org/)
    p *&!: 4.3.1
    p 3rd party3/Android)0
    p $Instrumented/Local Test1% (

    Robolectric.4"(Project Nitrogen)
    ()
    () Copyright 6 2010 Xtreme Labs, Pivotal Labs and Google Inc.
    This logo image (https://github.com/robolectric/robolectric/blob/master/images/robolectric-horizontal.png) is
    licensed under The MIT License (https://github.com/robolectric/robolectric/blob/master/LICENSE).

    View Slide

  11. $*-%
    Robolectric
    11
    p JVMAndroid


    (http://robolectric.org/)
    p #
    : 4.3.1
    p 3rd party
    +'
    Android
    "(
    p
    Instrumented/Local Test
    ) !


    Robolectric
    &,
    (Project Nitrogen)
    ()
    () Copyright
    .
    2010 Xtreme Labs, Pivotal Labs and Google Inc.
    This logo image (https://github.com/robolectric/robolectric/blob/master/images/robolectric-horizontal.png) is
    licensed under The MIT License (https://github.com/robolectric/robolectric/blob/master/LICENSE).

    View Slide

  12. Local TestRobolectric 3.x
    12
    Local Test /w
    Robolectric 3.x
    Instrumented Test

    PC (JVM) Android

    Android API
    (Espresso API)


    src/test src/androidTest

    View Slide

  13. Local TestRobolectric 4.x

    13
    Local Test /w
    Robolectric 4.x
    Instrumented Test

    PC (JVM) Android

    Android API
    (Espresso API)


    src/test src/androidTest

    View Slide

  14. Robolectric 4.x
    14
    Local Test UI Test

    p
    UI Test

    p UI Test

    ()
    This image is a modification based on work
    (https://d.android.com/training/testing/fundamentals ) created and shared
    by the Android Open Source Project (https://d.android.com/license) and
    used according to terms described in the Creative Commons 2.5 Attribution
    License (https://creativecommons.org/licenses/by/2.5/).

    View Slide

  15. Robolectric 4.xEspresso Support*1
    15
    p Robolectric 4.x Espresso Support
    p 74UI Test).

    p Robolectric32( ) 5+%/
    p "!$0
    p #!&6-'
    ,

    View Slide

  16. Robolectric 4.x(
    16
    p Robolectric
    &

    p *% "' $&

    p Robolectric!
    (#))
    p Robolectric(#))

    $,- +

    View Slide

  17. %)
    17
    p .+
    p Robolectric

    p Espresso&
    Local Test
    p Espresso- "* (
    p ,$'#Android Jetpack Components
    p ,$'#Espresso API
    p Robolectric!UI

    View Slide

  18. Robolectric
    (05)
    18
    p JVM% Android! #)-
    !" (http://robolectric.org/)
    p ,( #: 4.3.1
    p 3rd party3/Android+1!"
    p &Instrumented/Local Test2' *
    Robolectric.4$(Project Nitrogen)
    ()
    () Copyright 6 2010 Xtreme Labs, Pivotal Labs and Google Inc.
    This logo image (https://github.com/robolectric/robolectric/blob/master/images/robolectric-horizontal.png) is
    licensed under The MIT License (https://github.com/robolectric/robolectric/blob/master/LICENSE).

    View Slide

  19. Robolectric: Android!,)
    19
    1. Android
    (jar)!
    https://mvnrepository.com/artifact/org.robolectric/android-all
    2. ! Android%-
    3. %#$
    *
    p Robolectric&'Shadow
    Shadow
    p Shadow+"(

    View Slide

  20. Robolectric: Shadow
    20
    p Android +

    #+$(
    p native*

    p Android*

    p etc.
    p ", API !&
    p ': logcat )%
    ShadowLog.stream = System.out

    View Slide

  21. Robolectric: 2
    (0/.6
    21
    ( @Before
    p 4'#!%"onCreate1
    p static$ (+&,-)
    p JVM5SQLite(3(sqlite4java))*
    p $ ( )

    View Slide

  22. Robolectric: !
    22

    (LEGACY)
    p
    ( )
    p (AsyncTask
    )
    ( )

    View Slide

  23. Robolectric:
    23
    &%(PAUSED)
    p !(
    p ShadowLooper.getShadowMainLooper().idle()
    p SystemClock )' $

    SystemClock.sleep()( * #"
    p !(
    )

    View Slide

  24. Robolectric: LEGACY# PAUSED#
    24
    Looper Mode46Robolectric4.3PAUSED2'
    LEGACY
    p $!(/0& 5+
    p )-"%* 3
    UI,
    PAUSED
    p )-"%* .1

    p EspressoUI

    View Slide

  25. Robolectric: LEGACY(%PAUSED(%
    25
    Looper ModeRobolectric4.3 PAUSED9,
    LEGACY
    p )&"$#!$-67+=1
    p .3 '*/
    ;
    UI
    2
    PAUSED
    p .3 '*/58
    p EspressoUI#!$

    0>
    PAUSED
    4:

    View Slide

  26. Robolectric:
    26
    p Robolectric "(
    p JVM! Android !
    p Android & Shadow )+
    p $"
    p Espresso
    PAUSED Looper Mode
    p
    #'%&"

    p '%
    $(,*$-
    p
    #

    View Slide

  27. %)
    27
    p .+
    p Robolectric

    p Espresso&
    Local Test
    p Espresso- "* (
    p ,$'#Android Jetpack Components
    p ,$'#Espresso API
    p Robolectric!UI

    View Slide


  28. : build.gradle
    28
    android {
    ...
    testOptions {
    unitTests.includeAndroidResources = true
    }
    } Robolectric

    View Slide



  29. : ()
    29

    Latest Stable
    Version()
    androidx.test:core-ktx 1.2.0
    androidx.test.ext:junit-ktx 1.1.1
    androidx.test.espresso:espresso-contrib 3.2.0
    org.robolectric:robolectric 4.3.1

    testImplementation
    ()2020/03/01

    View Slide


  30. :
    30
    p src/test

    p src/androidTest

    View Slide

  31. :


    31
    @RunWith(AndroidJUnit4::class)
    @LooperMode(LooperMode.Mode.PAUSED)
    class MyRobolectricTest {
    @get:Rule
    val activityScenarioRule =
    activityScenarioRule()
    ...
    }
    PAUSED Looper Mode

    ActivityTestRule

    View Slide

  32. :
    32
    p targetSdkVersion29JDK9
    p JDK8Robolectric
    28
    @RunWith(AndroidJUnit4::class)
    @LooperMode(LooperMode.Mode.PAUSED)
    @Config(sdk = 28)
    class MyRobolectricTest { ... }

    View Slide

  33. :

    33
    p Local TestRobolectricEspresso
    OK
    p
    p Instrumented Test
    p PAUSED Looper Mode
    p ActivityTestScenario
    p SDK

    View Slide

  34. %)
    34
    p .+
    p Robolectric

    p Espresso&
    Local Test
    p Espresso- "* (
    p ,$'#Android Jetpack Components
    p ,$'#Espresso API
    p Robolectric!UI

    View Slide

  35. 9' : Espresso(.)0/8*57;
    35
    Instrumented Robolectric
    $&% #
    AsyncTask
    IdlingResource
    <)06-
    31>+=?9

    Espresso(.)0/8 A
    DroidKaigi 2018,2
    Espresso!"#)06-:( http://bit.ly/2QdCeBt )4@

    View Slide

  36. : Robolectric
    36
    !1: View'(

    p +
    p Instrumented Test
    $#

    !2: RobolectricEspresso"&
    p Instrumented Test
    $#
    p Espresso ,*(OSS)
    p Robolectric )%

    View Slide

  37. - %: Robolectric- %
    37
    !1: View'(

    p +'(
    p Instrumented Test !$#

    !2: RobolectricEspresso"&
    p Instrumented Test !$#
    p Espresso*,*(OSS)+)
    p Robolectric&, )%
    $"#
    '(/.

    View Slide

  38. :

    38
    UiController

    loopMainThreadUntilIdle()
    p
    META-INF/services/
    androidx.test.platform.ui.UiController
    p Espresso : UiControllerImpl
    p Robolectric : LocalUiController

    View Slide

  39. : Robolectric
    39
    LocalUiController.loopMainThreadUntilIdle()
    @Override
    public void loopMainThreadUntilIdle() {
    shadowMainLooper().idle();
    }



    View Slide

  40. : Espresso
    40
    UiControllerImpl.loopMainThreadUntilIdle()
    ...
    do {
    if (!asyncIdle.isIdleNow()) { ... }
    if (!compatIdle.isIdleNow()) { ... }
    if (!dynamicIdle.isIdleNow()) { ... }
    dynamicIdle = loopUntil(..., dynamicIdle);
    } while (!asyncIdle.isIdleNow()
    || !compatIdle.isIdleNow()
    || !dynamicIdle.isIdleNow());

    View Slide


  41. : Espresso

    41
    ...
    do {
    if (!asyncIdle.isIdleNow()) { ... }
    if (!compatIdle.isIdleNow()) { ... }
    if (!dynamicIdle.isIdleNow()) { ... }
    dynamicIdle = loopUntil(..., dynamicIdle);
    } while (!asyncIdle.isIdleNow()
    || !compatIdle.isIdleNow()
    || !dynamicIdle.isIdleNow());

    UiControllerImpl.loopMainThreadUntilIdle()

    View Slide

  42. :
    loopMain...
    42
    class IdlingLocalUiController
    extends LocalUiController { ...
    public void loopMainThreadUntilIdle() {
    ...
    do {
    ...
    if (!dynamicIdle.isIdleNow()) { ... }
    dynamicIdle = loopUntil(..., dynamicIdle);
    ShadowLooper.shadowMainLooper().idle();
    } while (!dynamicIdle.isIdleNow());
    } }

    View Slide

  43. : loopMain...

    43
    class IdlingLocalUiController
    extends LocalUiController { ...
    public void loopMainThreadUntilIdle() {
    ...
    do {
    ...
    if (!dynamicIdle.isIdleNow()) { ... }
    dynamicIdle = loopUntil(..., dynamicIdle);
    ShadowLooper.shadowMainLooper().idle();
    } while (!dynamicIdle.isIdleNow());
    } }
    Robolectric

    View Slide

  44. class IdlingLocalUiController
    extends LocalUiController { ...
    public void loopMainThreadUntilIdle() {
    ...
    do {
    ...
    if (!dynamicIdle.isIdleNow()) { ... }
    dynamicIdle = loopUntil(..., dynamicIdle);
    ShadowLooper.shadowMainLooper().idle();
    } while (!dynamicIdle.isIdleNow());
    } }
    : loopMain...

    44
    loopMainThreadUntilIdle

    View Slide

  45. class IdlingLocalUiController
    extends LocalUiController { ...
    public void loopMainThreadUntilIdle() {
    ...
    do {
    ...
    if (!dynamicIdle.isIdleNow()) { ... }
    dynamicIdle = loopUntil(..., dynamicIdle);
    ShadowLooper.shadowMainLooper().idle();
    } while (!dynamicIdle.isIdleNow());
    } }
    : loopMain...

    45
    Espresso
    dynamicIdle

    View Slide

  46. class IdlingLocalUiController
    extends LocalUiController { ...
    public void loopMainThreadUntilIdle() {
    ...
    do {
    ...
    if (!dynamicIdle.isIdleNow()) { ... }
    dynamicIdle = loopUntil(..., dynamicIdle);
    ShadowLooper.shadowMainLooper().idle();
    } while (!dynamicIdle.isIdleNow());
    } }
    : loopMain...
    46
    1

    View Slide

  47. :
    47
    robolectric-4.3.1.jar

    META-INF/services/
    androidx.test.platform.ui.UiController
    - org.robolectric.android.internal.LocalUiController
    + androidx.test.espresso.base.IdlingLocalUiController

    View Slide

  48. - org.robolectric.android.internal.LocalUiController
    + androidx.test.espresso.base.IdlingLocalUiController
    : .
    48
    robolectric-4.3.1.jar
    META-INF/services/
    androidx.test.platform.ui.UiController
    .

    View Slide

  49. :

    49
    - final Message m = getNextMessage();
    + Duration nextScheduledTaskTime =
    + shadowOf(Looper.myLooper())
    + .getNextScheduledTaskTime();
    ...
    - m.getTarget().dispatchMessage(m);
    + Thread.sleep(nextScheduledTaskTime.toMillis());
    + shadowOf(Looper.myLooper()).runToNextTask();

    View Slide


  50. :
    50
    - final Message m = getNextMessage();
    + Duration nextScheduledTaskTime =
    + shadowOf(Looper.myLooper())
    + .getNextScheduledTaskTime();
    ...
    - m.getTarget().dispatchMessage(m);
    + Thread.sleep(nextScheduledTaskTime.toMillis());
    + shadowOf(Looper.myLooper()).runToNextTask();

    View Slide

  51. (:
    51
    - final Message m = getNextMessage();
    + Duration nextScheduledTaskTime =
    + shadowOf(Looper.myLooper())
    + .getNextScheduledTaskTime();
    ...
    - m.getTarget().dispatchMessage(m);
    + Thread.sleep(nextScheduledTaskTime.toMillis());
    + shadowOf(Looper.myLooper()).runToNextTask();
    )

    View Slide

  52. : )
    52
    - final Message m = getNextMessage();
    + Duration nextScheduledTaskTime =
    + shadowOf(Looper.myLooper())
    + .getNextScheduledTaskTime();
    ...
    - m.getTarget().dispatchMessage(m);
    + Thread.sleep(nextScheduledTaskTime.toMillis());
    + shadowOf(Looper.myLooper()).runToNextTask();
    )

    (

    View Slide

  53. 5 %: (1)+9 */.-
    53
    sleep()

    p (4!# %
    $: 3"!#

    1. 2,78
    Robolectric 603-

    2. Espresso5 %
    ( $603)
    3. "
    AppNotIdleException &'

    View Slide

  54. A.": /?04O
    54
    E8 https://git.io/Jv2ej >K
    (commit B" <)
    (1)Robolectric*9Pull Request:2)
    MD04,36@-J

    LH http://bit.ly/2TSsKxQ #>K
    +;URL/?04N 5I%(&CG
    *&($'=F!0457

    View Slide

  55. +: * !#
    55
    p https://git.io/Jv2ej ,-%
    p &
    $'.
    README"
    IdlingResource )Robolectric*
    (/ ( http://bit.ly/2IKBGPD )

    View Slide

  56. 0 #: /+
    56
    p Instrumented Test),
    IdlingResource

    p AsyncTask&. %#:
    ShadowPausedAsyncTask.overrideExecutor
    IdlingThreadPoolExecutor*'
    p UI AutomatorUiDevice.wait()
    1$!"
    -(

    View Slide

  57. 8':
    57
    p RobolectricEspresso3
    $&% #4<
    8'
    p Robolectric/9 IdlingResource(671
    p ;+):-*0 https://git.io/Jv2ej 5=
    p Instrumented Test.2
    IdlingResource3 Espresso!"#,
    p AsyncTask(671

    View Slide

  58. %)
    58
    p .+
    p Robolectric

    p Espresso&
    Local Test
    p Espresso- "* (
    p ,$'#Android Jetpack Components
    p ,$'#Espresso API
    p Robolectric!UI

    View Slide

  59. Jetpack:
    59
    RobolectricUI

    Robolectric↓
    Android Architecture Component 20197
    by Nozomi Takuma @ Android Test Night #7 (2019/07/25)
    http://bit.ly/32YCauI

    View Slide

  60. Jetpack: JetpackRobolectric


    60
    Jetpack
    p LiveData, Room, WorkManager, etc.
    Robolectric

    p Room, WorkManager

    View Slide

  61. Jetpack:
    61
    p RobolectricIdlingResource

    Instrumented Test
    p 2
    p TaskExecutorWithIdlingResourceRule
    p DataBindingIdlingResource

    View Slide

  62. Jetpack :
    62
    TaskExecutorWithIdlingResourceRule
    https://git.io/Jv2Ud
    p ArchTaskExecutor(AAC)


    p

    @get:Rule
    val executorRule =
    TaskExecutorWithIdlingResourceRule()

    View Slide

  63. Jetpack: -1
    63
    DataBindingIdlingResource
    https://git.io/Jv2UF
    p Data Binding
    (Fragment)
    p DataBinding

    View Slide

  64. Jetpack :
    -2
    64
    val dbIdlingResource = DataBindingIdlingResource()
    @Before fun setUp() {
    val scenario = activityScenarioRule.scenario
    dbIdlingResource.monitorActivity(scenario)
    IdlingRegistry.getInstance()
    .register(dbIndlingResource) }
    @After fun tearDown() {
    IdlingRegistry.getInstance()
    .unregister(dbIndlingResource) }

    View Slide

  65. Jetpack: -3
    65
    val dbIdlingResource = DataBindingIdlingResource()
    @Before fun setUp() {
    val scenario = activityScenarioRule.scenario
    dbIdlingResource.monitorActivity(scenario)
    IdlingRegistry.getInstance()
    .register(dbIndlingResource) }
    @After fun tearDown() {
    IdlingRegistry.getInstance()
    .unregister(dbIndlingResource) }



    View Slide

  66. Jetpack: $!&-4
    66
    val dbIdlingResource = DataBindingIdlingResource()
    @Before fun setUp() {
    val scenario = activityScenarioRule.scenario
    dbIdlingResource.monitorActivity(scenario)
    IdlingRegistry.getInstance()
    .register(dbIndlingResource) }
    @After fun tearDown() {
    IdlingRegistry.getInstance()
    .unregister(dbIndlingResource) }


    # "%

    View Slide

  67. Jetpack: Robolectric/
    67

    OK

    DataBinding
    LifeCycle
    LiveData
    Navigation (NavigationView
    )
    Paging (
    )
    Room
    ViewModel
    WorkManager
    SavedState

    View Slide

  68. Jetpack: Robolectric /

    68

    OK

    DataBinding
    LifeCycle
    LiveData
    Navigation (NavigationView)
    Paging ()
    Room
    ViewModel
    WorkManager
    SavedState



    View Slide

  69. Jetpack: Room

    69
    : Robolectric D
    p
    DB :
    p static R
    p
    Room.databaseBuilder()
    :
    p DBDAO
    :

    View Slide

  70. Jetpack
    : Room
    (Android Sunflower)
    70
    @Database(...)
    abstract class AppDatabase : RoomDatabase() {
    abstract fun plantDao(): PlantDao
    companion object {
    @Volatile
    private var instance: AppDatabase? = null
    fun getInstance(ctx: Context): AppDatabase {
    return instance ?: synchronized(this) {
    instance ?: buildDatabase(ctx).also {
    instance = it }
    } }

    View Slide

  71. Jetpack: Room (Android Sunflower)

    71
    // companion object
    private fun buildDatabase(ctx: Context) = Room
    .databaseBuilder(ctx, AppDatabase::class.java,
    DATABASE_NAME)...build()
    @VisibleForTesting(otherwise = NONE)
    fun clear() { instance = null }
    ...

    View Slide

  72. Jetpack : Room (Android Sunflower)
    72
    // companion object
    private fun buildDatabase(ctx: Context) = Room
    .databaseBuilder(ctx, AppDatabase::class.java,
    DATABASE_NAME)...build()
    @VisibleForTesting(otherwise = NONE)
    fun clear() { instance = null }
    ...
    )( )

    View Slide

  73. Jetpack : Room (Android Sunflower)
    73
    // B A
    class TestApplication : Application() {
    override fun onCreate() {
    ...
    val db = AppDatabase.getInstance(this)
    PlantRepository.updateDao(db.plantDao())
    ...
    }
    }
    P D
    P O
    ) (

    View Slide

  74. Jetpack: Room(Android Sunflower)
    74
    //
    @RunWith(AndroidJUnit4::class)
    @LooperMode(LooperMode.Mode.PAUSED)
    @Config(application = TestApplication::class)
    class RobolectricGardenActivityTest2 {
    ...
    @After fun tearDown() {
    val appDatabase = AppDatabase.getInstance(
    ApplicationProvider.getApplicationContext())
    appDatabase.close()
    AppDatabase.clear() } }

    View Slide

  75. Jetpack: Room!(Android Sunflower)#
    75
    //
    @RunWith(AndroidJUnit4::class)
    @LooperMode(LooperMode.Mode.PAUSED)
    @Config(application = TestApplication::class)
    class RobolectricGardenActivityTest2 {
    ...
    @After fun tearDown() {
    val appDatabase = AppDatabase.getInstance(
    ApplicationProvider.getApplicationContext())
    appDatabase.close()
    AppDatabase.clear() } }


    "

    View Slide

  76. Jetpack".: WorkManager".
    )0
    76
    ): WorkManager
    p 345Work%
    (&
    p static-&/,6(!
    #!: '"$
    p '%$21 (http://bit.ly/332dPUP)
    src/test/AndroidManifest.xml *#21
    p WorkManagerTestInitHelper+ '%$
    (http://bit.ly/2VVS5sk)

    View Slide

  77. Jetpack: WorkManager
    77
    // src/test/AndroidManifest.xml


    android:name=
    "androidx.work.impl.WorkManagerInitializer"
    android:authorities=
    "${applicationId}.workmanager-init"
    tools:node="remove" />




    View Slide

  78. Jetpack#: WorkManager#%)
    78
    //
    class TestApplication : Application() {
    override fun onCreate() {
    ...
    WorkManagerTestInitHelper
    .initializeTestWorkManager(this)
    ...
    }
    }


    • &'("
    • !
    $

    View Slide

  79. Jetpack08:
    79
    p Jetpack
    IdlingResource514

    p RoomWorkManager:60872
    p #!%09)+/3
    p

    p NavigationView ('( *.&,-)=4
    p Paging5RecyclerView=4
    (<,&$",& ; )

    View Slide

  80. %)
    80
    p .+
    p Robolectric

    p Espresso&
    Local Test
    p Espresso- "* (
    p ,$'#Android Jetpack Components
    p ,$'#Espresso API
    p Robolectric!UI

    View Slide

  81. Espresso API:

    81
    p withId() withText()
    $
    (!#%)
    p
    click(), replaceText(), typeText(),
    scrollTo(), pressKey()
    p "
    RecyclerViewActions

    View Slide

  82. Espresso API:
    82
    p DrawerActions
    openDrawer()

    p NavigationViewActions

    View Slide

  83. Espresso API:
    83
    p ! API2

    p DrawerActions
    p NavigationViewActions
    p "
    ()

    View Slide

  84. %)
    84
    p .+
    p Robolectric

    p Espresso&
    Local Test
    p Espresso- "* (
    p ,$'#Android Jetpack Components
    p ,$'#Espresso API
    p Robolectric!UI

    View Slide

  85. !UI: Robolectric-6
    85
    p /7(4
    .+)

    p NavigationView"&$%
    :0
    9
    p #+*:0#
    8(4;, 1235'

    View Slide

  86. !UI :
    86
    p '
    p 1Activity/Fragment,
    -$A(&B"%
    p 1 hop
    (&.*
    -$A(&.* B"%
    p
    )#
    p PagingRecyclerView +

    View Slide

  87. UI: Shared Test
    87
    Instrumented TestLocal Test
    ?
    // app/build.gradle
    sourceSets {
    androidTest {
    java.srcDirs += file('src/sharedTest/java')
    }
    test {
    java.srcDirs += file('src/sharedTest/java')
    }
    }

    View Slide

  88. UI: Shared Test#
    88
    Instrumented Test
    Local Test ?
    // app/build.gradle
    sourceSets {
    androidTest {
    java.srcDirs += file('src/sharedTest/java')
    }
    test {
    java.srcDirs += file('src/sharedTest/java')
    }
    }
    androidTest
    test!

    src/sharedTest "

    View Slide


  89. UI : Shared Test
    89
    src/sharedTest
    src/androidTest
    src/test
    src/sharedTest
    Instrumented Test
    Local Test

    View Slide

  90. UI: Shared Test#
    90
    p src/sharedTest
    p AndroidAPIRobolectric"
    RobolectricAPI Android!
    p EspressoAPI

    p UI"src/sharedTest/
    PageObject (http://bit.ly/2TzLLVF)
    p () !
    (src/androidTest
    src/test)

    View Slide

  91. !UI:
    91
    p "$#.'


    p
    )!
    p 1Activity/Fragment-
    p 1 hop*(/,

    p Shared Test%&
    UI
    p PageObject+!

    View Slide


  92. 92

    View Slide

  93. *(
    93
    Espresso1 !
    Robolectric+ 2,87
    p Robolectric
    static3/DB#$%
    p IdlingResource1 6&
    '4
    p JetpackEspresso API0)5-.
    p 1
    p RoomWorkManagerNavigationViewPaging5-
    p
    "

    View Slide

  94. URL
    94
    p Improving Robolectric's Looper simulation
    http://robolectric.org/blog/2019/06/04/paused-looper/
    p Issue #4870 IdlingResource doesn't seem to be working with
    Robolectric
    https://github.com/robolectric/robolectric/issues/4807
    p Espresso, Beyond the basicsby Iñaki Villar in 360|AnDev 2017
    http://bit.ly/2wUb2Bc
    p Shared Test


    by ksfee684 in Cookpad.apk #4
    http://bit.ly/2WlQ2Ot

    View Slide

  95. 3-URL ( 1:=,!()
    95
    p "2 UI Automator/

    in DroidKaigi 2016 ( http://bit.ly/2TP3CYS )
    p 2;+Espresso857.
    in DroidKaigi 2017 ( http://bit.ly/2TzLLVF )
    p Espresso#*6&9
    in DroidKaigi 2018 ( http://bit.ly/2QdCeBt )
    p EspressoAndroid)%$4
    in DroidKaigi 2019 ( http://bit.ly/33lLwRp )
    p .<Android'.(0=)
    https://peaks.cc/sumio_tym/android_testing

    View Slide

  96. 96

    Robolectric
    UI




    View Slide