Dasar-dasar Espresso

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

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

Komponen API

Komponen utama Espresso meliputi:

  • Espresso — Titik masuk ke interaksi dengan tampilan (melalui onView() dan onData() ). Selain itu, menampilkan 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 — Koleksi objek ViewAction yang dapat diteruskan ke metode ViewInteraction.perform(), seperti click().
  • ViewAssertions — Koleksi objek ViewAssertion yang dapat diteruskan ke metode ViewInteraction.check(). Sering kali, Anda akan menggunakan pernyataan kecocokan, yang menggunakan View matcher 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 satu-satunya — tampilan dalam hierarki tampilan saat ini. Matcher sangatlah andal dan akan sudah dikenal oleh mereka yang telah menggunakannya dengan Mockito atau JUnit. Jika belum familier dengan hamcrest matcher, kami sarankan Anda memulai dengan melihat sekilas presentasi ini.

Sering kali tampilan yang diinginkan memiliki R.id yang unik dan matcher withId sederhana akan mempersempit penelusuran tampilan. Namun, ada banyak kasus saat Anda tidak dapat menentukan R.id pada saat pengembangan pengujian. Misalnya, tampilan spesifik mungkin tidak memiliki R.id atau R.id-nya tidak unik. Hal ini dapat mengakibatkan pengujian instrumentasi normal menjadi rawan perubahan 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 container dengan R.id yang diketahui dan membuka kontennya untuk mendapatkan tampilan tertentu.

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

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 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 untuk menemukan 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 properti yang dapat diidentifikasi secara unik. Pada contoh di atas, salah satu tampilan memiliki teks "Hello!". Anda dapat menggunakan ini untuk mempersempit penelusuran dengan menggunakan matcher 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 spesifik karena itu akan memaksa framework untuk melakukan lebih banyak tugas daripada yang diperlukan. Misalnya, jika tampilan secara unik dapat diidentifikasi oleh teksnya, Anda tidak perlu mensyaratkan bahwa tampilan juga harus dapat ditetapkan dari TextView. Untuk banyak tampilan, R.id tampilan seharusnya cukup memadai.
  • Jika tampilan target ada di dalam AdapterView—seperti ListView, GridView, atau Spinner—metode onView() mungkin tidak akan 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 di atasnya 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 untuk ditampilkan—seperti click() dan typeText()—dengan scrollTo(). Langkah ini memastikan bahwa tampilan akan disajikan 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 metode check(). Pernyataan yang paling sering digunakan adalah pernyataan matches(). Pernyataan ini menggunakan objek ViewMatcher untuk menegaskan 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!" dapat dilihat—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 Adapter. Contoh AdapterView yang paling umum adalah ListView. Berbeda dengan widget statis seperti LinearLayout, hanya sebagian turunan dari AdapterView yang dapat dimuat ke dalam hierarki tampilan saat ini. Penelusuran onView() sederhana tidak akan menemukan tampilan yang saat ini tidak dimuat.

Espresso menangani kendala ini dengan memberikan titik masuk onData() terpisah yang mampu terlebih dahulu memuat item adapter yang diminta, menjadikannya fokus sebelum memulai operasi baik padanya atau turunannya.

Peringatan: Implementasi kustom AdapterView dapat bermasalah dengan metode onData() jika implementasi tersebut menghentikan kontrak pewarisan, khususnya API getItem(). Dalam kasus seperti itu, tindakan terbaiknya adalah dengan memperbaiki kode aplikasi Anda. Jika tidak dapat melakukannya, Anda dapat mengimplementasikan AdapterViewProtocol kustom yang cocok. Untuk 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 komponen lain seperti dalam Espresso API. Saat item dipilih, akan ada TextView yang berubah menjadi "One %s a day!", yang mana %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() kami 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. Misalnya:

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

Hierarki tampilan

Espresso mencetak hierarki tampilan dalam pesan pengecualian ketika onView() gagal.

  • Jika onView() tidak menemukan tampilan target, NoMatchingViewException akan dimunculkan. Anda dapat memeriksa hierarki tampilan di string pengecualian untuk menganalisis alasan matcher tidak cocok dengan tampilan mana pun.
  • Jika onView() menemukan beberapa tampilan yang cocok dengan matcher yang diberikan, AmbiguousViewMatcherException akan dimunculkan. Hierarki tampilan disajikan 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, akan lebih bermanfaat untuk menggunakan Hierarchy Viewer di Android Studio untuk mendapatkan penjelasan.

Peringatan tampilan adapter

Espresso memperingatkan pengguna tentang keberadaan widget AdapterView. Saat operasi onView() memunculkan widget NoMatchingViewException dan AdapterView saat ini 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