The Android Developer Challenge is back! Submit your idea before December 2.

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. Namun, Anda masih 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 (via onView() dan onData()), serta memperlihatkan API yang tidak selalu terikat ke tampilan apa 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(), misalnya seperti click().
  • ViewAssertions - Kumpulan objek ViewAssertion yang dapat diteruskan 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 lebih dikenal oleh mereka yang telah menggunakannya dengan Mockito atau JUnit. Jika tidak terbiasa dengan hamcrest matcher, kami sarankan Anda memulai dengan melihat sekilas presentasi ini.

Seringkali, tampilan yang diinginkan memiliki R.id 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 menavigasi ke kontennya untuk 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 dengan R.id-nya adalah langkah yang sederhana seperti memanggil onView():

Kotlin

    onView(withId(R.id.my_view))
    

Java

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

Terkadang, nilai R.id dibagikan di antara banyak tampilan. Saat hal ini terjadi, upaya untuk menggunakan R.id tertentu akan memberi Anda pengecualian, seperti AmbiguousViewMatcherException. Pesan pengecualian memberikan Anda representasi teks dari hierarki tampilan saat ini yang dapat dicari, dan 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 menentukannya karena tindakan tersebut akan memaksa framework untuk melakukan lebih banyak tugas 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 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 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 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 suatu 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 akan dianggap sebagai 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 menegaskan bahwa tampilan dengan teks "Hello!" dapat dilihat—misalnya setelah perubahan tanda visibilitas tampilan—maka kode tersebut akan baik-baik saja.

Pengujian sederhana pernyataan tampilan

Dalam contoh ini, SimpleActivity berisi Button dan TextView. Saat tombol 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. Tombol dalam SimpleActivity memiliki R.id 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 untuk diverifikasi memiliki R.id unik juga:

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 paling umum AdapterView adalah ListView. Berbeda dengan widget statis seperti LinearLayout, hanya sebagian 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 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 penerapan tersebut menghentikan kontrak pewarisan, khususnya getItem() API. 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 yang 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!", saat %s mewakili item yang dipilih.

Tujuan dari pengujian ini adalah untuk membuka Spinner, memilih item tertentu, dan memverifikasi bahwa TextView berisi item tersebut. Karena class Spinner didasarkan pada AdapterView, kami merekomendasikan agar menggunakan onData() daripada 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 isinya. 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. Misalnya:

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

Hierarki tampilan

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

  • Jika onView() tidak menemukan tampilan target, maka NoMatchingViewException akan dimunculkan. 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, maka AmbiguousViewMatcherException akan dimunculkan. 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, 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 NoMatchingViewException dan widget AdapterView saat ini ada di dalam tampilan hierarki, solusi yang paling umum adalah dengan 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