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
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
Taro Nagasawa
December 13, 2013
Technology
3.4k
3
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
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.8k
Kotlin 最新動向2022 #tfcon #techfeed
ntaro
1
2.4k
#Ubie 狂気の認知施策と選考設計
ntaro
14
14k
UbieにおけるサーバサイドKotlin活用事例
ntaro
1
1.2k
KotlinでSpring 完全理解ガイド #jsug
ntaro
6
3.7k
Kotlinでサーバサイドを始めよう!
ntaro
1
1.1k
Androidからサーバーサイドまで!プログラミング言語 Kotlinの魅力 #devboost
ntaro
5
3k
Kotlin Contracts #m3kt
ntaro
4
4.4k
How_to_Test_Server-side_Kotlin.pdf
ntaro
1
570
Other Decks in Technology
See All in Technology
AI駆動開発は個人技からチーム戦へ:組織でAIを使いこなすための実践設計
moongift
PRO
0
390
20260608_Codexの可能性_ノンプログラマー向け_大城追記
doradora09
PRO
0
720
Claude Mythos、Fable...フロンティアAIの最新動向と企業のセキュリティ対策
flatt_security
0
180
Power Automateアップデート情報
miyakemito
0
290
[しろおび夏祭り2026] チャットするAIから、作業するAIへ - 使われ方の変化と、その裏側で起きていること
kk0n
0
960
QAと開発の両側から進める AI活用 -QAプロセスAI支援ツールキットと Inner Loop / Outer Loopの取り組み-
legalontechnologies
PRO
2
400
数値で見る Microsoft MVP 〜Spec Kit と GitHub Copilot Agent で作るデータ可視化ダッシュボード〜
yutakaosada
0
180
【CEDEC2026】ゲームシナリオライターを支援するAIツール開発の実践 ― 設計とプロンプトの工夫 ―
cygames
PRO
1
210
LangfuseによるLLMOps基盤の構築と活用事例
zozotech
PRO
1
230
実践が先生だった— 新卒サーバーエンジニア1年目のリアル
mixi_engineers
PRO
0
230
MIRU 2026 チュートリアル
keisuke198619
0
480
Webの技術とガジェットで子どもも大人も楽しめるワクワク体験を提供する / Qiita Tech Festa Day 2026
you
PRO
1
330
Featured
See All Featured
Lightning Talk: Beautiful Slides for Beginners
inesmontani
PRO
2
620
How to Get Subject Matter Experts Bought In and Actively Contributing to SEO & PR Initiatives.
livdayseo
0
160
Skip the Path - Find Your Career Trail
mkilby
1
170
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
31
2.8k
Put a Button on it: Removing Barriers to Going Fast.
kastner
60
4.5k
It's Worth the Effort
3n
188
29k
Automating Front-end Workflow
addyosmani
1370
210k
Designing Dashboards & Data Visualisations in Web Apps
destraynor
232
55k
Git: the NoSQL Database
bkeepers
PRO
432
67k
How GitHub (no longer) Works
holman
316
150k
Taking LLMs out of the black box: A practical guide to human-in-the-loop distillation
inesmontani
PRO
3
2.3k
技術選定の審美眼(2025年版) / Understanding the Spiral of Technologies 2025 edition
twada
PRO
119
120k
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 ເͱຐ๏ͷͪ࣌ؒ