Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Kyash AndroidのUIテストを運用にのせるまで / How to introduce...
Search
konifar
September 11, 2018
Programming
1.2k
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Kyash AndroidのUIテストを運用にのせるまで / How to introduce UI tests in Kyash Android
https://atnd.org/events/99344
konifar
September 11, 2018
More Decks by konifar
See All by konifar
自分が "コントロールできる" と思い込んで "引き出しを増やす"
konifar
15
6k
自分が詳しくない領域でAIを使う #プロヒス2026
konifar
22
9.6k
マネージャー版 "提案のレベル" を上げる
konifar
27
22k
AIで 浮いた時間で 何をする? 2026春 #devsumi
konifar
21
6.4k
登壇資料を作る時に意識していること #登壇資料_findy
konifar
7
3.4k
事業部のプロジェクト進行と開発チームの改善の “時間軸" のすり合わせ
konifar
12
4.7k
チーム開発の “地ならし"
konifar
8
18k
AIで 浮いた時間で 何をする? #プロヒス2025
konifar
27
17k
物語を動かす行動"量" #エンジニアニメ
konifar
19
7.6k
Other Decks in Programming
See All in Programming
20260828_品質と開発生産性を両立させる、AI時代のE2Eテストの考え方
magicpod
0
160
PyO3 で既存 Python 評価器を Rust core 化する ー wasm-bindgen でブラウザにも配るための設計
kdash
1
460
Swift愛好会100回記念 第1回を振り返る
jollyjoester
0
120
Deep dive into the select statement (GopherCon UK)
jespino
0
170
AIの中の人になってみる
htkym
0
170
レビュー履歴をAIに食わせて、 Compose移行を加速するs
shihochan
0
270
「AI時代、配布するPythonコードをどう守るか: 難読化の実験と判断軸」 #PyconJP2026
pkshadeck
PRO
2
170
Go 1.27からのGODEBUG / Go 1.27 リリースパーティ #go127party
mazrean
0
320
LL言語やWebフレームワークのPostgreSQL対応 〜DBの機能がユーザーに届くまで〜
kentaroutakeda
0
140
What We Talk About When We Talk About XP
m_seki
2
370
Building an Out-of-Order CPU
latte72
0
690
RSSとCodexを使ってX投稿自動化してみた
ochtum
0
110
Featured
See All Featured
First, design no harm
axbom
PRO
2
1.3k
JAMstack: Web Apps at Ludicrous Speed - All Things Open 2022
reverentgeek
1
590
The Straight Up "How To Draw Better" Workshop
denniskardys
239
140k
Chasing Engaging Ingredients in Design
codingconduct
0
300
How Fast Is Fast Enough? [PerfNow 2025]
tammyeverts
3
870
Crafting Experiences
bethany
1
290
DevOps and Value Stream Thinking: Enabling flow, efficiency and business value
helenjbeal
1
380
Color Theory Basics | Prateek | Gurzu
gurzu
0
460
Gemini Prompt Engineering: Practical Techniques for Tangible AI Outcomes
mfonobong
2
520
AI: The stuff that nobody shows you
jnunemaker
PRO
9
970
Organizational Design Perspectives: An Ontology of Organizational Design Elements
kimpetersen
PRO
1
820
Keith and Marios Guide to Fast Websites
keithpitt
413
23k
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Ͱͤͳ͍͜ͱଟ͍ ͷͰɺৄ͍࠙͠ձͰ
͋Γ͕ͱ͏͍͟͝·ͨ͠