Kiến thức cơ bản về Espresso

Tài liệu này giải thích cách hoàn thành các tác vụ kiểm thử tự động phổ biến bằng Espresso API.

Espresso API khuyến khích tác giả kiểm thử suy nghĩ về những việc người dùng có thể làm trong khi tương tác với ứng dụng, chẳng hạn như xác định vị trí và tương tác với các phần tử trên giao diện người dùng. Đồng thời, khung này ngăn quyền truy cập trực tiếp vào các hoạt động và khung hiển thị của ứng dụng, vì việc giữ lại các đối tượng này và thao tác trên chúng ngoài luồng giao diện người dùng là một nguyên nhân chính gây ra kết quả kiểm thử không ổn định. Do đó, bạn sẽ không thấy các phương thức như getView()getCurrentActivity() trong API Espresso. Bạn vẫn có thể vận hành một cách an toàn trên các khung hiển thị bằng cách triển khai các lớp con của ViewActionViewAssertion.

Thành phần API

Sau đây là các thành phần chính của Espresso:

  • Espresso – Điểm truy cập cho các hoạt động tương tác với khung hiển thị (thông qua onView()onData()). Đồng thời hiển thị các API không nhất thiết phải liên kết với bất kỳ khung hiển thị nào, chẳng hạn như pressBack().
  • ViewMatchers – Một tập hợp các đối tượng triển khai giao diện Matcher<? super View>. Bạn có thể truyền một hoặc nhiều nội dung này cho phương thức onView() để tìm một khung hiển thị trong hệ phân cấp khung hiển thị hiện tại.
  • ViewAction – Một tập hợp các đối tượng ViewAction có thể được truyền đến phương thức ViewInteraction.perform(), chẳng hạn như click().
  • ViewAssertions – Một tập hợp các đối tượng ViewAssertion có thể truyền phương thức ViewInteraction.check(). Trong hầu hết trường hợp, bạn sẽ sử dụng câu nhận định khớp, dùng trình so khớp Khung hiển thị để xác nhận trạng thái của khung hiển thị đang được chọn.

Ví dụ:

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()));

Tìm một chế độ xem

Trong hầu hết các trường hợp, phương thức onView() sẽ sử dụng trình so khớp hamcrest dự kiến sẽ khớp với một và chỉ một khung hiển thị trong hệ phân cấp khung hiển thị hiện tại. Trình so khớp rất hiệu quả và sẽ quen thuộc với những ai đã sử dụng chúng với Mockito hoặc JUnit. Nếu chưa hiểu rõ về trình so khớp hamcrest, bạn nên bắt đầu xem qua bản trình bày này.

Thông thường, khung hiển thị mong muốn có một R.id duy nhất và một trình so khớp withId đơn giản sẽ thu hẹp phạm vi tìm kiếm khung hiển thị. Tuy nhiên, có nhiều trường hợp hợp lệ khi bạn không thể xác định R.id tại thời điểm phát triển chương trình kiểm thử. Ví dụ: thành phần hiển thị cụ thể có thể không có R.id hoặc R.id không phải là duy nhất. Điều này có thể khiến việc ghi các kiểm thử đo lường thông thường trở nên rườm rà và phức tạp vì cách truy cập thông thường vào khung hiển thị (bằng findViewById()) không hoạt động. Do đó, có thể bạn cần phải truy cập vào các thành viên riêng tư của Hoạt động hoặc Mảnh đang giữ khung hiển thị hoặc tìm một vùng chứa có R.id đã biết rồi chuyển đến nội dung của thành phần hiển thị đó cho khung hiển thị cụ thể.

Espresso xử lý vấn đề này một cách dễ dàng bằng cách cho phép bạn thu hẹp khung hiển thị bằng cách sử dụng các đối tượng ViewMatcher hiện có hoặc đối tượng tuỳ chỉnh của riêng bạn.

Để tìm một khung hiển thị theo R.id, bạn chỉ cần gọi onView():

Kotlin

onView(withId(R.id.my_view))

Java

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

Đôi khi, các giá trị R.id được chia sẻ giữa nhiều khung hiển thị. Khi điều này xảy ra khi người dùng cố gắng sử dụng một R.id cụ thể, thì bạn sẽ nhận được một ngoại lệ, chẳng hạn như AmbiguousViewMatcherException. Thông báo ngoại lệ cung cấp cho bạn một bản trình bày văn bản của hệ phân cấp khung hiển thị hiện tại. Bạn có thể tìm và tìm những khung hiển thị khớp với R.id không phải duy nhất:

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****

Khi xem qua các thuộc tính khác nhau của thành phần hiển thị, bạn có thể thấy các thuộc tính có thể nhận dạng duy nhất. Ở ví dụ trên, một trong các thành phần hiển thị có văn bản "Hello!". Bạn có thể sử dụng trình so khớp này để thu hẹp phạm vi tìm kiếm bằng cách sử dụng trình so khớp kết hợp:

Kotlin

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

Java

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

Bạn cũng có thể chọn không đảo ngược bất kỳ trình so khớp nào:

Kotlin

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

Java

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

Hãy xem ViewMatchers để biết trình so khớp khung hiển thị do Espresso cung cấp.

Những yếu tố nên cân nhắc

  • Trong một ứng dụng hoạt động tốt, tất cả các khung hiển thị mà người dùng có thể tương tác phải chứa văn bản mô tả hoặc có nội dung mô tả. Hãy xem phần Tăng khả năng hỗ trợ tiếp cận cho ứng dụng để biết thêm thông tin chi tiết. Nếu bạn không thể thu hẹp nội dung tìm kiếm bằng withText() hoặc withContentDescription(), hãy xem xét việc này là lỗi hỗ trợ tiếp cận.
  • Sử dụng trình so khớp có tính mô tả thấp nhất để tìm được chế độ xem bạn đang tìm. Đừng chỉ định quá mức vì việc này sẽ buộc khung làm nhiều việc hơn mức cần thiết. Ví dụ: nếu một khung hiển thị có thể được nhận dạng duy nhất bằng văn bản, bạn không cần chỉ định rằng TextView cũng có thể gán khung hiển thị đó. Đối với nhiều khung hiển thị, bạn chỉ cần R.id của khung hiển thị là đủ.
  • Nếu thành phần hiển thị mục tiêu nằm trong AdapterView (chẳng hạn như ListView, GridView hoặc Spinner), thì phương thức onView() có thể không hoạt động. Trong những trường hợp này, bạn nên sử dụng onData().

Thực hiện thao tác trên khung hiển thị

Khi tìm thấy trình so khớp phù hợp với chế độ xem mục tiêu, bạn có thể thực hiện các thực thể của ViewAction trên chế độ xem đó bằng phương thức thực hiện.

Ví dụ: để nhấp vào chế độ xem:

Kotlin

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

Java

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

Bạn có thể thực hiện nhiều hành động với một lệnh gọi thực hiện:

Kotlin

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

Java

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

Nếu khung hiển thị bạn đang xử lý nằm bên trong một ScrollView (dọc hoặc ngang), hãy cân nhắc các thao tác trước đó yêu cầu khung hiển thị đó (chẳng hạn như click()typeText()) bằng scrollTo(). Điều này đảm bảo khung hiển thị sẽ hiển thị trước khi tiếp tục thực hiện hành động khác:

Kotlin

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

Java

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

Hãy xem ViewActions để biết các thao tác với thành phần hiển thị do Espresso cung cấp.

Kiểm tra nhận định của khung hiển thị

Bạn có thể áp dụng câu nhận định cho khung hiển thị đang chọn bằng phương thức check(). Câu nhận định được dùng nhiều nhất là câu nhận định matches(). Phương thức này sử dụng một đối tượng ViewMatcher để xác nhận trạng thái của khung hiển thị đang được chọn.

Ví dụ: để kiểm tra xem một thành phần hiển thị có chứa văn bản "Hello!" hay không:

Kotlin

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

Java

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

Nếu bạn muốn xác nhận rằng "Hello!" là nội dung của khung hiển thị, thì phương pháp sau đây bị coi là phương pháp không hợp lệ:

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()));

Mặt khác, nếu bạn muốn xác nhận rằng một khung hiển thị có văn bản "Hello!" sẽ hiển thị (ví dụ: sau khi thay đổi cờ hiển thị khung hiển thị), thì bạn có thể dùng mã này.

Xem bài kiểm thử đơn giản cho câu nhận định

Trong ví dụ này, SimpleActivity chứa ButtonTextView. Khi người dùng nhấp vào nút này, nội dung của TextView sẽ thay đổi thành "Hello Espresso!".

Dưới đây là cách kiểm thử điều này bằng Espresso:

Nhấp vào nút

Bước đầu tiên là tìm một thuộc tính có thể giúp tìm thấy nút này. Nút trong SimpleActivity có một R.id duy nhất như dự kiến.

Kotlin

onView(withId(R.id.button_simple))

Java

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

Bây giờ, hãy thực hiện thao tác nhấp:

Kotlin

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

Java

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

Xác minh văn bản TextView

TextView có văn bản cần xác minh cũng có một R.id duy nhất:

Kotlin

onView(withId(R.id.text_simple))

Java

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

Bây giờ, hãy xác minh văn bản nội dung:

Kotlin

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

Java

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

Kiểm tra dữ liệu tải trong khung hiển thị bộ chuyển đổi

AdapterView là một loại tiện ích đặc biệt tải dữ liệu động từ Trình chuyển đổi. Ví dụ phổ biến nhất về AdapterViewListView. Trái ngược với các tiện ích tĩnh như LinearLayout, chỉ một tập hợp con AdapterView con có thể được tải vào hệ phân cấp khung hiển thị hiện tại. Nếu bạn tìm kiếm onView() đơn giản, thì hệ thống sẽ không tìm thấy những khung hiển thị hiện chưa được tải.

Espresso xử lý vấn đề này bằng cách cung cấp một điểm truy cập onData() riêng biệt có thể tải trước mục bộ chuyển đổi được đề cập, tập trung vào mục tiêu đó trước khi thao tác trên mục đó hoặc bất kỳ mục con nào.

Cảnh báo: Các phương thức triển khai tuỳ chỉnh của AdapterView có thể gặp sự cố với phương thức onData() nếu chúng phá vỡ các hợp đồng kế thừa, đặc biệt là API getItem(). Trong những trường hợp như vậy, cách hành động tốt nhất là tái cấu trúc mã xử lý ứng dụng của bạn. Nếu không thể làm như vậy, bạn có thể triển khai AdapterViewProtocol tuỳ chỉnh phù hợp. Để biết thêm thông tin, hãy xem lớp AdapterViewProtocols mặc định do Espresso cung cấp.

Kiểm thử đơn giản thành phần hiển thị bộ chuyển đổi

Bài kiểm thử đơn giản này minh hoạ cách sử dụng onData(). SimpleActivity chứa Spinner cùng với một vài mục đại diện cho các loại đồ uống cà phê. Khi một mục được chọn, TextView sẽ thay đổi thành "One %s a day!", trong đó %s biểu thị mục đã chọn.

Mục tiêu của kiểm thử này là mở Spinner, chọn một mục cụ thể và xác minh rằng TextView chứa mục đó. Vì lớp Spinner dựa trên AdapterView, bạn nên sử dụng onData() thay vì onView() để so khớp mục.

Mở phần lựa chọn mục

Kotlin

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

Java

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

Hãy chọn một mục

Đối với phần lựa chọn mục, Spinner sẽ tạo một ListView kèm theo nội dung. Khung hiển thị này có thể rất dài và phần tử này có thể không được đóng góp vào hệ phân cấp khung hiển thị. Bằng cách sử dụng onData(), chúng ta buộc phần tử mong muốn đưa vào hệ phân cấp khung hiển thị. Các mục trong Spinner là chuỗi nên chúng ta muốn so khớp một mục bằng Chuỗi "Americano":

Kotlin

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

Java

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

Xác minh văn bản là chính xác

Kotlin

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

Java

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

Gỡ lỗi

Espresso cung cấp thông tin gỡ lỗi hữu ích khi quy trình kiểm thử không thành công:

Ghi nhật ký

Espresso ghi nhật ký tất cả các hành động xem vào logcat. Ví dụ:

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

Hệ phân cấp chế độ xem

Espresso in hệ phân cấp khung hiển thị trong thông báo ngoại lệ khi onView() không thành công.

  • Nếu onView() không tìm thấy khung hiển thị mục tiêu, thì NoMatchingViewException sẽ được gửi. Bạn có thể kiểm tra hệ phân cấp khung hiển thị trong chuỗi ngoại lệ để phân tích lý do trình so khớp không khớp với bất kỳ khung hiển thị nào.
  • Nếu onView() tìm thấy nhiều khung hiển thị khớp với trình so khớp nhất định, thì hệ thống sẽ trả về AmbiguousViewMatcherException. Hệ phân cấp khung hiển thị sẽ được in và tất cả các khung hiển thị đã khớp sẽ được đánh dấu bằng nhãn 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****

Khi xử lý một hệ phân cấp khung hiển thị phức tạp hoặc hành vi không mong muốn của các tiện ích, bạn nên sử dụng Trình xem phân cấp trong Android Studio để giải thích.

Cảnh báo về chế độ xem bộ chuyển đổi

Espresso cảnh báo người dùng về sự hiện diện của các tiện ích AdapterView. Khi thao tác onView() gửi một tiện ích NoMatchingViewExceptionAdapterView có trong hệ phân cấp khung hiển thị, giải pháp phổ biến nhất là sử dụng onData(). Thông báo ngoại lệ sẽ bao gồm một cảnh báo kèm theo danh sách các chế độ xem bộ chuyển đổi. Bạn có thể sử dụng thông tin này để gọi onData() nhằm tải khung hiển thị mục tiêu.

Tài nguyên khác

Để biết thêm thông tin về cách sử dụng Espresso trong kiểm thử Android, hãy tham khảo các tài nguyên sau.

Mẫu