Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Speaker Deck
PRO
Sign in
Sign up for free
Kyash AndroidのUIテストを運用にのせるまで / How to introduce UI tests in Kyash Android
konifar
September 11, 2018
Programming
0
870
Kyash AndroidのUIテストを運用にのせるまで / How to introduce UI tests in Kyash Android
https://atnd.org/events/99344
konifar
September 11, 2018
Tweet
Share
More Decks by konifar
See All by konifar
VPoEとして1年 もっとこうすればよかった3選 / VPoE Retrospective
konifar
12
5.3k
Introduction to API Testing Automation by Postman
konifar
1
2.9k
Kyash iOSアプリのQAの歴史
konifar
0
5.1k
エンジニアから見たKyashのPdMのおしごと / An engineer's perspective for PdM roles in Kyash
konifar
3
1.4k
Kyash Androidのイースターエッグ実装 / Kyash Android Easter Eggs
konifar
2
920
Master of Android Theme
konifar
23
12k
Material Themingとは何か / material theming overview
konifar
2
250
KyashのGoogle Pay対応の概要 / kyash-google-pay
konifar
2
780
LottieAnimation with DataBinding
konifar
5
430
Other Decks in Programming
See All in Programming
Findy - エンジニア向け会社紹介 / Findy Letter for Engineers
findyinc
2
42k
Form実装基本を学び直してみた
hyugatsukui
0
240
An Advanced Introduction to R
nicetak
0
1.8k
ECテックカンファレンス2023
kspace
1
360
What's new in Shopware 6.5
shyim
0
110
フロントエンドで学んだことをデータ分析で使ってみた話
daichi_igarashi
0
180
Hasura の Relationship と権限管理
karszawa
0
170
AWSにおける標的型Bot対策
hacomono
0
420
Most Valuable Bug(?) ~インシデント未遂から得た学び~
tatsumiakahori
0
150
Swift Observation
shiz
4
290
監視せなあかんし、五大紙だけにオオカミってな🐺🐺🐺🐺🐺
sadnessojisan
2
1.5k
Becoming an Android Librarian (Android World Wide 2023 Jan)
skydoves
1
220
Featured
See All Featured
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
31
20k
Facilitating Awesome Meetings
lara
33
4.6k
Testing 201, or: Great Expectations
jmmastey
25
5.7k
Fireside Chat
paigeccino
16
1.8k
YesSQL, Process and Tooling at Scale
rocio
159
12k
Robots, Beer and Maslow
schacon
154
7.3k
How GitHub (no longer) Works
holman
298
140k
Building Your Own Lightsaber
phodgson
96
4.9k
Web Components: a chance to create the future
zenorocha
304
40k
Writing Fast Ruby
sferik
613
58k
Practical Orchestrator
shlominoach
178
8.9k
Principles of Awesome APIs and How to Build Them.
keavy
117
15k
Transcript
Kyash Androidͷ UIςετΛӡ༻ʹͷͤ Δ·Ͱ 2018/09/11 (Ր) ώΧˑϥϘ Kyash Inc @konifar
ઌɺKyash Android ͷUIͷ ςετ͕Γ࢝Ί·ͨ͠
None
UIͷςετΛೖΕͨཧ༝ • UnitςετͰर͍ʹ͍͘όά͕ൃੜͨ͠ • खಈͷςετέʔεΛ࡞͍͕ͬͯͨɺ֬ೝ͠ͳ ͍࣌ʹ͔͗ͬͯόάى͖Δ • KPTʹͯʰςετΛࣗಈԽ͢Δʱͱ͍͏ Try͕ ग़ͯͬͯΈΔ͜ͱʹͨ͠
ҙࣝͨ͜͠ͱ ᘳΛࢦͭͭ͠ɺ·ͣӡ༻ʹͷͤΔ => ͞·͟·ͳબఆΛ͢Δ࣌ͷҙࢥܾఆʹӨڹ͢ ΔͷͰॏཁ
ࠓ͢͜ͱ 1. ςετπʔϧͷબఆ 2. ύοέʔδɾΫϥεͷׂࢦ 3. ֎෦ͱͷ௨৴ͷࢦ 4. CIαʔϏεͷબఆ
ࠓ͢͜ͱ 1. ςετπʔϧͷબఆ 2. ύοέʔδɾΫϥεͷׂࢦ 3. ֎෦ͱͷ௨৴ͷࢦ 4. CIαʔϏεͷબఆ
Espresso • RobotiumAppiumͱ͍ͬͨબࢶ͋Δ ͕ɺEspressoΛ͏͜ͱʹͨ͠ • Support Libraryʹೖ͍ͬͯΔ͜ͱɺ؆ܿʹه ड़Ͱ͖Δ͜ͱɺઌਓͷݟ͋;Ε͍ͯΔ͜ ͱͳͲ͕ཧ༝
EspressoͷૉΒ͍͠ࢿྉ • มߋʹڧ͍EspressoςετίʔυΛޮྑ͘ ॻ͜͏ https://speakerdeck.com/sumio/droidkaigi2017-lets-write-sustainable- espresso-test-rapidly • EspressoςετίʔυͷಉظॲཧΛڀΊΔ https://speakerdeck.com/sumio/synchronization-capabilities-of-espresso
ࠓ͢͜ͱ 1. ςετπʔϧͷબఆ 2. ύοέʔδɾΫϥεͷׂࢦ 3. ֎෦ͱͷ௨৴ͷࢦ 4. CIαʔϏεͷબఆ
ύοέʔδɾΫϥεͷׂ • Ͳ͜ʹԿΛॻ͍͍͍͔ͯ໌֬Ͱͳ͍ͱॻ͖ʹ ͍͘͠൙ཞ͕ͪ͠ • productionίʔυͱҧ͏ύοέʔδߏ Ͱɺ࠷ݶͷࢦΛܾΊͨ
/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 | | | | |--...
di ύοέʔδ • KyashͰDagger2Λ͍ͬͯΔ • ςετ࣮ߦ࣌ʹGoogleAnalyticsAPIΫϥΠ Ξϯτͷ࣮Λม͑ΔͨΊͷςετ༻ͷ ModuleΛஔ͘
pageobject ύοέʔδ • PageObjectύλʔϯ https://martinfowler.com/bliki/PageObject.html • ֤ը໘Ͱඞཁͳૢ࡞ݕূΛ·ͱΊͨΫϥε Λஔ͘
ϩάΠϯը໘ͷ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()) }
ϩάΠϯը໘ͷ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()) } ϝʔϧΞυϨεͷೖྗ
ϩάΠϯը໘ͷ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()) } ϩάΠϯϘλϯͷԡԼ
ϩάΠϯը໘ͷ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ؔͰϝιουνΣʔϯʹ
PageObject ͷݺͼग़͠ LoginPageObject .inputEmail("
[email protected]
") .inputPassword("kyash123") .clickEmailLoginButton()
PageObject ͷݺͼग़͠ LoginPageObject .inputEmail("
[email protected]
") .inputPassword("kyash123") .clickEmailLoginButton() ← ϝʔϧΞυϨεΛೖྗ
PageObject ͷݺͼग़͠ LoginPageObject .inputEmail("
[email protected]
") .inputPassword("kyash123") .clickEmailLoginButton() ← ύεϫʔυΛೖྗ
PageObject ͷݺͼग़͠ LoginPageObject .inputEmail("
[email protected]
") .inputPassword("kyash123") .clickEmailLoginButton() ← ϩάΠϯϘλϯΛԡԼ
PageObject ͷݺͼग़͠ LoginPageObject .inputEmail("
[email protected]
") .inputPassword("kyash123") .clickEmailLoginButton() ݺͼग़͢ଆEspressoͷίʔυʹґଘ͢Δ͜ͱͳ͘ ؆ܿͰಡΈ͍͢ςετίʔυ͕ॻ͚Δ
scenario ύοέʔδ • ࣮ࡍʹJUnitςετΛ࣮ߦ͢ΔΫϥεΛஔ͘
@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() }
@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Λհͯ͠ૢ࡞ɾݕূΛߦ͏
@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() } ը໘͕දࣔ͞ΕΔ·Ͱͭ
ͪ߹Θͤͷॲཧ fun waitUntilShown() = apply { val result = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
.wait(Until.hasObject(By.text(getString(R.string.hogehoge))), 5000) assertTrue(result) } sleep()ΘͣɺUI AutomatorΛͬͯ Կ͔͕දࣔ͞ΕΔ·Ͱͭ
testing ύοέʔδ • EspressoͰUIͷςετΛॻ্͘ͰඞཁͳUtility MatcherΛஔ͘ • ࠓͷͱ͜Ζͳ͍͕ɺࠓޙΫϥε͕ଟ͘ͳͬ ͖ͯͨΒ͏গׂ͢͠Δ͔
ࠓ͢͜ͱ 1. ςετπʔϧͷબఆ 2. ύοέʔδɾΫϥεͷׂࢦ 3. ֎෦ͱͷ௨৴ͷࢦ 4. CIαʔϏεͷબఆ
3ͭͷબࢶ 1. ςετ༻ͷαʔόʔΛ༻ҙͯͭ͠ͳ͙ 2. Mock Web ServerΛ͏ 3. APIΫϥΠΞϯτΛϞοΫʹஔ͖͑Δ
1. ςετ༻ͷαʔόʔΛ༻ҙ͠ ͯͭͳ͙ ςετ"1*αʔόʔ "1*ΫϥΠΞϯτ γφϦΦςετ json
1. ςετ༻ͷαʔόʔΛ༻ҙ͠ ͯͭͳ͙ • ࣮ࡍʹૢ࡞͢Δͷͱ΄΅ಉ݅͡ͰςετͰ ͖ΔͨΊɺAPIͷ༷͕༧ظͤͣมߋ͞Εͨ ߹ɺΤϥʔ͕ஔ͖͍ͯΔ߹ݕग़Ͱ͖Δ • ࣮ߦ࣌ʹৗʹಉ͡ঢ়ଶʹͨ͠Γಉ࣌ʹ࣮ߦ͞ ΕͨΓͨ͠߹Λߟྀ͢Δͱɺςετ࣮ߦ͝
ͱʹdockerΛ্ཱͪ͛Δͷ͕ඞཁ
2. Mock Web ServerΛ͏ ςετ"1*αʔόʔ "1*ΫϥΠΞϯτ γφϦΦςετ .PDL8FC4FSWFS mock json
2. Mock Web ServerΛ͏ • OkHttpͷmockwebserverΛ͑ɺൺֱత ಋೖ͕༰қɻϓϩδΣΫτͷίʔυͰ݁ ͢Δɺ1ʹൺͯӡ༻͍͢͠ • ϞοΫϨεϙϯεͷjsonΛ༻ҙ͓͔ͯ͠ͳ͚
ΕͳΒͳ͍ɺख͕͔͔ؒΔ͠APIଆͷมߋ ՃʹैͰ͖ͳ͘ͳΔՄೳੑ͋Δ
3. APIΫϥΠΞϯτΛϞοΫʹ ஔ͖͑Δ ςετ"1*αʔόʔ "1*ΫϥΠΞϯτ γφϦΦςετ .PDL8FC4FSWFS mock object ϞοΫ
"1*ΫϥΠΞϯτ
3. APIΫϥΠΞϯτΛϞοΫʹ ஔ͖͑Δ • αʔόʔAPIΫϥΠΞϯτظ௨ΓͷৼΔ ͍Λ͢ΔલఏͰɺͦΕΑΓ্ͷϨΠϠʔΛ Ϣχοτςετͱಉ͡Α͏ʹςετͰ͖Δͷ Ͱଞͷ2ͭͱൺͯϝϯςφϯε͍͢͠ • APIͷjsonͱύʔεͰόά͕͋ͬͨ߹ʹؾ
͚ͮͳ͍
KyashͰʰ3. APIΫϥΠΞϯτ ΛϞοΫʹஔ͖͑ΔʱΛબ • ࠓ·Ͱόά͕ى͖͍ͯͨ෦3ͷΓํͰ र͑ͦ͏ͩͬͨ • ·ͣӡ༻ʹͷͤΔͱ͜Ζ·ͰΓ͔ͨͬͨ • ࠓޙςεταʔόʔΛཱͯΔʹͯ͠ɺঢ়گ
ʹԠͯ͡ϞοΫ͢Δํࣜͱڞଘ͍ͯ͘͜͠ͱ ʹͳΓͦ͏
@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<KyashApi>().apply { SplashPageObject.mockApi(this) SignInPageObject.mockApi(this) MainPageObject.mockApiForEmptyData(this) } val app = AndroidTestUtils.getApp() app.reloadDagger(kyashApi, facebookSdkUtil) app.logout() }
@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<KyashApi>().apply { SplashPageObject.mockApi(this) SignInPageObject.mockApi(this) MainPageObject.mockApiForEmptyData(this) } val app = AndroidTestUtils.getApp() app.reloadDagger(kyashApi, facebookSdkUtil) app.logout() } PageObject͝ͱʹAPIΛϞοΫ͢Δ
@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<KyashApi>().apply { SplashPageObject.mockApi(this) SignInPageObject.mockApi(this) MainPageObject.mockApiForEmptyData(this) } val app = AndroidTestUtils.getApp() app.reloadDagger(kyashApi, facebookSdkUtil) app.logout() } ϞοΫͨ͠APIΫϥΠΞϯτΛͯ͠daggerΛॳظԽ
ࠓ͢͜ͱ 1. ςετπʔϧͷબఆ 2. ύοέʔδɾΫϥεͷׂࢦ 3. ֎෦ͱͷ௨৴ͷࢦ 4. CIαʔϏεͷબఆ
CIαʔϏεͷબఆ • Firebase Test LabΛͬͯςετΛ࣮ߦ https://firebase.google.com/docs/test-lab/overview • CircleCIɺBitriseɺTravisCIɺJenkins ԿͰ Α͔ͬͨͷͰɺಋೖָ͕ͦ͏ͳͷΛબ
BitriseΛબ • αʔόʔͷCIڞଘ͍ͯ͠ΔCircleCIͷΩϡʔʹ࣌ؒ ͷ͔͔ΔδϣϒΛੵΈͨ͘ͳ͍ • iOS appͰBitriseΛ͍ͬͯͯ՝͍ۚͯ͠Δ • (ଞͱൺֱͯ͠) ఆظ࣮ߦͷઃఆΛGUIͰ؆୯ʹͰ͖Δ
• Virtual Device Testing for AndroidͰFirebase Test Labͱͷ࿈ܞGUIͰ؆୯ʹͰ͖Δ
Virtual Device Testing for Android • Bitriseͷbeta൛ͷStep • GUI্ͰઃఆΛॻ͍࣮ͯߦ͢Δ͚ͩͰFirebase Test
LabͰUIςετΛ࣮ߦͰ͖Δ
None
VIRTUAL DEVICE TESTS λϒ͕Ճ͞ΕΔ
ςετ݁Ռ͕ݟΕΔ
ςετதͷಈըݟΕΔ
࣮ߦλΠϛϯά • ݱঢ়15͘Β͍͔͔ΔͷͰɺpushPR͝ͱͷ ࣮ߦ͍ͯ͠ͳ͍ • ຖ0࣌ʹఆظ࣮ߦ + ϦϦʔεϒϥϯνʹϚʔ δ͞ΕͨλΠϛϯάͰ࣮ߦ
·ͱΊ
ςετπʔϧͷબఆ • ҆ఆͷEspressoΛબ • ૉΒ͍͠ࢿྉ͕͋ΔͷͰಡ͏ https://speakerdeck.com/sumio/droidkaigi2017-lets-write-sustainable- espresso-test-rapidly https://speakerdeck.com/sumio/synchronization-capabilities-of-espresso
ύοέʔδɾΫϥεͷׂࢦ • productionͱҧ͏ύοέʔδߏ • PageObjectΛ࡞ͬͯը໘͝ͱͷૢ࡞ݕূΛ ·ͱΊΔ • JUnitͷςετscenarioύοέʔδҎԼʹஔ ͖ɺPageObjectΛհͯ͠ςετΛΈཱͯΔ
֎෦ͱͷ௨৴ͷࢦ • 3ͭͷબࢶ͕͋ͬͨ • ݕূ͍ͨ͜͠ͱಋೖͷ༰қ͞Λߟྀ͠ɺAPI ΫϥΠΞϯτΛϞοΫ͢Δํ๏Λબ
CIαʔϏεͷબఆ • ఆظ࣮ߦUIςετͷઃఆ͕ൺֱత༰қͳ BitriseΛબఆ • ຖ0࣌ʹఆظ࣮ߦ + ϦϦʔεϒϥϯνʹϚʔ δ͞ΕͨλΠϛϯάͰ࣮ߦ
10Ͱͤͳ͍͜ͱଟ͍ ͷͰɺৄ͍࠙͠ձͰ
͋Γ͕ͱ͏͍͟͝·ͨ͠