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
3.4k
Espresso 〜コーヒー片手にUIテスト〜
Android Test Casual Talks #1の資料
Taro Nagasawa
December 13, 2013
Tweet
Share
More Decks by Taro Nagasawa
See All by Taro Nagasawa
Kotlin 最新動向2022 #tfcon #techfeed
ntaro
1
2k
#Ubie 狂気の認知施策と選考設計
ntaro
13
12k
UbieにおけるサーバサイドKotlin活用事例
ntaro
1
1k
KotlinでSpring 完全理解ガイド #jsug
ntaro
6
3.2k
Kotlinでサーバサイドを始めよう!
ntaro
1
840
Androidからサーバーサイドまで!プログラミング言語 Kotlinの魅力 #devboost
ntaro
5
2.3k
Kotlin Contracts #m3kt
ntaro
4
3.5k
How_to_Test_Server-side_Kotlin.pdf
ntaro
1
390
Kotlin Fest 2018 - Opening session
ntaro
0
4.2k
Other Decks in Technology
See All in Technology
ServiceNow Knowledge 24の歩き方 EYストラテジー・アンド・コンサルティング
manarobot
0
190
私が trocco を推す理由
__allllllllez__
1
200
Databricks における 『MLOps』
databricksjapan
2
170
開発パフォーマンスを最大化するための開発体制
ham0215
2
200
家族アルバム みてねにおけるGrafana活用術 / Grafana Meetup Japan Vol.1 LT
isaoshimizu
1
390
EMとして2023年度に頑張ったこと / What we did well in FY2023 as a EM
pauli
1
160
オーナーシップを持つ領域を明確にする
konifar
13
3.1k
マルチアカウント環境への発見的統制の導入
ch1aki
1
1.3k
どうするコスト最適化のトレードオフ
tetsuyaooooo
1
500
コンパウンドスタートアップのためのスケーラブルでセキュアなInfrastructure as Codeパイプラインを考える / Scalable and Secure Infrastructure as Code Pipeline for a Compound Startup
yuyatakeyama
4
4.7k
一生覚えておきたい「システム開発=コミュニケーション」〜初めての実務案件振り返りLT〜
maimyyym
0
120
FrontDoorとWebAppsを組み合わせた際のリダイレクト処理の注意点
kenichirokimura
1
500
Featured
See All Featured
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
501
140k
A Philosophy of Restraint
colly
197
16k
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
20
1.9k
Navigating Team Friction
lara
178
13k
Six Lessons from altMBA
skipperchong
21
3k
Building an army of robots
kneath
300
41k
No one is an island. Learnings from fostering a developers community.
thoeni
16
2.1k
Building a Scalable Design System with Sketch
lauravandoore
456
32k
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
2
1.3k
What's in a price? How to price your products and services
michaelherold
237
11k
BBQ
matthewcrist
80
8.8k
Designing Experiences People Love
moore
136
23k
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 ເͱຐ๏ͷͪ࣌ؒ