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
Espresso 〜コーヒー片手にUIテスト〜
Search
Taro Nagasawa
December 13, 2013
Technology
3.4k
3
Share
Espresso 〜コーヒー片手にUIテスト〜
Android Test Casual Talks #1の資料
Taro Nagasawa
December 13, 2013
More Decks by Taro Nagasawa
See All by Taro Nagasawa
Android開発者のための Kotlin Multiplatform入門
ntaro
0
1.6k
Kotlin 最新動向2022 #tfcon #techfeed
ntaro
1
2.4k
#Ubie 狂気の認知施策と選考設計
ntaro
13
14k
UbieにおけるサーバサイドKotlin活用事例
ntaro
1
1.2k
KotlinでSpring 完全理解ガイド #jsug
ntaro
6
3.6k
Kotlinでサーバサイドを始めよう!
ntaro
1
1k
Androidからサーバーサイドまで!プログラミング言語 Kotlinの魅力 #devboost
ntaro
5
3k
Kotlin Contracts #m3kt
ntaro
4
4.4k
How_to_Test_Server-side_Kotlin.pdf
ntaro
1
560
Other Decks in Technology
See All in Technology
タクシーアプリ『GO』の実践的データ活用
mot_techtalk
2
120
Claude Codeを組織で使いこなす— サーバサイドAIエージェント運用の実践知
techtekt
PRO
0
200
Oracle Cloud Infrastructure IaaS 新機能アップデート 2026/3 - 2026/5
oracle4engineer
PRO
1
170
Chart.js が簡単に使えるようになっていたので OGP 画像生成に使った話
kamekyame
0
150
AI活用を推進するために ファインディが下した、一つの小さな決断
starfish719
0
240
エンジニアは生成AIと どのように向き合うべきか? ことばの意味という観点から
verypluming
3
350
Cloud Run のアップデート 触ってみる&紹介
gre212
0
300
Unlocking the Apps
pimterry
0
190
地元にいないローカルオーガナイザーの立ち回り
uvb_76
1
460
Oracle AI Database@Azure:サービス概要のご紹介
oracle4engineer
PRO
6
1.9k
そのPoC、何を検証したつもりでしたか? AIプロダクトの価値検証で陥った落とし穴
techtekt
PRO
0
110
Sony_KMP_Journey_KotlinConf2026
sony
2
210
Featured
See All Featured
Put a Button on it: Removing Barriers to Going Fast.
kastner
60
4.3k
The AI Revolution Will Not Be Monopolized: How open-source beats economies of scale, even for LLMs
inesmontani
PRO
3
3.5k
How to Think Like a Performance Engineer
csswizardry
28
2.6k
The SEO Collaboration Effect
kristinabergwall1
1
470
Faster Mobile Websites
deanohume
310
31k
Navigating Weather and Climate Data
rabernat
0
210
DBのスキルで生き残る技術 - AI時代におけるテーブル設計の勘所
soudai
PRO
65
55k
We Have a Design System, Now What?
morganepeng
55
8.2k
The Illustrated Children's Guide to Kubernetes
chrisshort
51
52k
HDC tutorial
michielstock
2
690
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
254
22k
Google's AI Overviews - The New Search
badams
0
1k
Transcript
Espresso ʙίʔώʔยखʹUIςετʙ Android Test Casual Talks #1 2013-12-13 ! ຊAndroidͷձ
ொాࢧ෦ @ngsw_taro
ࣗݾհ • ͨΖ͏ @ngsw_taro • ຊAndroidͷձ ொాࢧ෦ • ʮເͱຐ๏ͷͪ࣌ؒʯ։ൃऀ •
ࣗশKotlinΤόϯδΣϦετ
ເͱຐ๏ͷͪ࣌ؒ • σxζχʔϥϯυͷͪ࣌ؒΛௐΔΞϓϦ • 18ສDL • ධՁ 4.4 / 5.0
Kotlin • JetBrainsൃͷJVMݴޠ • Android StudioͰ͑Δʂ • kotlinAndroidLib var clicked
= false! button.setOnClickListener {! ! clicked = true! }
ຊ EspressoͬͯΈͨ
Espressoͱ •UIςετͷҝͷAPIηοτ •Ϣʔβࢹͷςετ͚
UIςετͷҝͷAPIηοτ • ViewMatcher: ViewΛरͬͯ • ViewAction: ViewΛૢ࡞ͯ͠ • ViewAssertion: Viewͷঢ়ଶΛ͔֬ΊΔ
Ϣʔβࢹͷςετ͚ • ʹݟ͑Δ෦Λςετ͢Δ • ϥΠϑαΠΫϧʁϞοΫʁɹɹɹ ͦΜͳΜΒΜ
؆୯ͳྫ
• ໊લΛೖྗ͢ΔͱϘλϯ͕༗ޮʹͳΔ • ϘλϯΛԡ͢ͱѫࡰจ͕දࣔ͞ΕΔ(ผActivity)
public void testInitialNameEditText() {! onView(withId(R.id.nameEditText))! .check(matches(isCompletelyDisplayed()))! .check(matches(hasFocus()))! .check(matches(withText("")));! } nameEditTextͷॳظঢ়ଶͷςετ
public void testInitialNameEditText() {! onView(withId(R.id.nameEditText))! .check(matches(isCompletelyDisplayed()))! .check(matches(hasFocus()))! .check(matches(withText("")));! } ViewInteractionΛฦ͢ɻ
͜Εʹରͯ͠ΞΫγϣϯΛૹͬͨΓɺ Ξαʔτͨ͠Γ͢Δɻ nameEditTextͷॳظঢ়ଶͷςετ
public void testInitialNameEditText() {! onView(withId(R.id.nameEditText))! .check(matches(isCompletelyDisplayed()))! .check(matches(hasFocus()))! .check(matches(withText("")));! } ViewMatcherɻ
View༻ʹಛԽ͞ΕͨMatcher͕͋Δɻ ͜͜ͰResourceIDͰViewΛऔಘɻ nameEditTextͷॳظঢ়ଶͷςετ
public void testInitialNameEditText() {! onView(withId(R.id.nameEditText))! .check(matches(isCompletelyDisplayed()))! .check(matches(hasFocus()))! .check(matches(withText("")));! } ViewInteractionͷϝιουɻ
checkͱperformͷ2ͭ͋Δɻ checkͰViewAssertionΛऔΔɻ nameEditTextͷॳظঢ়ଶͷςετ
public void testInitialNameEditText() {! onView(withId(R.id.nameEditText))! .check(matches(isCompletelyDisplayed()))! .check(matches(hasFocus()))! .check(matches(withText("")));! } ViewAssertionɻ
ViewMatcherΛऔͬͯɺ Ϛον͢Δ͔ݕূ͢Δɻ nameEditTextͷॳظঢ়ଶͷςετ
public void testInitialNameEditText() {! onView(withId(R.id.nameEditText))! .check(matches(isCompletelyDisplayed()))! .check(matches(hasFocus()))! .check(matches(withText("")));! } ViewMatcherɻ
View͕ը໘ʹશͯදࣔ͞Ε͍ͯΕ Ϛον͢Δɻ nameEditTextͷॳظঢ়ଶͷςετ
public void testInitialNameEditText() {! onView(withId(R.id.nameEditText))! .check(matches(isCompletelyDisplayed()))! .check(matches(hasFocus()))! .check(matches(withText("")));! } ςΩετ
ϑΥʔΧεΛ࣋ͬͯΔ nameEditTextͷॳظঢ়ଶͷςετ
public void testInitialGreetButton() {! onView(withId(R.id.greetButton))! .check(matches(isCompletelyDisplayed()))! .check(matches(not(isEnabled())))! .check(matches(withText("Greet!")));! } greetButtonͷॳظঢ়ଶͷςετ
public void testInitialGreetButton() {! onView(withId(R.id.greetButton))! .check(matches(isCompletelyDisplayed()))! .check(matches(not(isEnabled())))! .check(matches(withText("Greet!")));! } greetButtonͷॳظঢ়ଶͷςετ
࠷ॳϘλϯ͕ԡͤͳ͍͜ͱΛ֬ೝ
public void testInputNameEditText() {! onView(withId(R.id.nameEditText))! .perform(typeText(“Taro"));! ! onView(withId(R.id.greetButton))! .check(matches(isEnabled()));! }
nameEditTextʹೖྗ͢Δςετ
public void testInputNameEditText() {! onView(withId(R.id.nameEditText))! .perform(typeText(“Taro"));! ! onView(withId(R.id.greetButton))! .check(matches(isEnabled()));! }
nameEditTextʹೖྗ͢Δςετ ςΩετΛೖྗ͢Δ Viewʹରͯ͠ΞΫγϣϯΛૹΔ Ϙλϯ͕༗ޮ͔֬ೝ
onView(withId(R.id.nameEditText))! .perform(typeText("Taro"));! onView(withId(R.id.greetButton))! .perform(click());! ! onView(withId(R.id.greetView))! .check(matches(isCompletelyDisplayed()))! .check(matches(withText("Hello, Taro!"))); greetButtonΛԡ͢ςετ
onView(withId(R.id.nameEditText))! .perform(typeText("Taro"));! onView(withId(R.id.greetButton))! .perform(click());! ! onView(withId(R.id.greetView))! .check(matches(isCompletelyDisplayed()))! .check(matches(withText("Hello, Taro!"))); greetButtonΛԡ͢ςετ
ΫϦοΫʂ
onView(withId(R.id.nameEditText))! .perform(typeText("Taro"));! onView(withId(R.id.greetButton))! .perform(click());! ! onView(withId(R.id.greetView))! .check(matches(isCompletelyDisplayed()))! .check(matches(withText("Hello, Taro!"))); greetButtonΛԡ͢ςετ
৽͘͠ىಈ͢ΔActivityͷ ViewΛ֬ೝ͢Δʂ
֦ு͕؆୯
͖ͬ͞ςετͯ͠ͳ͍ Hintͱͯ͠ʮNameʯ͕ ઃఆ͞Ε͍ͯΔ͔֬ೝ͍ͨ͠
public void testInitialNameEditText() {! onView(withId(R.id.nameEditText))! .check(matches(isCompletelyDisplayed()))! .check(matches(hasFocus()))! .check(matches(withText(“”)))! .check(matches(withHint(“Name”)))! }
API͕ఏڙ͞Ε͍ͯͳ͍ The method withHint(String) is undefined
public static Matcher<View> withHint(final String hint) {! final Matcher<String> stringMatcher
= is(hint);! checkNotNull(stringMatcher);! return new BoundedMatcher<View, EditText>(EditText.class) {! @Override! public void describeTo(Description description) {! description.appendText("with hint: ");! stringMatcher.describeTo(description);! }! ! @Override! protected boolean matchesSafely(EditText editText) {! return stringMatcher.matches(editText.getHint().toString());! }! };! } ͳ͍ͳΒࣗͰ࡞Εʢry
public static Matcher<View> withHint(final String hint) {! final Matcher<String> stringMatcher
= is(hint);! checkNotNull(stringMatcher);! return new BoundedMatcher<View, EditText>(EditText.class) {! @Override! public void describeTo(Description description) {! description.appendText("with hint: ");! stringMatcher.describeTo(description);! }! ! @Override! protected boolean matchesSafely(EditText editText) {! return stringMatcher.matches(editText.getHint().toString());! }! };! } ͳ͍ͳΒࣗͰ࡞Εʢry ཁhamcrestͷ MatcherΛఆٛ͢Ε͍͍͚ͩ
ͪΐͬͱ໘ͳͱ͜Ζ
ྫ͑ϑϥάϝϯτ Fragment
TextViewʹಉ͡IDΛ͏ R.id.greetView R.id.greetView
ྫ֎͕ى͜Δ onView(withId(R.id.greetView))! .check(matches(withText(“”))); 'with id: is <2131230720>' matches multiple views
in the hierarchy. Ϗϡʔ֊ͰಉҰID͕ෳ͋Δ͔Β
Ճ݅ͰߜΓࠐΉ onView(allOf(! withId(R.id.greetView),! hasSibling(withText("Preview: ")! ))).check(matches(withText("")));
Ճ݅ͰߜΓࠐΉ onView(allOf(! withId(R.id.greetView),! hasSibling(withText("Preview: ")! ))).check(matches(withText(""))); ෳͷMatcherΛऔΔ “Preview: ”ͱදࣔͯ͠Δ ࢞ຓϏϡʔΛ࣋ͭ
ͪΐͬͱ೦ͳͱ͜Ζ
ςετࣦഊ onView(withId(R.id.greetButton))! .check(matches(withText(“Greet!”))); Expected: with text is “Greet!” Actual: with
text is “Greet”
Espresso͞Μ͕ు͘Ϩϙʔτ com.google.android.apps.common.testing.ui.espresso .base.DefaultFailureHandler $AssertionFailedWithCauseError: 'with text: is "Greet!"' doesn't match
the selected view. Expected: with text: is "Greet!" Got: "Button{id=2131230722, res-name=greetButton, visibility=VISIBLE, width=275, height=145, has- focus=false, has-focusable=true, has-window- focus=true, is-clickable=true, is-enabled=false, is- focused=false, is-focusable=true, is-layout- requested=false, is-selected=false, root-is-layout- requested=false, has-input-connection=false, x=142.0, y=240.0, text=Greet, input-type=0, ime- target=false}"
Espresso͞Μ͕ు͘Ϩϙʔτ com.google.android.apps.common.testing.ui.espresso .base.DefaultFailureHandler $AssertionFailedWithCauseError: 'with text: is "Greet!"' doesn't match
the selected view. Expected: with text: is "Greet!" Got: "Button{id=2131230722, res-name=greetButton, visibility=VISIBLE, width=275, height=145, has- focus=false, has-focusable=true, has-window- focus=true, is-clickable=true, is-enabled=false, is- focused=false, is-focusable=true, is-layout- requested=false, is-selected=false, root-is-layout- requested=false, has-input-connection=false, x=142.0, y=240.0, text=Greet, input-type=0, ime- target=false}"
Espresso͞Μ͕ు͘Ϩϙʔτ com.google.android.apps.common.testing.ui.espresso .base.DefaultFailureHandler $AssertionFailedWithCauseError: 'with text: is "Greet!"' doesn't match
the selected view. Expected: with text: is "Greet!" Got: "Button{id=2131230722, res-name=greetButton, visibility=VISIBLE, width=275, height=145, has- focus=false, has-focusable=true, has-window- focus=true, is-clickable=true, is-enabled=false, is- focused=false, is-focusable=true, is-layout- requested=false, is-selected=false, root-is-layout- requested=false, has-input-connection=false, x=142.0, y=240.0, text=Greet, input-type=0, ime- target=false}"
ʊਓਓਓਓਓਓਓʊ ʼɹಡΈͮΒ͍ɹʻ ʉY^Y^Y^Y^Y^Yʉ
·ͱΊ • EspressoViewʹಛԽͨ͠ςετAPI • hamcrest͔ͩΒҠߦίετ͘ɺ֦ுੑߴ͠ • ͍͔ʹMatcherΛΈ߹ΘͤΔ͔͕ॏཁͬΆ͍ • ϨϙʔτಡΈͮΒ͍ •
KotlinૉΒ͍͠ݴޠ
͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠ɻ @ngsw_taro ເͱຐ๏ͷͪ࣌ؒ