Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

$ 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

Slide 3

Slide 3 text

) 3 Espresso- Robolectric' /% p Robolectric!'* p Robolectric.+1$0 "#& p Robolectric'(,'(, p Robolectric' UI

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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/).

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

Local TestInstrumented Test 8 Local Test Instrumented Test PC (JVM) Android Android API (Espresso API) src/test src/androidTest

Slide 9

Slide 9 text

Local Test Instrumented Test 9 Local Test Instrumented Test PC (JVM) Android Android API (Espresso API) src/test src/androidTest

Slide 10

Slide 10 text

+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).

Slide 11

Slide 11 text

$*-% 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).

Slide 12

Slide 12 text

Local TestRobolectric 3.x 12 Local Test /w Robolectric 3.x Instrumented Test PC (JVM) Android Android API (Espresso API) src/test src/androidTest

Slide 13

Slide 13 text

Local TestRobolectric 4.x 13 Local Test /w Robolectric 4.x Instrumented Test PC (JVM) Android Android API (Espresso API) src/test src/androidTest

Slide 14

Slide 14 text

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/).

Slide 15

Slide 15 text

Robolectric 4.xEspresso Support*1 15 p Robolectric 4.x Espresso Support p 74UI Test). p Robolectric32( ) 5+%/ p "!$0 p #!&6-' ,

Slide 16

Slide 16 text

Robolectric 4.x( 16 p Robolectric & p *% "' $& p Robolectric! (#)) p Robolectric(#)) $,- +

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

Robolectric: Shadow 20 p Android + #+$( p native* p Android* p etc. p ", API !& p ': logcat )% ShadowLog.stream = System.out

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

Robolectric: ! 22 (LEGACY) p ( ) p (AsyncTask ) ( )

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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:

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

: () 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

Slide 30

Slide 30 text

: 30 p src/test p src/androidTest

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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@

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

5 %: (1)+9 */.- 53 sleep() p (4!# % $: 3"!# 1. 2,78 Robolectric 603- 2. Espresso5 % ( $603) 3. " AppNotIdleException &'

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

0 #: /+ 56 p Instrumented Test), IdlingResource p AsyncTask&. %#: ShadowPausedAsyncTask.overrideExecutor IdlingThreadPoolExecutor*' p UI AutomatorUiDevice.wait() 1$!" -(

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

Jetpack: 61 p RobolectricIdlingResource Instrumented Test p 2 p TaskExecutorWithIdlingResourceRule p DataBindingIdlingResource

Slide 62

Slide 62 text

Jetpack : 62 TaskExecutorWithIdlingResourceRule https://git.io/Jv2Ud p ArchTaskExecutor(AAC) p @get:Rule val executorRule = TaskExecutorWithIdlingResourceRule()

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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) } # "%

Slide 67

Slide 67 text

Jetpack: Robolectric/ 67 OK DataBinding LifeCycle LiveData Navigation (NavigationView ) Paging ( ) Room ViewModel WorkManager SavedState

Slide 68

Slide 68 text

Jetpack: Robolectric / 68 OK DataBinding LifeCycle LiveData Navigation (NavigationView) Paging () Room ViewModel WorkManager SavedState

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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 } } }

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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 } ... )( )

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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() } }

Slide 75

Slide 75 text

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() } } "

Slide 76

Slide 76 text

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)

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

Jetpack#: WorkManager#%) 78 // class TestApplication : Application() { override fun onCreate() { ... WorkManagerTestInitHelper .initializeTestWorkManager(this) ... } } • &'(" • ! $

Slide 79

Slide 79 text

Jetpack08: 79 p Jetpack IdlingResource514 p RoomWorkManager:60872 p #!%09)+/3 p p NavigationView ('( *.&,-)=4 p Paging5RecyclerView=4 (<,&$",& ; )

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

Espresso API: 82 p DrawerActions openDrawer() p NavigationViewActions

Slide 83

Slide 83 text

Espresso API: 83 p ! API2 p DrawerActions p NavigationViewActions p " ()

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

!UI: Robolectric-6 85 p /7(4 .+) p NavigationView"&$% :0 9 p #+*:0# 8(4;, 1235'

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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') } }

Slide 88

Slide 88 text

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 "

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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)

Slide 91

Slide 91 text

!UI: 91 p "$#.' p )! p 1Activity/Fragment- p 1 hop*(/, p Shared Test%& UI p PageObject+!

Slide 92

Slide 92 text

92

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

96 Robolectric UI