Menguji UI untuk satu aplikasi

Menguji interaksi pengguna dalam satu aplikasi membantu memastikan bahwa pengguna tidak mengalami hasil yang tidak terduga atau memiliki pengalaman buruk saat berinteraksi dengan aplikasi Anda. Anda harus terbiasa membuat pengujian antarmuka pengguna (UI) jika Anda perlu memverifikasi bahwa UI aplikasi berfungsi dengan benar.

Framework pengujian Espresso, yang disediakan oleh AndroidX Test, menyediakan API untuk menulis pengujian UI guna mensimulasikan interaksi pengguna dalam satu aplikasi target. Pengujian Espresso dapat dijalankan pada perangkat yang menjalankan Android 2.3.3 (API level 10) dan yang lebih tinggi. Manfaat utama menggunakan Espresso adalah framework pengujian ini menyediakan sinkronisasi tindakan pengujian secara otomatis dengan UI aplikasi yang sedang Anda uji. Espresso mendeteksi kapan thread utama tidak digunakan, sehingga dapat menjalankan perintah pengujian Anda pada waktu yang tepat, yang akan meningkatkan keandalan pengujian. Kemampuan ini juga membebaskan Anda dari keharusan untuk menambah solusi pengaturan waktu, seperti Thread.sleep() dalam kode pengujian Anda.

Framework pengujian Espresso adalah API berbasis instrumentasi dan bekerja dengan runner pengujian AndroidJUnitRunner.

Menyiapkan Espresso

Sebelum membuat pengujian UI Anda dengan Espresso, pastikan untuk menetapkan referensi dependensi ke library Espresso:

    dependencies {
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
    }
    

Nonaktifkan animasi pada perangkat pengujian Anda. Membiarkan animasi sistem tetap aktif pada perangkat pengujian dapat menyebabkan hasil yang tidak terduga atau dapat menyebabkan pengujian Anda gagal. Nonaktifkan animasi dari Setelan dengan membuka Opsi developer dan nonaktifkan semua opsi berikut:

  • Skala animasi jendela
  • Skala animasi transisi
  • Skala durasi animator

Jika Anda ingin menyiapkan project untuk menggunakan fitur Espresso selain dari fitur yang disediakan core API, lihat panduan khusus untuk Espresso.

Membuat class pengujian Espresso

Untuk membuat pengujian Espresso, ikuti model pemrograman ini:

  1. Temukan komponen UI yang ingin Anda uji di Activity (misalnya, tombol login di aplikasi) dengan memanggil metode onView(), atau metode onData() untuk kontrol AdapterView.
  2. Simulasikan interaksi pengguna khusus untuk dijalankan pada komponen UI tersebut dengan memanggil metode ViewInteraction.perform() atau DataInteraction.perform() dan meneruskan tindakan pengguna (misalnya, mengklik tombol login). Untuk mengurutkan beberapa tindakan pada komponen UI yang sama, gabungkan tindakan tersebut dengan menggunakan daftar yang dipisahkan koma dalam argumen metode Anda.
  3. Bila perlu ulangi langkah-langkah di atas, untuk mensimulasikan alur penggunaan di beberapa aktivitas di aplikasi target.
  4. Gunakan metode ViewAssertions untuk memeriksa apakah UI mencerminkan keadaan atau perilaku yang diharapkan setelah interaksi pengguna ini dilakukan.

Langkah-langkah ini dibahas lebih mendalam di bagian di bawah ini.

Cuplikan kode berikut ini menunjukkan bagaimana class pengujian Anda dapat memanggil alur kerja dasar ini:

Kotlin

    onView(withId(R.id.my_view))            // withId(R.id.my_view) is a ViewMatcher
            .perform(click())               // click() is a ViewAction
            .check(matches(isDisplayed()))  // matches(isDisplayed()) is a ViewAssertion
    

Java

    onView(withId(R.id.my_view))            // withId(R.id.my_view) is a ViewMatcher
            .perform(click())               // click() is a ViewAction
            .check(matches(isDisplayed())); // matches(isDisplayed()) is a ViewAssertion
    

Menggunakan Espresso dengan ActivityTestRule

Bagian berikut menjelaskan cara membuat pengujian Espresso baru dalam gaya JUnit 4 dan menggunakan ActivityTestRule untuk mengurangi jumlah kode boilerplate yang perlu Anda tulis. Dengan menggunakan ActivityTestRule, framework pengujian meluncurkan aktivitas yang diuji sebelum setiap metode pengujian dianotasikan dengan @Test dan sebelum metode apa pun yang dianotasikan dengan @Before. Framework ini menangani penonaktifan aktivitas setelah pengujian selesai dan semua metode yang dijelaskan dengan @After dijalankan.

Kotlin

    package com.example.android.testing.espresso.BasicSample

    import org.junit.Before
    import org.junit.Rule
    import org.junit.Test
    import org.junit.runner.RunWith

    import androidx.test.rule.ActivityTestRule
    import androidx.test.runner.AndroidJUnit4

    @RunWith(AndroidJUnit4::class)
    @LargeTest
    class ChangeTextBehaviorTest {

        private lateinit var stringToBetyped: String

        @get:Rule
        var activityRule: ActivityTestRule<MainActivity>
                = ActivityTestRule(MainActivity::class.java)

        @Before
        fun initValidString() {
            // Specify a valid string.
            stringToBetyped = "Espresso"
        }

        @Test
        fun changeText_sameActivity() {
            // Type text and then press the button.
            onView(withId(R.id.editTextUserInput))
                    .perform(typeText(stringToBetyped), closeSoftKeyboard())
            onView(withId(R.id.changeTextBt)).perform(click())

            // Check that the text was changed.
            onView(withId(R.id.textToBeChanged))
                    .check(matches(withText(stringToBetyped)))
        }
    }
    

Java

    package com.example.android.testing.espresso.BasicSample;

    import org.junit.Before;
    import org.junit.Rule;
    import org.junit.Test;
    import org.junit.runner.RunWith;

    import androidx.test.rule.ActivityTestRule;
    import androidx.test.runner.AndroidJUnit4;

    @RunWith(AndroidJUnit4.class)
    @LargeTest
    public class ChangeTextBehaviorTest {

        private String stringToBetyped;

        @Rule
        public ActivityTestRule<MainActivity> activityRule
                = new ActivityTestRule<>(MainActivity.class);

        @Before
        public void initValidString() {
            // Specify a valid string.
            stringToBetyped = "Espresso";
        }

        @Test
        public void changeText_sameActivity() {
            // Type text and then press the button.
            onView(withId(R.id.editTextUserInput))
                    .perform(typeText(stringToBetyped), closeSoftKeyboard());
            onView(withId(R.id.changeTextBt)).perform(click());

            // Check that the text was changed.
            onView(withId(R.id.textToBeChanged))
                    .check(matches(withText(stringToBetyped)));
        }
    }
    

Mengakses komponen UI

Sebelum Espresso dapat berinteraksi dengan aplikasi yang diuji, Anda harus terlebih dahulu menentukan komponen atau tampilan UI. Espresso mendukung penggunaan Hamcrest matcher untuk menentukan tampilan dan adaptor di aplikasi Anda.

Untuk menemukan tampilan, panggil metode onView() dan teruskan matcher tampilan yang menentukan tampilan yang Anda targetkan. Tindakan ini dijelaskan lebih mendalam di Menentukan matcher tampilan. Metode onView() menampilkan objek ViewInteraction yang memungkinkan pengujian Anda untuk berinteraksi dengan tampilan. Namun, memanggil metode onView() mungkin tidak berfungsi jika Anda ingin mencari tampilan dalam tata letak RecyclerView. Dalam hal ini, ikuti instruksi di Menemukan lokasi di AdapterView sebagai gantinya.

Catatan: Metode onView() tidak memeriksa apakah tampilan yang Anda tentukan valid. Sebagai gantinya, Espresso hanya mencari hierarki tampilan saat ini menggunakan pencocokan yang disediakan. Jika tidak ada kecocokan yang ditemukan, metode akan menampilkan NoMatchingViewException.

Cuplikan kode berikut ini menunjukkan bagaimana Anda dapat menulis pengujian yang mengakses kolom EditText, memasukkan string teks, menutup keyboard virtual, lalu mengklik tombol.

Kotlin

    fun testChangeText_sameActivity() {
        // Type text and then press the button.
        onView(withId(R.id.editTextUserInput))
                .perform(typeText(STRING_TO_BE_TYPED), closeSoftKeyboard())
        onView(withId(R.id.changeTextButton)).perform(click())

        // Check that the text was changed.
        ...
    }
    

Java

    public void testChangeText_sameActivity() {
        // Type text and then press the button.
        onView(withId(R.id.editTextUserInput))
                .perform(typeText(STRING_TO_BE_TYPED), closeSoftKeyboard());
        onView(withId(R.id.changeTextButton)).perform(click());

        // Check that the text was changed.
        ...
    }
    

Menentukan matcher tampilan

Anda dapat menentukan matcher tampilan dengan menggunakan pendekatan ini:

  • Memanggil metode di class ViewMatchers. Misalnya, untuk menemukan tampilan dengan mencari string teks yang ditampilkan oleh tampilan, Anda dapat memanggil metode seperti ini:

    Kotlin

        onView(withText("Sign-in"))
        

    Java

        onView(withText("Sign-in"));
        

    Demikian pula, Anda dapat memanggil withId() dan memberikan ID resource (R.id) tampilan, seperti yang ditunjukkan dalam contoh berikut:

    Kotlin

        onView(withId(R.id.button_signin))
        

    Java

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

    Tidak ada jaminan bahwa ID resource Android akan bersifat unik. Jika pengujian Anda mencoba mencocokkan dengan ID resource yang digunakan oleh lebih dari satu tampilan, Espresso akan menampilkan AmbiguousViewMatcherException.

  • Menggunakan class Hamcrest Matchers. Anda dapat menggunakan metode allOf() untuk menggabungkan beberapa matcher, seperti containsString() dan instanceOf(). Pendekatan ini memungkinkan Anda untuk memfilter hasil kecocokan dengan lebih sempit, seperti yang ditunjukkan pada contoh berikut:

    Kotlin

        onView(allOf(withId(R.id.button_signin), withText("Sign-in")))
        

    Java

        onView(allOf(withId(R.id.button_signin), withText("Sign-in")));
        

    Anda dapat menggunakan kata kunci not untuk memfilter tampilan yang tidak sesuai dengan matcher, seperti yang ditunjukkan dalam contoh berikut:

    Kotlin

        onView(allOf(withId(R.id.button_signin), not(withText("Sign-out"))))
        

    Java

        onView(allOf(withId(R.id.button_signin), not(withText("Sign-out"))));
        

    Untuk menggunakan metode ini dalam pengujian Anda, impor paket org.hamcrest.Matchers. Untuk mempelajari pencocokan Hamcrest lebih lanjut, lihat situs Hamcrest.

Untuk meningkatkan performa pengujian Espresso Anda, tentukan informasi pencocokan minimum yang diperlukan untuk menemukan tampilan target Anda. Misalnya, jika tampilan secara unik dapat diidentifikasi oleh teks deskriptif, Anda tidak perlu menentukan bahwa tampilan juga dapat ditetapkan dari instance TextView.

Menemukan tampilan di AdapterView

Dalam widget AdapterView, tampilan secara dinamis diisi dengan tampilan turunan saat waktu proses. Jika tampilan target yang ingin Anda uji tersedia di dalam AdapterView (seperti ListView, GridView, atau Spinner), metode onView() mungkin tidak berfungsi karena hanya sebagian dari tampilan yang dapat dimuat dalam hierarki tampilan saat ini.

Sebagai gantinya, panggil metode onData() untuk mendapatkan objek DataInteraction guna mengakses elemen tampilan target. Espresso mengelola pemuatan elemen tampilan target ke dalam hierarki tampilan saat ini. Espresso juga mengelola scroll ke elemen target dan memfokuskan elemen.

Catatan: Metode onData() tidak memeriksa apakah item yang Anda tentukan sesuai dengan tampilan. Espresso hanya menelusuri hierarki tampilan saat ini. Jika tidak ada kecocokan yang ditemukan, metode akan menampilkan NoMatchingViewException.

Cuplikan kode berikut menunjukkan bagaimana Anda dapat menggunakan metode onData() bersama dengan pencocokan Hamcrest untuk mencari baris tertentu dalam daftar yang berisi string yang diberikan. Dalam contoh ini, class LongListActivity berisi daftar string yang ditampilkan melalui SimpleAdapter.

Kotlin

    onData(allOf(`is`(instanceOf(Map::class.java)),
            hasEntry(equalTo(LongListActivity.ROW_TEXT),
            `is`("test input"))))
    

Java

    onData(allOf(is(instanceOf(Map.class)),
            hasEntry(equalTo(LongListActivity.ROW_TEXT), is("test input"))));
    

Melakukan tindakan

Panggil metode ViewInteraction.perform() atau DataInteraction.perform() untuk mensimulasikan interaksi pengguna pada komponen UI. Anda harus meneruskan satu atau beberapa objek ViewAction sebagai argumen. Espresso memicu setiap tindakan secara berurutan sesuai dengan urutan yang diberikan, dan menjalankan tindakan tersebut di thread utama.

Class ViewActions menyediakan daftar metode pembantu untuk menentukan tindakan umum. Anda dapat menggunakan metode ini sebagai pintasan yang mudah daripada membuat dan mengonfigurasi objek ViewAction individual. Anda dapat menentukan tindakan seperti:

Jika tampilan target berada di dalam ScrollView, lakukan tindakan ViewActions.scrollTo() terlebih dahulu untuk menampilkan tampilan di layar sebelum melanjutkan dengan tindakan lainnya. Tindakan ViewActions.scrollTo() tidak akan berpengaruh jika tampilan sudah ditampilkan.

Menguji aktivitas Anda secara terpisah dengan Intent Espresso

Intent Espresso memungkinkan validasi dan penghentian intent yang dikirim oleh aplikasi. Dengan Intent Espresso, Anda dapat menguji aplikasi, aktivitas, atau layanan secara terpisah dengan mencegah intent keluar, menghentikan hasilnya, dan mengirimkan hasil kembali ke komponen yang diuji.

Untuk memulai pengujian dengan Intent Espresso, Anda perlu menambahkan baris berikut ke file build.gradle aplikasi Anda:

    dependencies {
      androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.0'
    }
    

Untuk menguji intent, Anda perlu membuat instance class IntentsTestRule, yang sangat mirip dengan class ActivityTestRule. Class IntentsTestRule menginisialisasi Intent Espresso sebelum setiap pengujian, menghentikan aktivitas host, dan melepaskan Intent Espresso setelah setiap pengujian.

Class pengujian yang ditunjukkan dalam cuplikan kode berikut menyediakan pengujian sederhana untuk intent eksplisit. Pengujian ini menguji aktivitas dan intent yang dibuat dalam tutorial Membuat Aplikasi Pertama Anda.

Kotlin

    private const val MESSAGE = "This is a test"
    private const val PACKAGE_NAME = "com.example.myfirstapp"

    @RunWith(AndroidJUnit4::class)
    class SimpleIntentTest {

        /* Instantiate an IntentsTestRule object. */
        @get:Rule
        var intentsRule: IntentsTestRule<MainActivity> = IntentsTestRule(MainActivity::class.java)

        @Test
        fun verifyMessageSentToMessageActivity() {

            // Types a message into a EditText element.
            onView(withId(R.id.edit_message))
                    .perform(typeText(MESSAGE), closeSoftKeyboard())

            // Clicks a button to send the message to another
            // activity through an explicit intent.
            onView(withId(R.id.send_message)).perform(click())

            // Verifies that the DisplayMessageActivity received an intent
            // with the correct package name and message.
            intended(allOf(
                    hasComponent(hasShortClassName(".DisplayMessageActivity")),
                    toPackage(PACKAGE_NAME),
                    hasExtra(MainActivity.EXTRA_MESSAGE, MESSAGE)))

        }
    }
    

Java

    @Large
    @RunWith(AndroidJUnit4.class)
    public class SimpleIntentTest {

        private static final String MESSAGE = "This is a test";
        private static final String PACKAGE_NAME = "com.example.myfirstapp";

        /* Instantiate an IntentsTestRule object. */
        @Rule
        public IntentsTestRule<MainActivity> intentsRule =
                new IntentsTestRule<>(MainActivity.class);

        @Test
        public void verifyMessageSentToMessageActivity() {

            // Types a message into a EditText element.
            onView(withId(R.id.edit_message))
                    .perform(typeText(MESSAGE), closeSoftKeyboard());

            // Clicks a button to send the message to another
            // activity through an explicit intent.
            onView(withId(R.id.send_message)).perform(click());

            // Verifies that the DisplayMessageActivity received an intent
            // with the correct package name and message.
            intended(allOf(
                    hasComponent(hasShortClassName(".DisplayMessageActivity")),
                    toPackage(PACKAGE_NAME),
                    hasExtra(MainActivity.EXTRA_MESSAGE, MESSAGE)));

        }
    }
    

Untuk mengetahui informasi selengkapnya tentang Intent Espresso, lihat Dokumentasi Intent Espresso di situs AndroidX Test. Anda juga dapat mendownload contoh kode IntentsBasicSample dan IntentsAdvancedSample.

Menguji WebViews dengan Espresso Web

Espresso Web memungkinkan Anda menguji komponen WebView yang berada dalam sebuah aktivitas. Espresso Web menggunakan WebDriver API untuk memeriksa dan mengontrol perilaku WebView.

Untuk memulai pengujian dengan Espresso Web, Anda perlu menambahkan baris berikut ke file build.gradle aplikasi:

    dependencies {
      androidTestImplementation 'androidx.test.espresso:espresso-web:3.1.0'
    }
    

Jika membuat pengujian menggunakan Espresso Web, Anda harus mengaktifkan JavaScript di WebView saat Anda membuat instance objek ActivityTestRule untuk menguji aktivitas. Pada saat pengujian, Anda dapat memilih elemen HTML yang ditampilkan di WebView dan mensimulasikan interaksi pengguna, seperti memasukkan teks ke dalam kotak teks lalu mengklik tombol. Setelah tindakan selesai, Anda kemudian dapat memverifikasi bahwa hasil pada halaman Web cocok dengan hasil yang Anda harapkan.

Dalam cuplikan kode berikut, class menguji komponen WebView dengan nilai id 'webview' dalam aktivitas yang sedang diuji. Pengujian typeTextInInput_clickButton_SubmitsForm() memilih elemen <input> pada halaman Web, memasukkan beberapa teks, dan memeriksa teks yang muncul di elemen lain.

Kotlin

    private const val MACCHIATO = "Macchiato"
    private const val DOPPIO = "Doppio"

    @LargeTest
    @RunWith(AndroidJUnit4::class)
    class WebViewActivityTest {

        @get:Rule
        val activityRule = object : ActivityTestRule<WebViewActivity>(
                WebViewActivity::class.java,
                false,      /* Initial touch mode */
                false       /* launch activity */
        ) {
            override fun afterActivityLaunched() {
                // Enable JavaScript.
                onWebView().forceJavascriptEnabled()
            }
        }

        @Test
        fun typeTextInInput_clickButton_SubmitsForm() {
            // Lazily launch the Activity with a custom start Intent per test
            activityRule.launchActivity(withWebFormIntent())

            // Selects the WebView in your layout.
            // If you have multiple WebViews you can also use a
            // matcher to select a given WebView, onWebView(withId(R.id.web_view)).
            onWebView()
                    // Find the input element by ID
                    .withElement(findElement(Locator.ID, "text_input"))
                    // Clear previous input
                    .perform(clearElement())
                    // Enter text into the input element
                    .perform(DriverAtoms.webKeys(MACCHIATO))
                    // Find the submit button
                    .withElement(findElement(Locator.ID, "submitBtn"))
                    // Simulate a click via JavaScript
                    .perform(webClick())
                    // Find the response element by ID
                    .withElement(findElement(Locator.ID, "response"))
                    // Verify that the response page contains the entered text
                    .check(webMatches(getText(), containsString(MACCHIATO)))
        }
    }
    

Java

    @LargeTest
    @RunWith(AndroidJUnit4.class)
    public class WebViewActivityTest {

        private static final String MACCHIATO = "Macchiato";
        private static final String DOPPIO = "Doppio";

        @Rule
        public ActivityTestRule<WebViewActivity> activityRule =
            new ActivityTestRule<WebViewActivity>(WebViewActivity.class,
                false /* Initial touch mode */, false /*  launch activity */) {

            @Override
            protected void afterActivityLaunched() {
                // Enable JavaScript.
                onWebView().forceJavascriptEnabled();
            }
        }

        @Test
        public void typeTextInInput_clickButton_SubmitsForm() {
           // Lazily launch the Activity with a custom start Intent per test
           activityRule.launchActivity(withWebFormIntent());

           // Selects the WebView in your layout.
           // If you have multiple WebViews you can also use a
           // matcher to select a given WebView, onWebView(withId(R.id.web_view)).
           onWebView()
               // Find the input element by ID
               .withElement(findElement(Locator.ID, "text_input"))
               // Clear previous input
               .perform(clearElement())
               // Enter text into the input element
               .perform(DriverAtoms.webKeys(MACCHIATO))
               // Find the submit button
               .withElement(findElement(Locator.ID, "submitBtn"))
               // Simulate a click via JavaScript
               .perform(webClick())
               // Find the response element by ID
               .withElement(findElement(Locator.ID, "response"))
               // Verify that the response page contains the entered text
               .check(webMatches(getText(), containsString(MACCHIATO)));
        }
    }
    

Untuk informasi selengkapnya tentang Espresso Web, lihat dokumentasi Espresso Web di situs AndroidX Test. Anda juga dapat mendownload cuplikan kode ini sebagai bagian dari Contoh kode Espresso Web.

Memverifikasi hasil

Panggil metode ViewInteraction.check() atau DataInteraction.check() untuk menyatakan bahwa tampilan di UI cocok dengan beberapa status yang diharapkan. Anda harus meneruskan objek ViewAssertion sebagai argumen. Jika pernyataan tersebut gagal, Espresso akan menampilkan AssertionFailedError.

Class ViewAssertions menyediakan daftar metode helper untuk menentukan penegasan umum. Pernyataan yang dapat Anda gunakan meliputi:

  • doesNotExist : Menyatakan bahwa tidak ada tampilan yang cocok dengan kriteria yang ditentukan dalam hierarki tampilan saat ini.
  • matches: Menyatakan bahwa tampilan yang ditentukan ada dalam hierarki tampilan saat ini dan statusnya cocok dengan beberapa Hamcrest matcher yang diberikan.
  • selectedDescendentsMatch: Menyatakan bahwa tampilan turunan yang ditentukan untuk tampilan induk ada, dan statusnya cocok dengan beberapa Hamcrest matcher yang diberikan.

Cuplikan kode berikut menunjukkan cara Anda dapat memeriksa apakah teks yang ditampilkan di UI memiliki nilai yang sama dengan teks yang sebelumnya dimasukkan dalam kolom EditText.

Kotlin

    fun testChangeText_sameActivity() {
        // Type text and then press the button.
        ...

        // Check that the text was changed.
        onView(withId(R.id.textToBeChanged))
                .check(matches(withText(STRING_TO_BE_TYPED)))
    }
    

Java

    public void testChangeText_sameActivity() {
        // Type text and then press the button.
        ...

        // Check that the text was changed.
        onView(withId(R.id.textToBeChanged))
                .check(matches(withText(STRING_TO_BE_TYPED)));
    }
    

Menjalankan pengujian Espresso pada perangkat atau emulator

Anda dapat menjalankan pengujian Espresso dari Android Studio atau dari command line. Pastikan untuk menentukan AndroidJUnitRunner sebagai runner instrumentasi default di project Anda.

Untuk menjalankan pengujian Espresso Anda, ikuti langkah-langkah untuk menjalankan pengujian berinstrumen yang dijelaskan dalam Memulai Pengujian.

Anda juga harus membaca Referensi Espresso API.

Referensi lainnya

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

Contoh

Codelab