Dokumen ini menjelaskan cara menyelesaikan tugas pengujian otomatis yang umum menggunakan Espresso API.
Espresso API mendorong penulis pengujian untuk memikirkan apa yang mungkin dilakukan pengguna
lakukan saat berinteraksi dengan aplikasi - menemukan elemen UI dan berinteraksi
dengan mereka. Pada saat yang sama, framework mencegah akses langsung ke aktivitas
dan tampilan aplikasi karena berpegang pada objek ini dan
pada UI thread, merupakan sumber utama kegagalan pengujian. Dengan demikian, Anda akan
tidak melihat metode seperti getView()
dan getCurrentActivity()
di Espresso API.
Anda masih dapat beroperasi dengan aman pada tampilan dengan mengimplementasikan subclass Anda sendiri
ViewAction
dan ViewAssertion
.
Komponen API
Komponen utama Espresso meliputi:
- Espresso – Titik entri ke interaksi dengan tampilan (melalui
onView()
danonData()
). Juga mengekspos API yang tidak selalu terikat ke tampilan apa pun, seperti sebagaipressBack()
. - ViewMatchers – Kumpulan objek yang mengimplementasikan
Antarmuka
Matcher<? super View>
. Anda bisa meneruskan satu atau beberapa ID ini ke MetodeonView()
untuk menemukan tampilan dalam hierarki tampilan saat ini. - ViewActions – Kumpulan objek
ViewAction
yang dapat diteruskan ke metodeViewInteraction.perform()
, seperticlick()
. - ViewAssertions – Kumpulan objek
ViewAssertion
yang dapat meneruskan metodeViewInteraction.check()
. Sering kali, Anda akan menggunakan pernyataan yang cocok, yang menggunakan pencocok View untuk menegaskan status tampilan yang dipilih saat ini.
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
Dalam sebagian besar kasus, metode onView()
mengambil hamcrest matcher
yang diharapkan cocok dengan satu — dan hanya satu — tampilan dalam tampilan saat ini
hierarki sebelumnya. Pencocok sangat ampuh dan akan familier bagi mereka yang pernah menggunakan
mereka dengan Mockito atau JUnit. Jika Anda tidak terbiasa dengan hamcrest matcher, kami
sarankan agar Anda mulai dengan melihat sekilas
presentasi.
Sering kali tampilan yang diinginkan memiliki R.id
yang unik dan pencocok withId
sederhana akan
mempersempit pencarian tampilan. Namun, ada banyak kasus
yang sah ketika Anda
tidak dapat menentukan R.id
pada waktu pengembangan pengujian. Misalnya, tampilan spesifik
mungkin tidak memiliki R.id
atau R.id
tidak unik. Hal ini dapat normal
uji instrumentasi 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 dikenal dan buka kontennya untuk
tampilan tertentu.
Espresso menangani masalah ini secara rapi 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. Ketika ini terjadi,
mencoba menggunakan R.id
tertentu akan memberi Anda pengecualian, seperti
AmbiguousViewMatcherException
. Pesan pengecualian memberi Anda teks
dari hierarki tampilan saat ini, yang bisa Anda cari 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 melalui berbagai atribut tampilan, Anda mungkin menemukan secara unik
properti yang dapat diidentifikasi. Pada contoh di atas, salah satu tampilan memiliki teks
"Hello!"
. Anda dapat menggunakannya untuk mempersempit penelusuran dengan menggunakan kombinasi
pencocok:
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 lebih banyak
spesifikasi
pendukung. Jika Anda tidak dapat mempersempit penelusuran menggunakan
withText()
atauwithContentDescription()
, pertimbangkan untuk memperlakukannya sebagai bug aksesibilitas. - Gunakan pencocok paling tidak deskriptif yang menemukan satu tampilan yang Anda cari
untuk mereka. Jangan terlalu spesifik karena akan memaksa framework untuk melakukan lebih banyak tugas daripada
diperlukan. Misalnya, jika tampilan dapat dikenali secara khusus oleh teksnya, Anda
tidak perlu menentukan bahwa tampilan juga dapat ditetapkan dari
TextView
. Untuk banyak tampilanR.id
tampilan seharusnya memadai. - Jika tampilan target berada di dalam
AdapterView
—sepertiListView
,GridView
, atauSpinner
—metodeonView()
mungkin tidak berfungsi. Di kasus, Anda harus menggunakanonData()
sebagai gantinya.
Menjalankan tindakan pada tampilan
Setelah menemukan pencocok yang sesuai untuk tampilan target, Anda dapat
menjalankan instance ViewAction
di dalamnya 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 sedang Anda kerjakan terletak di dalam ScrollView
(vertikal atau
horizontal), pertimbangkan tindakan sebelumnya yang mengharuskan tampilan
ditampilkan—seperti click()
dan typeText()
—dengan scrollTo()
. Ini
memastikan bahwa tampilan 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 dipilih saat ini dengan check()
. Pernyataan yang paling sering digunakan adalah pernyataan matches()
. Proses ini menggunakan
ViewMatcher
untuk menyatakan status tampilan yang dipilih saat ini.
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!"
adalah
terlihat—misalnya setelah perubahan tanda visibilitas tampilan—
kode Anda sudah cukup.
Pengujian sederhana pernyataan tampilan
Dalam contoh ini, SimpleActivity
berisi Button
dan TextView
. Jika
diklik, konten TextView
akan berubah menjadi "Hello Espresso!"
.
Berikut cara mengujinya dengan Espresso:
Klik pada tombol
Langkah pertama adalah mencari properti yang membantu menemukan tombol. Tujuan
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
. Sebagai
bukan widget statis seperti LinearLayout
, hanya sebagian dari
Turunan AdapterView
dapat dimuat ke dalam hierarki tampilan saat ini. Cara sederhana
Penelusuran onView()
tidak akan menemukan tampilan yang saat ini tidak dimuat.
Espresso menangani hal ini dengan menyediakan titik entri onData()
terpisah yang
dapat memuat item adaptor yang dimaksud terlebih dahulu, menjadikannya fokus sebelum
yang beroperasi di dalamnya atau
anak-anaknya.
Peringatan: Penerapan kustom dari
AdapterView
dapat mengalami masalah dengan onData()
jika mereka memutus kontrak pewarisan, terutama metode
API getItem()
. Dalam kasus seperti itu, tindakan terbaik adalah
memfaktorkan ulang kode aplikasi. Jika tidak dapat melakukannya, Anda dapat mengimplementasikan
cocok dengan AdapterViewProtocol
kustom. Untuk informasi selengkapnya, ikuti
lihat defaultnya
Class AdapterViewProtocols
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 dalam kopi. Jika
item dipilih, ada TextView
yang berubah menjadi "One %s a day!"
, dengan
%s
mewakili item yang dipilih.
Tujuan pengujian ini adalah untuk membuka Spinner
, memilih item tertentu, dan
verifikasi bahwa TextView
berisi item tersebut. Karena class Spinner
didasarkan
diAdapterView
, sebaiknya gunakan onData()
dan bukan onView()
untuk
cocok dengan 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 pada tampilan
hierarki sebelumnya. Dengan menggunakan onData()
, kita memaksa elemen yang diinginkan ke tampilan
hierarki sebelumnya. 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. Misalnya:
ViewInteraction: Performing 'single click' action on view with text: Espresso
Hierarki tampilan
Espresso mencetak hierarki tampilan dalam pesan pengecualian saat onView()
gagal.
- Jika
onView()
tidak menemukan tampilan target, berartiNoMatchingViewException
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
ditampilkan. Hierarki tampilan dicetak dan semua tampilan yang cocok ditandai dengan labelMATCHES
:
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 akan selalu bermanfaat bila Anda menggunakan Hierarchy Viewer di Android Studio untuk penjelasan.
Peringatan tampilan adapter
Espresso memperingatkan pengguna tentang keberadaan widget AdapterView
. Jika 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 informasi selengkapnya tentang menggunakan Espresso dalam pengujian Android, lihat referensi berikut.
Contoh
- CustomMatcherSample:
Menunjukkan cara memperluas Espresso agar cocok dengan properti petunjuk objek
EditText
. - RecyclerViewSample:
Tindakan
RecyclerView
untuk Espresso. - (lainnya...)