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
自分が詳しくない領域でAIを使う #プロヒス2026
konifar
18
6.2k
マネージャー版 "提案のレベル" を上げる
konifar
26
20k
AIで 浮いた時間で 何をする? 2026春 #devsumi
konifar
21
6k
登壇資料を作る時に意識していること #登壇資料_findy
konifar
7
3.2k
事業部のプロジェクト進行と開発チームの改善の “時間軸" のすり合わせ
konifar
12
4.5k
チーム開発の “地ならし"
konifar
8
18k
AIで 浮いた時間で 何をする? #プロヒス2025
konifar
27
17k
物語を動かす行動"量" #エンジニアニメ
konifar
19
7.3k
提案のレベルを上げる #QiitaConference
konifar
98
42k
Other Decks in Programming
See All in Programming
エンジニアと一緒にテストコードの設計と実装を改善した話
mototakatsu
0
210
Language Server 使ってる? 〜VSCode と Zed の場合〜 / Are you using a Language Server? ~For VS Code and Zed~
handlename
0
800
生成AI時代にこそ効くGo | Why Go Works in the Age of Generative AI
mom0tomo
8
3.3k
PHPで使える日時の表現と、その知り方 #frontend_phpcon_do
o0h
PRO
0
260
AI 時代のソフトウェア設計の学び方
masuda220
PRO
29
13k
セキュリティの専門家じゃなくてもできる。「セキュリティ意識」をアップデートして サプライチェーン攻撃への耐性を高めよう。
tk3fftk
5
890
ローカルLLMを使ってB2Bサービスを作っていての学び
yaotti
0
200
LLMによるContent Moderationの本番運用の裏側と品質担保への挑戦
suikabar
3
720
AI 輔助遺留系統現代化的經驗分享
jame2408
1
840
Oxcを導入して開発体験が向上した話
yug1224
4
320
その問い、本当に正しいですか?AI時代のエンジニアに必要な哲学と認知科学 / ai-philosophy-cognitive-science
minodriven
11
5.9k
Performance Engineering for Everyone
elenatanasoiu
0
190
Featured
See All Featured
Why Mistakes Are the Best Teachers: Turning Failure into a Pathway for Growth
auna
0
160
A Guide to Academic Writing Using Generative AI - A Workshop
ks91
PRO
1
330
The Illustrated Children's Guide to Kubernetes
chrisshort
51
52k
What does AI have to do with Human Rights?
axbom
PRO
1
2.2k
Build The Right Thing And Hit Your Dates
maggiecrowley
39
3.2k
Agile Actions for Facilitating Distributed Teams - ADO2019
mkilby
0
210
Paper Plane (Part 1)
katiecoart
PRO
0
9.1k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
PRO
201
75k
Building Better People: How to give real-time feedback that sticks.
wjessup
370
20k
How to train your dragon (web standard)
notwaldorf
97
6.7k
Product Roadmaps are Hard
iamctodd
PRO
55
12k
Efficient Content Optimization with Google Search Console & Apps Script
katarinadahlin
PRO
1
630
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Ͱͤͳ͍͜ͱଟ͍ ͷͰɺৄ͍࠙͠ձͰ
͋Γ͕ͱ͏͍͟͝·ͨ͠