ข้อมูลเบื้องต้นเกี่ยวกับเอสเปรสโซ

เอกสารนี้จะอธิบายวิธีทำงานทั่วไปของการทดสอบอัตโนมัติโดยใช้ Espresso API

Espresso API ส่งเสริมให้ผู้เขียนการทดสอบคิดในแง่ของสิ่งที่ผู้ใช้อาจ ขณะโต้ตอบกับแอปพลิเคชัน เช่น การค้นหาองค์ประกอบ UI และการโต้ตอบ ร่วมกัน ในขณะเดียวกัน กรอบการทำงานก็ป้องกันไม่ให้เข้าถึงกิจกรรมโดยตรง และมุมมองของแอปพลิเคชัน เพราะการยึดวัตถุเหล่านี้ไว้และทำงาน อยู่นอกเธรด UI คือสาเหตุสำคัญของการทดสอบความไม่สม่ำเสมอ ดังนั้นคุณจะ ไม่เห็นเมธอด เช่น getView() และ getCurrentActivity() ใน Espresso API คุณยังสามารถดำเนินการกับข้อมูลพร็อพเพอร์ตี้ได้อย่างปลอดภัยโดยใช้คลาสย่อยของ ViewAction และ ViewAssertion

คอมโพเนนต์ API

ส่วนประกอบหลักของ Espresso ประกอบด้วย:

  • Espresso – จุดแรกเข้าไปยังการโต้ตอบกับทิวทัศน์ (ผ่าน onView() และ onData()) นอกจากนี้ยังแสดง API ที่ไม่จำเป็นต้องเชื่อมโยงกับข้อมูลพร็อพเพอร์ตี้ใดๆ เช่น ด้วยชื่อ pressBack()
  • ViewMatchers – คอลเล็กชันของออบเจ็กต์ที่ใช้ อินเทอร์เฟซ Matcher<? super View> คุณสามารถส่งข้อมูลต่อไปนี้อย่างน้อย 1 รายการไปยัง onView() เพื่อค้นหาข้อมูลพร็อพเพอร์ตี้ภายในลำดับชั้นการแสดงผลปัจจุบัน
  • ViewActions – คอลเล็กชันของ ViewAction ออบเจ็กต์ที่สามารถส่งผ่านไปยัง เมธอด ViewInteraction.perform() เช่น click()
  • ViewAssertions – คอลเล็กชันของออบเจ็กต์ ViewAssertion รายการที่สามารถ ผ่านเมธอด ViewInteraction.check() แล้ว โดยส่วนใหญ่คุณจะใช้เมธอด ตรงกับการยืนยัน ซึ่งใช้ตัวจับคู่มุมมองเพื่อยืนยันสถานะของ มุมมองที่เลือกในปัจจุบัน

ตัวอย่าง

Kotlin

// withId(R.id.my_view) is a ViewMatcher
// click() is a ViewAction
// matches(isDisplayed()) is a ViewAssertion
onView(withId(R.id.my_view))
    .perform(click())
    .check(matches(isDisplayed()))

Java

// withId(R.id.my_view) is a ViewMatcher
// click() is a ViewAction
// matches(isDisplayed()) is a ViewAssertion
onView(withId(R.id.my_view))
    .perform(click())
    .check(matches(isDisplayed()));

ค้นหาข้อมูลพร็อพเพอร์ตี้

ในกรณีส่วนใหญ่ เมธอด onView() จะนำตัวจับคู่แฮมเครสต์ ที่คาดว่าจะตรงกับมุมมอง 1 รายการ และเพียงรายการเดียว — ภายในมุมมองปัจจุบัน ลำดับชั้น เครื่องมือจับคู่มีประสิทธิภาพและจะคุ้นเคยสำหรับผู้ที่เคยใช้ ด้วย Mockito หรือ JUnit หากคุณไม่คุ้นเคยกับการจับคู่แฮมเครสต์ เรา แนะนำให้คุณเริ่มต้นด้วยการดู งานนำเสนอ

ข้อมูลพร็อพเพอร์ตี้ที่ต้องการมักจะมี R.id ที่ไม่ซ้ำและตัวจับคู่ withId แบบง่ายๆ จะ ช่วยจำกัดการค้นหาข้อมูลพร็อพเพอร์ตี้ให้แคบลง อย่างไรก็ตาม มีหลายกรณีที่ ระบุ R.id ไม่ได้ขณะทดสอบเวลาพัฒนา ตัวอย่างเช่น ข้อมูลพร็อพเพอร์ตี้ที่ต้องการ อาจไม่มี R.id หรือ R.id ซ้ำกัน ซึ่งจะทำให้เป็นปกติ การวัดคุมการเขียนมีความยากและซับซ้อน เนื่องจากวิธีปกติในการ เข้าถึงมุมมองด้วย findViewById() จะไม่ทำงาน ดังนั้นคุณจึง ต้องการเข้าถึงสมาชิกส่วนตัวของกิจกรรมหรือส่วนย่อยที่เก็บมุมมองหรือ ค้นหาคอนเทนเนอร์ที่มี R.id ที่รู้จัก และไปที่เนื้อหาของคอนเทนเนอร์สำหรับ มุมมองใดมุมมองหนึ่ง

Espresso จัดการปัญหานี้ได้อย่างสะอาดตาโดยให้คุณจำกัดมุมมองให้แคบลง โดยใช้ออบเจ็กต์ ViewMatcher ที่มีอยู่หรือออบเจ็กต์ที่กำหนดเอง

การค้นหามุมมองตาม R.id นั้นทำได้ง่ายๆ เพียงเรียกใช้ onView():

Kotlin

onView(withId(R.id.my_view))

Java

onView(withId(R.id.my_view));

ในบางครั้ง ค่า R.id อาจมีการแชร์กันระหว่างข้อมูลพร็อพเพอร์ตี้หลายรายการ เมื่อเกิดเหตุการณ์นี้ขึ้น การพยายามใช้ R.id บางอย่างจะทำให้มีข้อยกเว้น เช่น AmbiguousViewMatcherException ข้อความข้อยกเว้นจะแสดงข้อความ การนำเสนอลำดับชั้นของมุมมองปัจจุบัน ซึ่งคุณสามารถค้นหาและดูได้ ข้อมูลพร็อพเพอร์ตี้ที่ตรงกับ R.id ที่ซ้ำกัน ได้แก่

java.lang.RuntimeException:
androidx.test.espresso.AmbiguousViewMatcherException
This matcher matches multiple views in the hierarchy: (withId: is <123456789>)

...

+----->SomeView{id=123456789, res-name=plus_one_standard_ann_button,
visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true,
window-focus=true, is-focused=false, is-focusable=false, enabled=true,
selected=false, is-layout-requested=false, text=,
root-is-layout-requested=false, x=0.0, y=625.0, child-count=1}
****MATCHES****
|
+------>OtherView{id=123456789, res-name=plus_one_standard_ann_button,
visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true,
window-focus=true, is-focused=false, is-focusable=true, enabled=true,
selected=false, is-layout-requested=false, text=Hello!,
root-is-layout-requested=false, x=0.0, y=0.0, child-count=1}
****MATCHES****

เมื่อดูแอตทริบิวต์ต่างๆ ของมุมมองแล้ว คุณอาจเห็นว่า พร็อพเพอร์ตี้ที่ระบุตัวตนได้ ในตัวอย่างข้างต้น มุมมองหนึ่งมีข้อความ "Hello!" คุณสามารถใช้วิธีนี้เพื่อจำกัดการค้นหาให้แคบลงโดยใช้ชุดค่าผสม ตัวจับคู่:

Kotlin

onView(allOf(withId(R.id.my_view), withText("Hello!")))

Java

onView(allOf(withId(R.id.my_view), withText("Hello!")));

นอกจากนี้ คุณยังเลือกที่จะไม่คืนค่าการจับคู่ที่ตรงกันได้ดังนี้

Kotlin

onView(allOf(withId(R.id.my_view), not(withText("Unwanted"))))

Java

onView(allOf(withId(R.id.my_view), not(withText("Unwanted"))));

โปรดดู ViewMatchers เครื่องมือจับคู่มุมมองจาก Espresso

ข้อควรพิจารณา

  • ในแอปพลิเคชันที่มีลักษณะการทำงานที่ดี มุมมองทั้งหมดที่ผู้ใช้โต้ตอบด้วยได้ ควรมีข้อความอธิบายหรือมีคำอธิบายเนื้อหา โปรดดู การทําให้แอปเข้าถึงได้ง่ายขึ้นสําหรับสิ่งต่างๆ มากขึ้น รายละเอียด หากไม่สามารถจำกัดขอบเขตการค้นหาโดยใช้ withText() หรือ คุณ withContentDescription() โปรดพิจารณาว่าข้อความนี้เป็นข้อบกพร่องในการช่วยเหลือพิเศษ
  • ใช้เครื่องมือจับคู่ที่สื่อความหมายซึ่งพบข้อมูลพร็อพเพอร์ตี้ที่คุณต้องการดู สำหรับ อย่าระบุมากเกินไปเนื่องจากจะบังคับให้เฟรมเวิร์กทำงานมากกว่า เป็นสิ่งจำเป็น ตัวอย่างเช่น หากข้อความระบุมุมมองได้อย่างไม่ซ้ำกัน คุณสามารถ ไม่จำเป็นต้องระบุว่าให้สิทธิ์ข้อมูลพร็อพเพอร์ตี้จาก TextView ได้ด้วย สำหรับผู้ที่ชอบ การดู R.id ของการดูก็น่าจะเพียงพอแล้ว
  • หากมุมมองเป้าหมายอยู่ใน AdapterView เช่น ListView GridView หรือ Spinner - เมธอด onView() อาจใช้ไม่ได้ ใน คุณควรใช้ onData() แทน

ดำเนินการกับข้อมูลพร็อพเพอร์ตี้

เมื่อคุณพบตัวจับคู่ที่เหมาะสมกับมุมมองเป้าหมายแล้ว คุณอาจ ทำอินสแตนซ์ของ ViewAction ในคำสั่งนั้นโดยใช้เมธอด

เช่น หากต้องการคลิกข้อมูลพร็อพเพอร์ตี้ ให้ทําดังนี้

Kotlin

onView(...).perform(click())

Java

onView(...).perform(click());

คุณสามารถดำเนินการได้มากกว่า 1 รายการด้วยการเรียกใช้ครั้งเดียว

Kotlin

onView(...).perform(typeText("Hello"), click())

Java

onView(...).perform(typeText("Hello"), click());

หากมุมมองที่คุณกําลังทํางานด้วยอยู่ใน ScrollView (แนวตั้งหรือ แนวนอน) ให้พิจารณาการดำเนินการก่อนหน้าที่กำหนดให้มุมมอง ที่แสดง เช่น click() และ typeText() ด้วย scrollTo() ช่วงเวลานี้ ตรวจสอบว่ามุมมองปรากฏขึ้นก่อนที่จะดำเนินการอย่างอื่น

Kotlin

onView(...).perform(scrollTo(), click())

Java

onView(...).perform(scrollTo(), click());

โปรดดู ViewActions เพื่อดูการทำงานโดย Espresso

ตรวจสอบการยืนยันการดู

คุณสามารถใช้การยืนยันกับมุมมองที่เลือกในปัจจุบันด้วย check() การยืนยันที่ใช้บ่อยที่สุดคือการยืนยัน matches() โดยใช้ ViewMatcher เพื่อยืนยันสถานะของมุมมองที่เลือกในปัจจุบัน

ตัวอย่างเช่น หากต้องการตรวจสอบว่ามุมมองมีข้อความ "Hello!" หรือไม่ ให้ทำดังนี้

Kotlin

onView(...).check(matches(withText("Hello!")))

Java

onView(...).check(matches(withText("Hello!")));

หากต้องการยืนยันว่า "Hello!" เป็นเนื้อหาของการแสดงผล การดำเนินการต่อไปนี้ถือเป็นแนวทางปฏิบัติที่ไม่เหมาะสม

Kotlin

// Don't use assertions like withText inside onView.
onView(allOf(withId(...), withText("Hello!"))).check(matches(isDisplayed()))

Java

// Don't use assertions like withText inside onView.
onView(allOf(withId(...), withText("Hello!"))).check(matches(isDisplayed()));

ในทางกลับกัน หากคุณต้องการยืนยันว่าการดูที่มีข้อความ "Hello!" เป็น แสดงตัวได้ ตัวอย่างเช่น หลังจากมีการเปลี่ยนแปลงค่าสถานะการมองเห็น โค้ดก็ไม่มีปัญหา

ดูการทดสอบการยืนยันอย่างง่าย

ในตัวอย่างนี้ SimpleActivity มี Button และ TextView เมื่อ คลิกปุ่มแล้ว เนื้อหาของ TextView จะเปลี่ยนเป็น "Hello Espresso!"

วิธีทดสอบด้วย Espresso มีดังนี้

คลิกปุ่ม

ขั้นตอนแรกคือมองหาพร็อพเพอร์ตี้ที่จะช่วยค้นหาปุ่ม ใน SimpleActivity มี R.id ที่ไม่ซ้ำกันตามที่คาดไว้

Kotlin

onView(withId(R.id.button_simple))

Java

onView(withId(R.id.button_simple));

วิธีดำเนินการคลิกมีดังนี้

Kotlin

onView(withId(R.id.button_simple)).perform(click())

Java

onView(withId(R.id.button_simple)).perform(click());

ยืนยันข้อความ TextView

TextView ที่มีข้อความให้ยืนยันมี R.id ที่ไม่ซ้ำกันด้วย:

Kotlin

onView(withId(R.id.text_simple))

Java

onView(withId(R.id.text_simple));

วิธียืนยันข้อความเนื้อหา

Kotlin

onView(withId(R.id.text_simple)).check(matches(withText("Hello Espresso!")))

Java

onView(withId(R.id.text_simple)).check(matches(withText("Hello Espresso!")));

ตรวจสอบการโหลดข้อมูลในมุมมองอะแดปเตอร์

AdapterView เป็นวิดเจ็ตประเภทพิเศษที่โหลดข้อมูลแบบไดนามิกจาก อะแดปเตอร์ ตัวอย่างที่พบบ่อยที่สุดของ AdapterView คือ ListView อาส ต่างจากวิดเจ็ตแบบคงที่ เช่น LinearLayout แต่มีเพียง ระบบอาจโหลดรายการย่อย AdapterView รายการลงในลำดับชั้นการแสดงผลปัจจุบัน องค์ประกอบ การค้นหา onView() จะไม่พบมุมมองที่ไม่ได้โหลดในขณะนี้

Espresso จัดการเรื่องนี้โดยการระบุจุดแรกเข้า onData() แยกต่างหาก โหลดรายการอะแดปเตอร์ที่เป็นปัญหาได้ก่อน ทำให้โฟกัสก่อน หรือบริษัทย่อยๆ ของ Google

คำเตือน: การติดตั้งที่กำหนดเองของ AdapterView อาจมีปัญหาเกี่ยวกับ onData() หากพวกเขาละเมิดสัญญาการรับค่า โดยเฉพาะ getItem() API ในกรณีดังกล่าว การดำเนินการที่ดีที่สุดคือ เปลี่ยนโครงสร้างภายในโค้ดของแอปพลิเคชัน หากทำไม่ได้ คุณสามารถใช้ ตรงกับ AdapterViewProtocol ที่กำหนดเอง สำหรับข้อมูลเพิ่มเติม โปรดดู ให้ดูที่ค่าเริ่มต้น คลาส AdapterViewProtocols จาก Espresso

การทดสอบแบบง่ายของมุมมองอะแดปเตอร์

การทดสอบง่ายๆ นี้จะสาธิตวิธีใช้ onData() SimpleActivity มี Spinner กับรายการที่เป็นเครื่องดื่มประเภทกาแฟ 2-3 รายการ เมื่อ รายการที่เลือกไว้ มี TextView ที่เปลี่ยนเป็น "One %s a day!" โดยที่ %s แสดงรายการที่เลือก

เป้าหมายของการทดสอบนี้คือการเปิด Spinner เลือกรายการที่เจาะจง และ ตรวจสอบว่า TextView มีรายการดังกล่าวอยู่ เนื่องจากชั้นเรียน Spinner อิงตาม ใน AdapterView ขอแนะนำให้ใช้ onData() แทน onView() สำหรับ การจับคู่สินค้า

เปิดการเลือกรายการ

Kotlin

onView(withId(R.id.spinner_simple)).perform(click())

Java

onView(withId(R.id.spinner_simple)).perform(click());

เลือกรายการ

สําหรับการเลือกรายการ Spinner จะสร้าง ListView พร้อมเนื้อหา มุมมองนี้อาจยาวมาก และองค์ประกอบอาจไม่ได้แสดงในข้อมูลพร็อพเพอร์ตี้ ลำดับชั้น การใช้ onData() เราจะบังคับให้องค์ประกอบที่ต้องการเข้าสู่มุมมอง ลำดับชั้น รายการใน Spinner เป็นสตริง เราจึงต้องการจับคู่รายการ ที่เท่ากับสตริง "Americano":

Kotlin

onData(allOf(`is`(instanceOf(String::class.java)),
        `is`("Americano"))).perform(click())

Java

onData(allOf(is(instanceOf(String.class)), is("Americano"))).perform(click());

ยืนยันว่าข้อความถูกต้อง

Kotlin

onView(withId(R.id.spinnertext_simple))
    .check(matches(withText(containsString("Americano"))))

Java

onView(withId(R.id.spinnertext_simple))
    .check(matches(withText(containsString("Americano"))));

การแก้ไขข้อบกพร่อง

Espresso จะให้ข้อมูลการแก้ไขข้อบกพร่องที่เป็นประโยชน์เมื่อการทดสอบล้มเหลว โดยมีรายละเอียดดังนี้

การบันทึก

Espresso จะบันทึกการดำเนินการในมุมมองทั้งหมดไปยัง Logcat เช่น

ViewInteraction: Performing 'single click' action on view with text: Espresso

ดูลำดับชั้น

เอสเพรสโซพิมพ์ลำดับชั้นการแสดงผลในข้อความข้อยกเว้นเมื่อ onView() ล้มเหลว

  • หาก onView() ไม่พบมุมมองเป้าหมาย NoMatchingViewException จะเป็น โยนได้ คุณสามารถตรวจสอบลำดับชั้นการแสดงผลในสตริงข้อยกเว้นเพื่อวิเคราะห์ ทำไมตัวจับคู่จึงไม่ตรงกับจำนวนการดูใดๆ
  • หาก onView() พบข้อมูลพร็อพเพอร์ตี้หลายรายการที่ตรงกับตัวจับคู่ที่ระบุ โยน AmbiguousViewMatcherException แล้ว ระบบจะพิมพ์ลำดับชั้นของมุมมองทั้งหมด การดูที่จับคู่จะมีป้ายกำกับ MATCHES:
java.lang.RuntimeException:
androidx.test.espresso.AmbiguousViewMatcherException
This matcher matches multiple views in the hierarchy: (withId: is <123456789>)

...

+----->SomeView{id=123456789, res-name=plus_one_standard_ann_button,
visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true,
window-focus=true, is-focused=false, is-focusable=false, enabled=true,
selected=false, is-layout-requested=false, text=,
root-is-layout-requested=false, x=0.0, y=625.0, child-count=1}
****MATCHES****
|
+------>OtherView{id=123456789, res-name=plus_one_standard_ann_button,
visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true,
window-focus=true, is-focused=false, is-focusable=true, enabled=true,
selected=false, is-layout-requested=false, text=Hello!,
root-is-layout-requested=false, x=0.0, y=0.0, child-count=1}
****MATCHES****

เมื่อจัดการกับลำดับชั้นของมุมมองที่ซับซ้อนหรือลักษณะการทำงานที่ไม่คาดคิดของวิดเจ็ต คุณควรใช้ มุมมองลำดับชั้นใน Android Studio สำหรับ คำอธิบาย

คำเตือนเกี่ยวกับมุมมองอะแดปเตอร์

Espresso เตือนผู้ใช้เกี่ยวกับวิดเจ็ต AdapterView เมื่อonView() การดำเนินการทำให้เกิดวิดเจ็ต NoMatchingViewException และ AdapterView ที่แสดงในลำดับชั้นการแสดงผล วิธีแก้ที่พบบ่อยที่สุดคือการใช้ onData() ข้อความข้อยกเว้นจะรวมคำเตือนพร้อมรายการมุมมองอะแดปเตอร์ คุณอาจใช้ข้อมูลนี้เพื่อเรียกใช้ onData() เพื่อโหลดมุมมองเป้าหมาย

แหล่งข้อมูลเพิ่มเติม

หากต้องการข้อมูลเพิ่มเติมเกี่ยวกับการใช้ Espresso ในการทดสอบ Android โปรดดู แหล่งข้อมูลต่อไปนี้

ตัวอย่าง

  • CustomMatcherSample: แสดงวิธีขยาย Espresso เพื่อให้ตรงกับคุณสมบัติคำแนะนำของออบเจ็กต์ EditText
  • RecyclerViewSample: การดำเนินการ RecyclerView รายการสำหรับ Espresso
  • (เพิ่มเติม...)