Dasar-dasar Espresso

Dokumen ini menjelaskan cara menyelesaikan tugas pengujian otomatis umum menggunakan Espresso API.

Espresso API mendorong penulis pengujian untuk memikirkan apa yang mungkin dilakukan pengguna saat berinteraksi dengan aplikasi, yaitu menemukan elemen UI dan berinteraksi dengannya. Pada saat yang sama, framework mencegah akses langsung ke aktivitas dan tampilan aplikasi karena menyimpan objek tersebut dan mengoperasikannya dari UI thread merupakan sumber utama kegagalan pengujian. Dengan demikian, Anda tidak akan melihat metode seperti getView() dan getCurrentActivity() di Espresso API. Anda masih dapat beroperasi pada tampilan dengan aman dengan mengimplementasikan subclass ViewAction dan ViewAssertion Anda sendiri.

Komponen API

Komponen utama Espresso meliputi:

  • Espresso – Titik entri ke interaksi dengan tampilan (melalui onView() dan onData()). Selain itu, mengekspos API yang tidak selalu terikat ke tampilan mana pun, seperti pressBack().
  • ViewMatchers – Kumpulan objek yang mengimplementasikan antarmuka Matcher<? super View>. Anda dapat meneruskan satu atau beberapa objek tersebut ke metode onView() untuk menemukan tampilan dalam hierarki tampilan saat ini.
  • ViewActions – Kumpulan objek ViewAction yang dapat diteruskan ke metode ViewInteraction.perform(), seperti click().
  • ViewAssertions - Kumpulan objek ViewAssertion yang dapat diteruskan ke metode ViewInteraction.check(). Biasanya, Anda akan menggunakan pernyataan yang cocok, yang menggunakan pencocok View untuk menegaskan status tampilan yang saat ini dipilih.

Contoh:

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

Menemukan tampilan

Pada sebagian besar kasus, metode onView() mengambil hamcrest matcher yang diharapkan cocok dengan satu — dan hanya satu — tampilan dalam hierarki tampilan saat ini. Pencocok sangat andal dan akan familier bagi mereka yang telah menggunakannya dengan Mockito atau JUnit. Jika tidak terbiasa dengan hamcrest matcher, sebaiknya Anda mulai dengan melihat sekilas presentasi ini.

Sering kali tampilan yang diinginkan memiliki R.id unik dan pencocok withId sederhana akan mempersempit penelusuran tampilan. Namun, ada banyak kasus saat Anda tidak dapat menentukan R.id pada waktu pengembangan pengujian. Misalnya, tampilan spesifik mungkin tidak memiliki R.id atau R.id-nya tidak unik. Hal ini dapat mengakibatkan uji instrumentasi normal menjadi rapuh dan rumit untuk ditulis karena cara normal untuk mengakses tampilan—dengan findViewById()—tidak berfungsi. Dengan demikian, Anda mungkin perlu mengakses anggota pribadi Aktivitas atau Fragmen yang menyimpan tampilan, atau menemukan penampung dengan R.id yang diketahui dan membuka kontennya untuk tampilan tertentu.

Espresso menangani masalah ini dengan mudah dengan memungkinkan Anda mempersempit tampilan menggunakan objek ViewMatcher yang sudah ada atau objek kustom Anda sendiri.

Menemukan tampilan berdasarkan R.id-nya semudah memanggil onView():

Kotlin

onView(withId(R.id.my_view))

Java

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

Terkadang, nilai R.id digunakan bersama untuk beberapa tampilan. Jika hal ini terjadi, upaya untuk menggunakan R.id tertentu akan memberi Anda pengecualian, seperti AmbiguousViewMatcherException. Pesan pengecualian memberi Anda representasi teks dari hierarki tampilan saat ini, yang dapat Anda telusuri dan temukan tampilan yang cocok dengan R.id non-unik:

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

Melihat berbagai atribut tampilan, Anda mungkin menemukan properti yang dapat diidentifikasi secara unik. Pada contoh di atas, salah satu tampilan memiliki teks "Hello!". Anda dapat menggunakan ini untuk mempersempit penelusuran menggunakan pencocok kombinasi:

Kotlin

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

Java

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

Anda juga dapat memilih untuk tidak membalikkan matcher apa pun:

Kotlin

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

Java

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

Lihat ViewMatchers untuk matcher tampilan yang disediakan oleh Espresso.

Pertimbangan

  • Dalam aplikasi yang berperilaku baik, semua tampilan yang dapat berinteraksi dengan pengguna harus berisi teks deskriptif atau memiliki deskripsi konten. Lihat Membuat aplikasi lebih mudah diakses untuk detail selengkapnya. Jika Anda tidak dapat mempersempit penelusuran menggunakan withText() atau withContentDescription(), pertimbangkan untuk memperlakukannya sebagai bug aksesibilitas.
  • Gunakan matcher paling tidak deskriptif yang menemukan satu tampilan yang Anda cari. Jangan terlalu menentukannya karena hal ini akan memaksa framework untuk melakukan lebih banyak pekerjaan daripada yang diperlukan. Misalnya, jika tampilan secara unik dapat diidentifikasi oleh teksnya, Anda tidak perlu menentukan bahwa tampilan juga dapat ditetapkan dari TextView. Untuk banyak tampilan, R.id tampilan seharusnya cukup.
  • Jika tampilan target ada di dalam AdapterView—seperti ListView, GridView, atau Spinner—metode onView() mungkin tidak berfungsi. Dalam kasus ini, Anda harus menggunakan onData() sebagai gantinya.

Menjalankan tindakan pada tampilan

Setelah menemukan matcher yang sesuai untuk tampilan target, Anda dapat menjalankan instance ViewAction pada tampilan tersebut menggunakan metode perform.

Misalnya, untuk mengklik tampilan:

Kotlin

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

Java

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

Anda dapat menjalankan lebih dari satu tindakan dengan satu panggilan perform:

Kotlin

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

Java

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

Jika tampilan yang Anda kerjakan terletak di dalam ScrollView (vertikal atau horizontal), pertimbangkan tindakan sebelumnya yang mengharuskan tampilan ditampilkan—seperti click() dan typeText()—dengan scrollTo(). Hal ini memastikan bahwa tampilan akan ditampilkan sebelum melanjutkan ke tindakan lain:

Kotlin

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

Java

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

Lihat ViewActions untuk tindakan tampilan yang disediakan oleh Espresso.

Memeriksa pernyataan tampilan

Pernyataan dapat diterapkan ke tampilan yang saat ini dipilih dengan metode check(). Pernyataan yang paling sering digunakan adalah pernyataan matches(). Class ini menggunakan objek ViewMatcher untuk menyatakan status tampilan yang saat ini dipilih.

Misalnya, untuk memeriksa apakah tampilan memiliki teks "Hello!":

Kotlin

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

Java

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

Jika Anda ingin menyatakan bahwa "Hello!" adalah konten tampilan, kode berikut ini dianggap praktik yang tidak baik:

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

Di sisi lain, jika Anda ingin menyatakan bahwa tampilan dengan teks "Hello!" terlihat—misalnya setelah perubahan tanda visibilitas tampilan— kode tersebut akan baik-baik saja.

Pengujian sederhana pernyataan tampilan

Dalam contoh ini, SimpleActivity berisi Button dan TextView. Saat tombol diklik, konten TextView berubah menjadi "Hello Espresso!".

Berikut cara mengujinya dengan Espresso:

Klik pada tombol

Langkah pertama adalah mencari properti yang membantu menemukan tombol. Tombol di SimpleActivity memiliki R.id yang unik, seperti yang diharapkan.

Kotlin

onView(withId(R.id.button_simple))

Java

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

Sekarang untuk menjalankan klik:

Kotlin

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

Java

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

Verifikasi teks TextView

TextView dengan teks yang akan diverifikasi juga memiliki R.id unik:

Kotlin

onView(withId(R.id.text_simple))

Java

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

Sekarang, untuk memverifikasi teks konten:

Kotlin

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

Java

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

Memeriksa pemuatan data dalam tampilan adapter

AdapterView adalah jenis widget khusus yang memuat datanya secara dinamis dari Adaptor. Contoh AdapterView yang paling umum adalah ListView. Berbeda dengan widget statis seperti LinearLayout, hanya subset turunan AdapterView yang dapat dimuat ke dalam hierarki tampilan saat ini. Penelusuran onView() sederhana tidak akan menemukan tampilan yang saat ini tidak dimuat.

Espresso menangani hal ini dengan menyediakan titik entri onData() terpisah yang dapat terlebih dahulu memuat item adaptor yang dimaksud, sehingga menjadikannya fokus sebelum mengoperasikannya atau turunannya.

Peringatan: Implementasi kustom AdapterView dapat menimbulkan masalah dengan metode onData() jika implementasi tersebut menghentikan kontrak pewarisan, khususnya getItem() API. Dalam kasus tersebut, tindakan terbaiknya adalah memfaktorkan ulang kode aplikasi Anda. Jika tidak dapat melakukannya, Anda dapat mengimplementasikan AdapterViewProtocol kustom yang cocok. Untuk mengetahui informasi selengkapnya, lihat class AdapterViewProtocols default yang disediakan oleh Espresso.

Pengujian sederhana tampilan adapter

Pengujian sederhana ini menunjukkan cara menggunakan onData(). SimpleActivity berisi Spinner dengan beberapa item yang mewakili jenis minuman kopi. Saat item dipilih, akan ada TextView yang berubah menjadi "One %s a day!", dengan %s mewakili item yang dipilih.

Sasaran pengujian ini adalah untuk membuka Spinner, memilih item tertentu, dan memverifikasi bahwa TextView berisi item tersebut. Karena class Spinner didasarkan pada AdapterView, sebaiknya gunakan onData(), bukan onView(), untuk mencocokkan item.

Membuka pemilihan item

Kotlin

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

Java

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

Memilih item

Untuk pemilihan item, Spinner membuat ListView bersama kontennya. Tampilan ini bisa sangat panjang, dan elemen mungkin tidak dikontribusikan ke hierarki tampilan. Dengan menggunakan onData(), kita memaksa elemen yang diinginkan ke dalam hierarki tampilan. Item dalam Spinner adalah string, jadi kita ingin mencocokkan item yang sama dengan String "Americano":

Kotlin

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

Java

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

Memverifikasi bahwa teks benar

Kotlin

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

Java

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

Proses Debug

Espresso memberikan informasi debug yang berguna saat pengujian gagal:

Logging

Espresso mencatat semua tindakan tampilan ke logcat. Contoh:

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

Hierarki tampilan

Espresso menampilkan hierarki tampilan dalam pesan pengecualian jika onView() gagal.

  • Jika onView() tidak menemukan tampilan target, NoMatchingViewException akan ditampilkan. Anda dapat memeriksa hierarki tampilan di string pengecualian untuk menganalisis mengapa matcher tidak cocok dengan tampilan mana pun.
  • Jika onView() menemukan beberapa tampilan yang cocok dengan matcher yang diberikan, AmbiguousViewMatcherException akan ditampilkan. Hierarki tampilan ditampilkan dan semua tampilan yang cocok akan ditandai dengan label 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****

Saat menangani hierarki tampilan yang rumit atau perilaku widget yang tidak terduga, sebaiknya gunakan Hierarchy Viewer di Android Studio untuk mendapatkan penjelasan.

Peringatan tampilan adapter

Espresso memperingatkan pengguna tentang keberadaan widget AdapterView. Saat operasi onView() menampilkan widget NoMatchingViewException dan AdapterView ada dalam hierarki tampilan, solusi yang paling umum adalah menggunakan onData(). Pesan pengecualian akan menyertakan peringatan dengan daftar tampilan adapter. Anda dapat menggunakan informasi ini saat memanggil onData() untuk memuat tampilan target.

Referensi lainnya

Untuk mengetahui informasi selengkapnya tentang penggunaan Espresso dalam pengujian Android, lihat referensi berikut.

Contoh