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

Kyash AndroidのUIテストを運用にのせるまで / How to introduce UI tests in Kyash Android

konifar
September 11, 2018

Kyash AndroidのUIテストを運用にのせるまで / How to introduce UI tests in Kyash Android

konifar

September 11, 2018
Tweet

More Decks by konifar

Other Decks in Programming

Transcript

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  4. ҙࣝͨ͜͠ͱ
    ׬ᘳΛ໨ࢦͭͭ͠ɺ·ͣӡ༻ʹͷͤΔ


    => ͞·͟·ͳબఆΛ͢Δ࣌ͷҙࢥܾఆʹӨڹ͢
    ΔͷͰॏཁ

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  8. Espressoͷૉ੖Β͍͠ࢿྉ
    • มߋʹڧ͍EspressoςετίʔυΛޮ཰ྑ͘
    ॻ͜͏

    https://speakerdeck.com/sumio/droidkaigi2017-lets-write-sustainable-
    espresso-test-rapidly
    • EspressoςετίʔυͷಉظॲཧΛڀΊΔ

    https://speakerdeck.com/sumio/synchronization-capabilities-of-espresso

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  13. pageobject ύοέʔδ
    • PageObjectύλʔϯ

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

    View full-size slide

  14. ϩάΠϯը໘ͷ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())
    }

    View full-size slide

  15. ϩάΠϯը໘ͷ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())
    }
    ϝʔϧΞυϨεͷೖྗ

    View full-size slide

  16. ϩάΠϯը໘ͷ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())
    }
    ϩάΠϯϘλϯͷԡԼ

    View full-size slide

  17. ϩάΠϯը໘ͷ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ؔ਺ͰϝιουνΣʔϯʹ

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  25. @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Λհͯ͠ૢ࡞ɾݕূΛߦ͏

    View full-size slide

  26. @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()
    }
    ը໘͕දࣔ͞ΕΔ·Ͱ଴ͭ

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  39. @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ΛϞοΫ͢Δ

    View full-size slide

  40. @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ΛॳظԽ

    View full-size slide

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

    View full-size slide

  42. CIαʔϏεͷબఆ
    • Firebase Test LabΛ࢖ͬͯςετΛ࣮ߦ

    https://firebase.google.com/docs/test-lab/overview
    • CircleCIɺBitriseɺTravisCIɺJenkins ԿͰ΋
    Α͔ͬͨͷͰɺಋೖָ͕ͦ͏ͳ΋ͷΛબ୒

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  45. VIRTUAL DEVICE TESTS λϒ͕௥Ճ͞ΕΔ

    View full-size slide

  46. ςετ݁Ռ͕ݟΕΔ

    View full-size slide

  47. ςετதͷಈը΋ݟΕΔ

    View full-size slide

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

    View full-size slide

  49. ςετπʔϧͷબఆ
    • ҆ఆͷEspressoΛબ୒
    • ૉ੖Β͍͠ࢿྉ͕͋ΔͷͰಡ΋͏

    https://speakerdeck.com/sumio/droidkaigi2017-lets-write-sustainable-
    espresso-test-rapidly

    https://speakerdeck.com/sumio/synchronization-capabilities-of-espresso

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  54. ͋Γ͕ͱ͏͍͟͝·ͨ͠

    View full-size slide