Slide 1

Slide 1 text

Espresso ʙίʔώʔยखʹUIςετʙ Android Test Casual Talks #1 2013-12-13 ! ೔ຊAndroidͷձ ொాࢧ෦ @ngsw_taro

Slide 2

Slide 2 text

ࣗݾ঺հ • ͨΖ͏ @ngsw_taro • ೔ຊAndroidͷձ ொాࢧ෦ • ʮເͱຐ๏ͷ଴ͪ࣌ؒʯ։ൃऀ • ࣗশKotlinΤόϯδΣϦετ

Slide 3

Slide 3 text

ເͱຐ๏ͷ଴ͪ࣌ؒ • σxζχʔϥϯυͷ଴ͪ࣌ؒΛௐ΂ΔΞϓϦ • ໿18ສDL • ධՁ 4.4 / 5.0

Slide 4

Slide 4 text

Kotlin • JetBrainsൃͷJVMݴޠ • Android StudioͰ࢖͑Δʂ • kotlinAndroidLib var clicked = false! button.setOnClickListener {! ! clicked = true! }

Slide 5

Slide 5 text

ຊ୊ Espresso࢖ͬͯΈͨ

Slide 6

Slide 6 text

Espressoͱ͸ •UIςετͷҝͷAPIηοτ •Ϣʔβࢹ఺ͷςετ޲͚

Slide 7

Slide 7 text

UIςετͷҝͷAPIηοτ • ViewMatcher: ViewΛरͬͯ • ViewAction: ViewΛૢ࡞ͯ͠ • ViewAssertion: Viewͷঢ়ଶΛ͔֬ΊΔ

Slide 8

Slide 8 text

Ϣʔβࢹ఺ͷςετ޲͚ • ໨ʹݟ͑Δ෦෼Λςετ͢Δ • ϥΠϑαΠΫϧʁϞοΫʁɹɹɹ ͦΜͳΜ஌ΒΜ

Slide 9

Slide 9 text

؆୯ͳྫ

Slide 10

Slide 10 text

• ໊લΛೖྗ͢ΔͱϘλϯ͕༗ޮʹͳΔ • ϘλϯΛԡ͢ͱѫࡰจ͕දࣔ͞ΕΔ(ผActivity)

Slide 11

Slide 11 text

public void testInitialNameEditText() {! onView(withId(R.id.nameEditText))! .check(matches(isCompletelyDisplayed()))! .check(matches(hasFocus()))! .check(matches(withText("")));! } nameEditTextͷॳظঢ়ଶͷςετ

Slide 12

Slide 12 text

public void testInitialNameEditText() {! onView(withId(R.id.nameEditText))! .check(matches(isCompletelyDisplayed()))! .check(matches(hasFocus()))! .check(matches(withText("")));! } ViewInteractionΛฦ͢ɻ ͜Εʹରͯ͠ΞΫγϣϯΛૹͬͨΓɺ Ξαʔτͨ͠Γ͢Δɻ nameEditTextͷॳظঢ়ଶͷςετ

Slide 13

Slide 13 text

public void testInitialNameEditText() {! onView(withId(R.id.nameEditText))! .check(matches(isCompletelyDisplayed()))! .check(matches(hasFocus()))! .check(matches(withText("")));! } ViewMatcherɻ View༻ʹಛԽ͞ΕͨMatcher͕͋Δɻ ͜͜Ͱ͸ResourceIDͰViewΛऔಘɻ nameEditTextͷॳظঢ়ଶͷςετ

Slide 14

Slide 14 text

public void testInitialNameEditText() {! onView(withId(R.id.nameEditText))! .check(matches(isCompletelyDisplayed()))! .check(matches(hasFocus()))! .check(matches(withText("")));! } ViewInteractionͷϝιουɻ checkͱperformͷ2ͭ͋Δɻ checkͰ͸ViewAssertionΛऔΔɻ nameEditTextͷॳظঢ়ଶͷςετ

Slide 15

Slide 15 text

public void testInitialNameEditText() {! onView(withId(R.id.nameEditText))! .check(matches(isCompletelyDisplayed()))! .check(matches(hasFocus()))! .check(matches(withText("")));! } ViewAssertionɻ ViewMatcherΛऔͬͯɺ Ϛον͢Δ͔ݕূ͢Δɻ nameEditTextͷॳظঢ়ଶͷςετ

Slide 16

Slide 16 text

public void testInitialNameEditText() {! onView(withId(R.id.nameEditText))! .check(matches(isCompletelyDisplayed()))! .check(matches(hasFocus()))! .check(matches(withText("")));! } ViewMatcherɻ View͕ը໘಺ʹશͯදࣔ͞Ε͍ͯΕ͹ Ϛον͢Δɻ nameEditTextͷॳظঢ়ଶͷςετ

Slide 17

Slide 17 text

public void testInitialNameEditText() {! onView(withId(R.id.nameEditText))! .check(matches(isCompletelyDisplayed()))! .check(matches(hasFocus()))! .check(matches(withText("")));! } ςΩετ ϑΥʔΧεΛ࣋ͬͯΔ nameEditTextͷॳظঢ়ଶͷςετ

Slide 18

Slide 18 text

public void testInitialGreetButton() {! onView(withId(R.id.greetButton))! .check(matches(isCompletelyDisplayed()))! .check(matches(not(isEnabled())))! .check(matches(withText("Greet!")));! } greetButtonͷॳظঢ়ଶͷςετ

Slide 19

Slide 19 text

public void testInitialGreetButton() {! onView(withId(R.id.greetButton))! .check(matches(isCompletelyDisplayed()))! .check(matches(not(isEnabled())))! .check(matches(withText("Greet!")));! } greetButtonͷॳظঢ়ଶͷςετ ࠷ॳ͸Ϙλϯ͕ԡͤͳ͍͜ͱΛ֬ೝ

Slide 20

Slide 20 text

public void testInputNameEditText() {! onView(withId(R.id.nameEditText))! .perform(typeText(“Taro"));! ! onView(withId(R.id.greetButton))! .check(matches(isEnabled()));! } nameEditTextʹೖྗ͢Δςετ

Slide 21

Slide 21 text

public void testInputNameEditText() {! onView(withId(R.id.nameEditText))! .perform(typeText(“Taro"));! ! onView(withId(R.id.greetButton))! .check(matches(isEnabled()));! } nameEditTextʹೖྗ͢Δςετ ςΩετΛೖྗ͢Δ Viewʹରͯ͠ΞΫγϣϯΛૹΔ Ϙλϯ͕༗ޮ͔֬ೝ

Slide 22

Slide 22 text

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Λԡ͢ςετ

Slide 23

Slide 23 text

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Λԡ͢ςετ ΫϦοΫʂ

Slide 24

Slide 24 text

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Λ֬ೝ͢Δʂ

Slide 25

Slide 25 text

֦ு͕؆୯

Slide 26

Slide 26 text

͖ͬ͞ςετͯ͠ͳ͍ Hintͱͯ͠ʮNameʯ͕ ઃఆ͞Ε͍ͯΔ͔֬ೝ͍ͨ͠

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

public static Matcher withHint(final String hint) {! final Matcher stringMatcher = is(hint);! checkNotNull(stringMatcher);! return new BoundedMatcher(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

Slide 29

Slide 29 text

public static Matcher withHint(final String hint) {! final Matcher stringMatcher = is(hint);! checkNotNull(stringMatcher);! return new BoundedMatcher(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Λఆٛ͢Ε͹͍͍͚ͩ

Slide 30

Slide 30 text

ͪΐͬͱ໘౗ͳͱ͜Ζ

Slide 31

Slide 31 text

ྫ͑͹ϑϥάϝϯτ Fragment

Slide 32

Slide 32 text

TextViewʹಉ͡IDΛ࢖͏ R.id.greetView R.id.greetView

Slide 33

Slide 33 text

ྫ֎͕ى͜Δ onView(withId(R.id.greetView))! .check(matches(withText(“”))); 'with id: is <2131230720>' matches multiple views in the hierarchy. Ϗϡʔ֊૚ͰಉҰID͕ෳ਺͋Δ͔Β

Slide 34

Slide 34 text

௥Ճ৚݅ͰߜΓࠐΉ onView(allOf(! withId(R.id.greetView),! hasSibling(withText("Preview: ")! ))).check(matches(withText("")));

Slide 35

Slide 35 text

௥Ճ৚݅ͰߜΓࠐΉ onView(allOf(! withId(R.id.greetView),! hasSibling(withText("Preview: ")! ))).check(matches(withText(""))); ෳ਺ͷMatcherΛऔΔ “Preview: ”ͱදࣔͯ͠Δ ࢞ຓϏϡʔΛ࣋ͭ

Slide 36

Slide 36 text

ͪΐͬͱ࢒೦ͳͱ͜Ζ

Slide 37

Slide 37 text

ςετࣦഊ onView(withId(R.id.greetButton))! .check(matches(withText(“Greet!”))); Expected: with text is “Greet!” Actual: with text is “Greet”

Slide 38

Slide 38 text

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}"

Slide 39

Slide 39 text

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}"

Slide 40

Slide 40 text

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}"

Slide 41

Slide 41 text

ʊਓਓਓਓਓਓਓʊ ʼɹಡΈͮΒ͍ɹʻ ʉY^Y^Y^Y^Y^Yʉ

Slide 42

Slide 42 text

·ͱΊ • Espresso͸ViewʹಛԽͨ͠ςετAPI • hamcrest͔ͩΒҠߦίετ௿͘ɺ֦ுੑߴ͠ • ͍͔ʹMatcherΛ૊Έ߹ΘͤΔ͔͕ॏཁͬΆ͍ • ϨϙʔτಡΈͮΒ͍ • Kotlin͸ૉ੖Β͍͠ݴޠ

Slide 43

Slide 43 text

͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠ɻ @ngsw_taro ເͱຐ๏ͷ଴ͪ࣌ؒ