Slide 1

Slide 1 text

Kyash Androidͷ UIςετΛӡ༻ʹͷͤ Δ·Ͱ 2018/09/11 (Ր) ώΧˑϥϘ Kyash Inc @konifar

Slide 2

Slide 2 text

ઌ೔ɺKyash Android ͷUIͷ ςετ͕૸Γ࢝Ί·ͨ͠

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

UIͷςετΛೖΕͨཧ༝ • UnitςετͰर͍ʹ͍͘όά͕ൃੜͨ͠ • खಈͷςετέʔεΛ࡞͍͕ͬͯͨɺ֬ೝ͠ͳ ͍࣌ʹ͔͗ͬͯόά͸ى͖Δ • KPTʹͯʰςετΛࣗಈԽ͢Δʱͱ͍͏ Try͕ ग़ͯ΍ͬͯΈΔ͜ͱʹͨ͠

Slide 5

Slide 5 text

ҙࣝͨ͜͠ͱ ׬ᘳΛ໨ࢦͭͭ͠ɺ·ͣӡ༻ʹͷͤΔ
 
 => ͞·͟·ͳબఆΛ͢Δ࣌ͷҙࢥܾఆʹӨڹ͢ ΔͷͰॏཁ

Slide 6

Slide 6 text

ࠓ೔࿩͢͜ͱ 1. ςετπʔϧͷબఆ 2. ύοέʔδɾΫϥεͷ෼ׂࢦ਑ 3. ֎෦ͱͷ௨৴ͷࢦ਑ 4. CIαʔϏεͷબఆ

Slide 7

Slide 7 text

ࠓ೔࿩͢͜ͱ 1. ςετπʔϧͷબఆ 2. ύοέʔδɾΫϥεͷ෼ׂࢦ਑ 3. ֎෦ͱͷ௨৴ͷࢦ਑ 4. CIαʔϏεͷબఆ

Slide 8

Slide 8 text

Espresso • Robotium΍Appiumͱ͍ͬͨબ୒ࢶ΋͋Δ ͕ɺEspressoΛ࢖͏͜ͱʹͨ͠ • Support Libraryʹೖ͍ͬͯΔ͜ͱɺ؆ܿʹه ड़Ͱ͖Δ͜ͱɺઌਓͷ஌ݟ΋͋;Ε͍ͯΔ͜ ͱͳͲ͕ཧ༝

Slide 9

Slide 9 text

Espressoͷૉ੖Β͍͠ࢿྉ • มߋʹڧ͍EspressoςετίʔυΛޮ཰ྑ͘ ॻ͜͏
 https://speakerdeck.com/sumio/droidkaigi2017-lets-write-sustainable- espresso-test-rapidly • EspressoςετίʔυͷಉظॲཧΛڀΊΔ
 https://speakerdeck.com/sumio/synchronization-capabilities-of-espresso

Slide 10

Slide 10 text

ࠓ೔࿩͢͜ͱ 1. ςετπʔϧͷબఆ 2. ύοέʔδɾΫϥεͷ෼ׂࢦ਑ 3. ֎෦ͱͷ௨৴ͷࢦ਑ 4. CIαʔϏεͷબఆ

Slide 11

Slide 11 text

ύοέʔδɾΫϥεͷ෼ׂ • Ͳ͜ʹԿΛॻ͍͍͍͔ͯ໌֬Ͱͳ͍ͱॻ͖ʹ ͍͘͠൙ཞ͕ͪ͠ • productionίʔυͱ͸ҧ͏ύοέʔδߏ੒ Ͱɺ࠷௿ݶͷࢦ਑ΛܾΊͨ

Slide 12

Slide 12 text

/src/androidTest |--AndroidManifest.xml |--java | |--co | | |--kyash | | | |--AndroidTestApp.kt | | | |--di | | | | |--TestAnalyticsModule.kt | | | | |--TestNetModule.kt | | | | |--... | | | |--pageobject | | | | |--account | | | | | |--AccountSettingPageObject.kt | | | | | |--... | | | | |--card | | | | | |--AboutLinkedCardPageObject.kt | | | | | |--... | | | | | ... | | | | ... | | | |--scenario | | | | |--AddLinkedCardTest.kt | | | | |--LoginTest.kt | | | | |--SignInTest.kt | | | | |--... | | | |--testing | | | | |--AndroidTestUtils.kt | | | | |--CustomTestRunner.kt | | | | |--...

Slide 13

Slide 13 text

di ύοέʔδ • KyashͰ͸Dagger2Λ࢖͍ͬͯΔ • ςετ࣮ߦ࣌ʹGoogleAnalytics΍APIΫϥΠ Ξϯτͷ࣮૷Λม͑ΔͨΊͷςετ༻ͷ ModuleΛஔ͘

Slide 14

Slide 14 text

pageobject ύοέʔδ • PageObjectύλʔϯ
 https://martinfowler.com/bliki/PageObject.html • ֤ը໘Ͱඞཁͳૢ࡞΍ݕূΛ·ͱΊͨΫϥε Λஔ͘

Slide 15

Slide 15 text

ϩάΠϯը໘ͷPageObject object LoginPageObject { ... fun inputEmail(text: String) = apply { onView(withId(R.id.email_edit).perform(scrollTo(), replaceText(text), closeSoftKeyboard()) } fun clickEmailLoginButton() = apply { onView(withId(R.id.button)).perform(scrollTo(), click()) }

Slide 16

Slide 16 text

ϩάΠϯը໘ͷPageObject object LoginPageObject { ... fun inputEmail(text: String) = apply { onView(withId(R.id.email_edit).perform(scrollTo(), replaceText(text), closeSoftKeyboard()) } fun clickEmailLoginButton() = apply { onView(withId(R.id.button)).perform(scrollTo(), click()) } ϝʔϧΞυϨεͷೖྗ

Slide 17

Slide 17 text

ϩάΠϯը໘ͷPageObject object LoginPageObject { ... fun inputEmail(text: String) = apply { onView(withId(R.id.email_edit).perform(scrollTo(), replaceText(text), closeSoftKeyboard()) } fun clickEmailLoginButton() = apply { onView(withId(R.id.button)).perform(scrollTo(), click()) } ϩάΠϯϘλϯͷԡԼ

Slide 18

Slide 18 text

ϩάΠϯը໘ͷPageObject object LoginPageObject { ... fun inputEmail(text: String) = apply { onView(withId(R.id.email_edit).perform(scrollTo(), replaceText(text), closeSoftKeyboard()) } fun clickEmailLoginButton() = apply { onView(withId(R.id.button)).perform(scrollTo(), click()) } Kotlinͷapplyؔ਺ͰϝιουνΣʔϯʹ

Slide 19

Slide 19 text

PageObject ͷݺͼग़͠ LoginPageObject .inputEmail("[email protected]") .inputPassword("kyash123") .clickEmailLoginButton()

Slide 20

Slide 20 text

PageObject ͷݺͼग़͠ LoginPageObject .inputEmail("[email protected]") .inputPassword("kyash123") .clickEmailLoginButton() ← ϝʔϧΞυϨεΛೖྗ

Slide 21

Slide 21 text

PageObject ͷݺͼग़͠ LoginPageObject .inputEmail("[email protected]") .inputPassword("kyash123") .clickEmailLoginButton() ← ύεϫʔυΛೖྗ

Slide 22

Slide 22 text

PageObject ͷݺͼग़͠ LoginPageObject .inputEmail("[email protected]") .inputPassword("kyash123") .clickEmailLoginButton() ← ϩάΠϯϘλϯΛԡԼ

Slide 23

Slide 23 text

PageObject ͷݺͼग़͠ LoginPageObject .inputEmail("[email protected]") .inputPassword("kyash123") .clickEmailLoginButton() ݺͼग़͢ଆ͸Espressoͷίʔυʹґଘ͢Δ͜ͱͳ͘ ؆ܿͰಡΈ΍͍͢ςετίʔυ͕ॻ͚Δ

Slide 24

Slide 24 text

scenario ύοέʔδ • ࣮ࡍʹJUnitςετΛ࣮ߦ͢ΔΫϥεΛஔ͘

Slide 25

Slide 25 text

@LargeTest @RunWith(AndroidJUnit4::class) class LoginTest { @get:Rule var activityTestRule = ActivityTestRule(SplashActivity::class.java, true, false) .. /** * EmailͰϩάΠϯͯ͠΢ΥϨοτը໘Λ։͘·Ͱ */ @Test fun emailLogin() { SplashPageObject.launch(activityTestRule) WelcomePageObject .waitUntilShown() .clickLoginButton() LoginPageObject .inputEmail("[email protected]") .inputPassword("kyash123") .clickEmailLoginButton() WalletPageObject.assertKyashCardInActiveExists() }

Slide 26

Slide 26 text

@LargeTest @RunWith(AndroidJUnit4::class) class LoginTest { @get:Rule var activityTestRule = ActivityTestRule(SplashActivity::class.java, true, false) .. /** * EmailͰϩάΠϯͯ͠΢ΥϨοτը໘Λ։͘·Ͱ */ @Test fun emailLogin() { SplashPageObject.launch(activityTestRule) WelcomePageObject .waitUntilShown() .clickLoginButton() LoginPageObject .inputEmail("[email protected]") .inputPassword("kyash123") .clickEmailLoginButton() WalletPageObject.assertKyashCardInActiveExists() } PageObjectΛհͯ͠ૢ࡞ɾݕূΛߦ͏

Slide 27

Slide 27 text

@LargeTest @RunWith(AndroidJUnit4::class) class LoginTest { @get:Rule var activityTestRule = ActivityTestRule(SplashActivity::class.java, true, false) .. /** * EmailͰϩάΠϯͯ͠΢ΥϨοτը໘Λ։͘·Ͱ */ @Test fun emailLogin() { SplashPageObject.launch(activityTestRule) WelcomePageObject .waitUntilShown() .clickLoginButton() LoginPageObject .inputEmail("[email protected]") .inputPassword("kyash123") .clickEmailLoginButton() WalletPageObject.assertKyashCardInActiveExists() } ը໘͕දࣔ͞ΕΔ·Ͱ଴ͭ

Slide 28

Slide 28 text

଴ͪ߹Θͤͷॲཧ fun waitUntilShown() = apply { val result = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) .wait(Until.hasObject(By.text(getString(R.string.hogehoge))), 5000) assertTrue(result) } sleep()͸࢖ΘͣɺUI AutomatorΛ࢖ͬͯ Կ͔͕දࣔ͞ΕΔ·Ͱ଴ͭ

Slide 29

Slide 29 text

testing ύοέʔδ • EspressoͰUIͷςετΛॻ্͘ͰඞཁͳUtility ΍MatcherΛஔ͘ • ࠓͷͱ͜Ζ໰୊ͳ͍͕ɺࠓޙΫϥε͕ଟ͘ͳͬ ͖ͯͨΒ΋͏গ͠෼ׂ͢Δ͔΋

Slide 30

Slide 30 text

ࠓ೔࿩͢͜ͱ 1. ςετπʔϧͷબఆ 2. ύοέʔδɾΫϥεͷ෼ׂࢦ਑ 3. ֎෦ͱͷ௨৴ͷࢦ਑ 4. CIαʔϏεͷબఆ

Slide 31

Slide 31 text

3ͭͷબ୒ࢶ 1. ςετ༻ͷαʔόʔΛ༻ҙͯͭ͠ͳ͙ 2. Mock Web ServerΛ࢖͏ 3. APIΫϥΠΞϯτΛϞοΫʹஔ͖׵͑Δ

Slide 32

Slide 32 text

1. ςετ༻ͷαʔόʔΛ༻ҙ͠ ͯͭͳ͙ ςετ"1*αʔόʔ "1*ΫϥΠΞϯτ γφϦΦςετ json

Slide 33

Slide 33 text

1. ςετ༻ͷαʔόʔΛ༻ҙ͠ ͯͭͳ͙ • ࣮ࡍʹૢ࡞͢Δͷͱ΄΅ಉ͡৚݅ͰςετͰ ͖ΔͨΊɺAPIͷ࢓༷͕༧ظͤͣมߋ͞Εͨ৔ ߹΍ɺΤϥʔ͕ஔ͖͍ͯΔ৔߹΋ݕग़Ͱ͖Δ • ࣮ߦ࣌ʹৗʹಉ͡ঢ়ଶʹͨ͠Γಉ࣌ʹ࣮ߦ͞ ΕͨΓͨ͠৔߹Λߟྀ͢Δͱɺςετ࣮ߦ͝ ͱʹdockerΛ্ཱͪ͛Δ౳ͷ޻෉͕ඞཁ

Slide 34

Slide 34 text

2. Mock Web ServerΛ࢖͏ ςετ"1*αʔόʔ "1*ΫϥΠΞϯτ γφϦΦςετ .PDL8FC4FSWFS mock json

Slide 35

Slide 35 text

2. Mock Web ServerΛ࢖͏ • OkHttpͷmockwebserverΛ࢖͑͹ɺൺֱత ಋೖ͕༰қɻϓϩδΣΫτ಺ͷίʔυͰ׬݁ ͢Δ෼ɺ1ʹൺ΂ͯӡ༻͠΍͍͢ • ϞοΫϨεϙϯεͷjsonΛ༻ҙ͓͔ͯ͠ͳ͚ Ε͹ͳΒͳ͍෼ɺख͕͔͔ؒΔ͠APIଆͷมߋ ΍௥Ճʹ௥ैͰ͖ͳ͘ͳΔՄೳੑ΋͋Δ

Slide 36

Slide 36 text

3. APIΫϥΠΞϯτΛϞοΫʹ ஔ͖׵͑Δ ςετ"1*αʔόʔ "1*ΫϥΠΞϯτ γφϦΦςετ .PDL8FC4FSWFS mock object ϞοΫ "1*ΫϥΠΞϯτ

Slide 37

Slide 37 text

3. APIΫϥΠΞϯτΛϞοΫʹ ஔ͖׵͑Δ • αʔόʔ΍APIΫϥΠΞϯτ͸ظ଴௨ΓͷৼΔ ෣͍Λ͢ΔલఏͰɺͦΕΑΓ্ͷϨΠϠʔΛ Ϣχοτςετͱಉ͡Α͏ʹςετͰ͖Δͷ Ͱଞͷ2ͭͱൺ΂ͯϝϯςφϯε͠΍͍͢ • APIͷjsonͱύʔεͰόά͕͋ͬͨ৔߹ʹ͸ؾ ͚ͮͳ͍

Slide 38

Slide 38 text

KyashͰ͸ʰ3. APIΫϥΠΞϯτ ΛϞοΫʹஔ͖׵͑ΔʱΛબ୒ • ࠓ·Ͱόά͕ى͖͍ͯͨ෦෼͸3ͷ΍ΓํͰ΋ र͑ͦ͏ͩͬͨ • ·ͣ͸ӡ༻ʹͷͤΔͱ͜Ζ·Ͱ΍Γ͔ͨͬͨ • ࠓޙςεταʔόʔΛཱͯΔʹͯ͠΋ɺঢ়گ ʹԠͯ͡ϞοΫ͢Δํࣜͱڞଘ͍ͯ͘͜͠ͱ ʹͳΓͦ͏

Slide 39

Slide 39 text

@LargeTest @RunWith(AndroidJUnit4::class) class LoginTest { @get:Rule var activityTestRule = ActivityTestRule(SplashActivity::class.java, true, false) @Before fun setUp() { val facebookSdkUtil = WelcomePageObject.mockFacebookSdk() val kyashApi = mock().apply { SplashPageObject.mockApi(this) SignInPageObject.mockApi(this) MainPageObject.mockApiForEmptyData(this) } val app = AndroidTestUtils.getApp() app.reloadDagger(kyashApi, facebookSdkUtil) app.logout() }

Slide 40

Slide 40 text

@LargeTest @RunWith(AndroidJUnit4::class) class LoginTest { @get:Rule var activityTestRule = ActivityTestRule(SplashActivity::class.java, true, false) @Before fun setUp() { val facebookSdkUtil = WelcomePageObject.mockFacebookSdk() val kyashApi = mock().apply { SplashPageObject.mockApi(this) SignInPageObject.mockApi(this) MainPageObject.mockApiForEmptyData(this) } val app = AndroidTestUtils.getApp() app.reloadDagger(kyashApi, facebookSdkUtil) app.logout() } PageObject͝ͱʹAPIΛϞοΫ͢Δ

Slide 41

Slide 41 text

@LargeTest @RunWith(AndroidJUnit4::class) class LoginTest { @get:Rule var activityTestRule = ActivityTestRule(SplashActivity::class.java, true, false) @Before fun setUp() { val facebookSdkUtil = WelcomePageObject.mockFacebookSdk() val kyashApi = mock().apply { SplashPageObject.mockApi(this) SignInPageObject.mockApi(this) MainPageObject.mockApiForEmptyData(this) } val app = AndroidTestUtils.getApp() app.reloadDagger(kyashApi, facebookSdkUtil) app.logout() } ϞοΫͨ͠APIΫϥΠΞϯτΛ౉ͯ͠daggerΛॳظԽ

Slide 42

Slide 42 text

ࠓ೔࿩͢͜ͱ 1. ςετπʔϧͷબఆ 2. ύοέʔδɾΫϥεͷ෼ׂࢦ਑ 3. ֎෦ͱͷ௨৴ͷࢦ਑ 4. CIαʔϏεͷબఆ

Slide 43

Slide 43 text

CIαʔϏεͷબఆ • Firebase Test LabΛ࢖ͬͯςετΛ࣮ߦ
 https://firebase.google.com/docs/test-lab/overview • CircleCIɺBitriseɺTravisCIɺJenkins ԿͰ΋ Α͔ͬͨͷͰɺಋೖָ͕ͦ͏ͳ΋ͷΛબ୒

Slide 44

Slide 44 text

BitriseΛબ୒ • αʔόʔͷCI΋ڞଘ͍ͯ͠ΔCircleCIͷΩϡʔʹ࣌ؒ ͷ͔͔ΔδϣϒΛੵΈͨ͘ͳ͍ • iOS appͰ͸BitriseΛ࢖͍ͬͯͯ՝ۚ΋͍ͯ͠Δ • (ଞͱൺֱͯ͠) ఆظ࣮ߦͷઃఆΛGUIͰ؆୯ʹͰ͖Δ • Virtual Device Testing for AndroidͰFirebase Test Labͱͷ࿈ܞ΋GUIͰ؆୯ʹͰ͖Δ

Slide 45

Slide 45 text

Virtual Device Testing for Android • Bitriseͷbeta൛ͷStep • GUI্ͰઃఆΛॻ͍࣮ͯߦ͢Δ͚ͩͰFirebase Test LabͰUIςετΛ࣮ߦͰ͖Δ

Slide 46

Slide 46 text

No content

Slide 47

Slide 47 text

VIRTUAL DEVICE TESTS λϒ͕௥Ճ͞ΕΔ

Slide 48

Slide 48 text

ςετ݁Ռ͕ݟΕΔ

Slide 49

Slide 49 text

ςετதͷಈը΋ݟΕΔ

Slide 50

Slide 50 text

࣮ߦλΠϛϯά • ݱঢ়15෼͘Β͍͔͔ΔͷͰɺpush΍PR͝ͱͷ ࣮ߦ͸͍ͯ͠ͳ͍ • ຖ೔0࣌ʹఆظ࣮ߦ + ϦϦʔεϒϥϯνʹϚʔ δ͞ΕͨλΠϛϯάͰ࣮ߦ

Slide 51

Slide 51 text

·ͱΊ

Slide 52

Slide 52 text

ςετπʔϧͷબఆ • ҆ఆͷEspressoΛબ୒ • ૉ੖Β͍͠ࢿྉ͕͋ΔͷͰಡ΋͏
 https://speakerdeck.com/sumio/droidkaigi2017-lets-write-sustainable- espresso-test-rapidly
 https://speakerdeck.com/sumio/synchronization-capabilities-of-espresso

Slide 53

Slide 53 text

ύοέʔδɾΫϥεͷ෼ׂࢦ਑ • productionͱ͸ҧ͏ύοέʔδߏ੒ • PageObjectΛ࡞ͬͯը໘͝ͱͷૢ࡞΍ݕূΛ ·ͱΊΔ • JUnitͷςετ͸scenarioύοέʔδҎԼʹஔ ͖ɺPageObjectΛհͯ͠ςετΛ૊ΈཱͯΔ

Slide 54

Slide 54 text

֎෦ͱͷ௨৴ͷࢦ਑ • 3ͭͷબ୒ࢶ͕͋ͬͨ • ݕূ͍ͨ͜͠ͱ΍ಋೖͷ༰қ͞Λߟྀ͠ɺAPI ΫϥΠΞϯτΛϞοΫ͢Δํ๏Λબ୒

Slide 55

Slide 55 text

CIαʔϏεͷબఆ • ఆظ࣮ߦ΍UIςετͷઃఆ͕ൺֱత༰қͳ BitriseΛબఆ • ຖ೔0࣌ʹఆظ࣮ߦ + ϦϦʔεϒϥϯνʹϚʔ δ͞ΕͨλΠϛϯάͰ࣮ߦ

Slide 56

Slide 56 text

10෼Ͱ࿩ͤͳ͍͜ͱ΋ଟ͍ ͷͰɺৄ͍͠࿩͸࠙਌ձͰ

Slide 57

Slide 57 text

͋Γ͕ͱ͏͍͟͝·ͨ͠