Slide 1

Slide 1 text

Build Testable Apps for Androidͷ঺հ SWETάϧʔϓ Nozomi Takuma

Slide 2

Slide 2 text

ࣗݾ঺հ Nozomi Takuma DeNAͷSWETάϧʔϓ Androidͱςετ͕޷͖

Slide 3

Slide 3 text

Session Summary AndroidΞϓϦʹ͓͚Δࣗಈςετͷ ઓུ TestableͳΞϓϦͷߏ੒ Unit/Integra=on/E2Eςετͷ࣮૷

Slide 4

Slide 4 text

ࣗಈςετͷઓུ Scope/Speed/Fidelityͷόϥϯε ςετϐϥϛου

Slide 5

Slide 5 text

ࣗಈςετͷઓུ

Slide 6

Slide 6 text

TODOΞϓϦΛྫʹ Cri=cal User Journey λεΫΛ࡞੒͢Δ λεΫͷਐḿΛ֬ೝ͢Δ

Slide 7

Slide 7 text

Monolithic Architecture

Slide 8

Slide 8 text

Ϋϥε͝ͱΛ੾Γ཭͢ͷ ͕೉͍͠ είʔϓͷখ͍͞ςετ ͕Ͱ͖ͣɺE2Eςετ͕
 ૿͍͑ͯ͘ Monolithic Architecture

Slide 9

Slide 9 text

Layered Architecture

Slide 10

Slide 10 text

Unitςετ͸໰୊ͳ͠ ࠷ऴతʹ֤Layer͕Monoli=cʹͳΓ
 ΠϯςάϨʔγϣϯςετΑΓ΋
 E2Eςετ͕૿͍͑ͯ͘ Layered Architecture

Slide 11

Slide 11 text

Modular Architecture

Slide 12

Slide 12 text

Architecture Component Naviga=on Data Binding Live Data Room View ViewModel Model

Slide 13

Slide 13 text

શମਤ

Slide 14

Slide 14 text

TDD(TDD&ATDDͬΆ͍?)

Slide 15

Slide 15 text

TDD(TDD&ATDDͬΆ͍?) ͱ͍͏લஔ͖͕͋Γ
 E2Eςετͷઆ໌͔Β͸͡·Γ·͢

Slide 16

Slide 16 text

E2E Test

Slide 17

Slide 17 text

Happy PathΛ
 ςετ E2E Test Scope

Slide 18

Slide 18 text

E2E Test Code @Test fun createTask() { // start up Tasks screen val ac=vityScenario = Ac=vityScenario.launch(TasksAc=vity::class.java) dataBindingIdlingResource.monitorAc=vity(ac=vityScenario) // Click on the "+" bu\on, add details, and save onView(withId(R.id.fab_add_task)).perform(click()) onView(withId(R.id.add_task_=tle)).perform(typeText("=tle")) onView(withId(R.id.add_task_descrip=on)).perform(typeText("descrip=on")) onView(withId(R.id.fab_save_task)).perform(click()) // Then verify task is displayed on screen onView(withText("=tle")).check(matches(isDisplayed())) }

Slide 19

Slide 19 text

E2E Test Code @Test fun createTask() { // start up Tasks screen val ac=vityScenario = Ac=vityScenario.launch(TasksAc=vity::class.java) dataBindingIdlingResource.monitorAc=vity(ac=vityScenario) // Click on the "+" bu\on, add details, and save onView(withId(R.id.fab_add_task)).perform(click()) onView(withId(R.id.add_task_=tle)).perform(typeText("=tle")) onView(withId(R.id.add_task_descrip=on)).perform(typeText("descrip=on")) onView(withId(R.id.fab_save_task)).perform(click()) // Then verify task is displayed on screen onView(withText("=tle")).check(matches(isDisplayed())) } Ac=vityΛىಈ͠
 ෳ਺ͷFragmentʹ·͕ͨͬͯ ϑϩʔΛݕূ

Slide 20

Slide 20 text

Integra=on Test

Slide 21

Slide 21 text

Integra=on Test Scope ςετͷείʔϓΛ খ͘͢͞Δ

Slide 22

Slide 22 text

Test Double ্ʹ͍͘΄Ͳ࣮ࡍͷ
 ৼΔ෣͍ʹۙ͘ͳΔ Fidelity

Slide 23

Slide 23 text

AddEditTaskFragmentTest VerifyͰ͖ΔΑ͏
 ϞοΫʹஔ͖׵͑ ࠓճ͸಺෦ʹMapΛ࣋ͭ Fakeʹஔ͖׵͑


Slide 24

Slide 24 text

Integra=on Test Code @Test fun validTask_isSaved() { // GIVEN - On the "Add Task" screen. val navController = mock(NavController::class.java) val bundle = AddEditTaskFragmentArgs(null, getApplica=onContext().getString(R.string.add_task)).toBundle() val scenario = launchFragmentInContainer(bundle, R.style.AppTheme) scenario.onFragment { Naviga=on.setViewNavController(it.view!!, navController) }
 .. } FragmentΛىಈ͠
 ϞοΫͷNavControllerΛ
 ౉͢

Slide 25

Slide 25 text

Integra=on Test Code @Test fun validTask_isSaved() {
 .. // WHEN - Valid =tle and descrip=on combina=on and click save onView(withId(R.id.add_task_=tle)).perform(replaceText("=tle")) onView(withId(R.id.add_task_descrip=on)).perform(replaceText("descrip=on")) onView(withId(R.id.fab_save_task)).perform(click()) .. }

Slide 26

Slide 26 text

Integra=on Test Code @Test fun validTask_isSaved() { .. // THEN - Verify that the repository saved the task val tasks = (repository.getTasksBlocking(true) as Result.Success).data assertEquals(tasks.size, 1) assertEquals(tasks[0].=tle, "=tle") verify(navController).navigate( AddEditTaskFragmentDirec=ons .ac=onAddEditTaskFragmentToTasksFragment(ADD_EDIT_RESULT_OK)) } Fake͔ΒσʔλΛ औಘͯ͠ݕূ


Slide 27

Slide 27 text

Integra=on Test Code @Test fun validTask_isSaved() { .. // THEN - Verify that the repository saved the task val tasks = (repository.getTasksBlocking(true) as Result.Success).data assertEquals(tasks.size, 1) assertEquals(tasks[0].=tle, "=tle") verify(navController).navigate( AddEditTaskFragmentDirec=ons .ac=onAddEditTaskFragmentToTasksFragment(ADD_EDIT_RESULT_OK)) } ਖ਼͍͠ύϥϝʔλͰNaviga=on Ͱ͖͍ͯΔ͔ݕূ

Slide 28

Slide 28 text

Unit Test

Slide 29

Slide 29 text

Unit Test Summary TaskLocalDataStoreTest ৄࡉͳ࣮૷ΑΓͲ͏ৼΔ෣͏͔ DaoΛMockͰ͸ͳ͘Fakeʹͯ͠
 ςετ Corou=neͷςετ༻ͷrunBlockingTest

Slide 30

Slide 30 text

Tes=ng Codelab ηογϣϯͷ಺༰Λ࣮ࡍʹखΛಈ͔ ͠ͳ͕ΒֶͿ͜ͱ͕Ͱ͖Δ g.co/codelabs/android-tes=ng

Slide 31

Slide 31 text

AndroidX Test Local TestͱInstrumenta=on Testͷࠩ෼Λٵऩ͢Δ
 unified API ҆ఆੑ΍૬ޓӡ༻ੑͷڧԽ Local JVM supportͷڧԽ Architecture component support

Slide 32

Slide 32 text

Nitrogen Unified APIͰॻ͔ΕͨςετΛ
 ༷ʑͳ؀ڥͰγʔϜϨεʹ
 ࣮ߦͰ͖ΔPlanorm

Slide 33

Slide 33 text

Nitrogen͕ϦϦʔε͞ΕΔ·Ͱ android { sourceSets { String sharedTestDir = 'src/sharedTest/java' test { java.srcDir sharedTestDir } androidTest { java.srcDir sharedTestDir } }

Slide 34

Slide 34 text

CodelabͰͷ࢖͍෼͚ androidTest E2E sharedTest Integra=on Test(FragmentɾRoom·ΘΓ) test Unit Test

Slide 35

Slide 35 text

Nitrogen EAP 2019 2Qʹalpha൛ϦϦʔε༧ఆͳͷͰ ͦΖͦΖ৮ΕΔΑ͏ʹͳΔͱظ଴ bit.ly/nitrogen-eap

Slide 36

Slide 36 text

·ͱΊ ࣗಈςετͷઓུͱͦΕΛ࣮ݱ͢ΔArchitectuteͷ঺հ CodelabΛ௨ͯ͡खΛಈ͔͠ͳ͕ΒֶͿ͜ͱ͕Ͱ͖Δ Unified APIΛ࢖༻ͨ͠γʔϜϨεͳ࣮ߦ(Nitrogen EAP!)

Slide 37

Slide 37 text

Session Info h\ps://events.google.com/io/schedule/events/ 7bd73b37-1bca-40b3-b8e9-6efe3a9ac234 h\ps://www.youtube.com/watch?v=VJi2vmaQe6w