news

Senin, 15 Juni 2020

Belajar Instrumentation Testing Aplikasi Android


Tujuan

Pada codelab kali ini Anda akan menguji antar muka dari aplikasi menggunakan Espresso. Tujuannya agar ui dan ux aplikasi dapat berjalan dengan benar. Hasil dari codelab kali ini akan menjadi seperti ini:

201911251452175db15e23c9c08a2ee1c0c9e13e59ee17.gif


Skenario Pengujian

Terdapat 4 skenario pengujian Instrumentation Testing:
  1. Pengujian keliling balok.
    • Aplikasi terbuka dan menampilkan beberapa view.
    • Memberi tindakan input pada edt_lenght, edt_width dan edt_height.
    • Memastikan Button btn_save telah ditampilkan.
    • Memberi tindakan klik pada btn_save.
    • Memastikan Button btn_calculate_circuference telah ditampilkan.
    • Memberi tindakan klik pada btn_calculate_circuference.
    • Memastikan TextView tv_result telah ditampilkan.
    • Memastikan hasil yang tampil sesuai ekspektasi.
  2. Pengujian volume balok.
    • Aplikasi terbuka dan menampilkan beberapa view.
    • Memberi tindakan input pada edt_lenght, edt_width dan edt_height.
    • Memastikan Button btn_save telah ditampilkan.
    • Memberi tindakan klik pada btn_save.
    • Memastikan Button btn_calculate_volume telah ditampilkan.
    • Memberi tindakan klik pada btn_calculate_volume.
    • Memastikan TextView tv_result telah ditampilkan.
    • Memastikan hasil yang ditampilkan sesuai dengan ekspektasi.
  3. Pengujian luas permukaan balok.
    • Memberi tindakan input pada edt_lenght, edt_width dan edt_height.
    • Memastikan Button btn_save telah ditampilkan.
    • Memberi tindakan klik pada btn_save.
    • Memastikan Button btn_calculate_surface_area telah ditampilkan.
    • Memberi tindakan klik pada btn_calculate_surface_area.
    • Memastikan TextView tv_result telah ditampilkan.
    • Memastikan hasil yang tampil sesuai ekspektasi.
  4. Pengujian input panjang, lebar dan tinggi balok.
    • Aplikasi terbuka dan menampilkan beberapa view.
    • Memberi tindakan empty input pada edt_lenght.
    • Memastikan Button btn_save telah ditampilkan.
    • Memberi tindakan klik pada btn_save.
    • Memastikan eror yang tampil sesuai ekspektasi.
    • Memberi tindakan input pada edt_lenght.
    • Memberi tindakan empty input pada edt_width.
    • Memastikan Button btn_save telah ditampilkan.
    • Memberi tindakan klik pada btn_save.
    • Memastikan eror yang tampil sesuai ekspektasi.
    • Memberi tindakan input pada edt_width.
    • Memberi tindakan empty input pada edt_height.
    • Memastikan Button btn_save telah ditampilkan.
    • Memberi tindakan klik pada btn_save.
    • Memastikan eror yang tampil sesuai ekspektasi.
    • Memberi tindakan input pada edt_height.
    • Memastikan Button btn_save telah ditampilkan.
    • Memberi tindakan klik pada btn_save.


Codelab UI Testing

  1. Bukalah proyek sebelumnya tentang Unit Testing : Latihan Menggunakan Mockito.
  2. Tentu pertama kali Anda perlu menambahkan library dari Espresso di build.gradle:
    androidTestImplementation 'androidx.test:rules:1.2.0'
    androidTestImplementation 'androidx.test:runner:1.2.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
  3. Untuk membuat proses pengujian berjalan lancar, silakan matikan semua fungsi animasi yang terdapat pada halaman Developer Options pada Setting → Developer Options seperti berikut:
    20180426110552b57f6fb11ab273a31b0f3bd12ca5de4a.20180426110602587120a5faaf1df67cd63c14ca7fd67a.
    Lakukan untuk semua konfigurasi animasi di atas. Alasannya adalah untuk memudahkan Espresso dalam menjalankan kode dengan beragam resource yang digunakannya.
  4. Sekarang saatnya Anda membuat kelas pengujian otomatis dengan cara buat kelas baru bernama MainActivityEspressoTest pada package utama yang berlabel (androidTest). Caranya, masuklah ke MainActivity kemudian tekan Alt+Enter maka akan muncul tampilan seperti ini:
    20190912103120f17d47aec48772d2fa91c9625376f2abPilihlah Create Test, sehingga akan muncul tampilan seperti ini:
    20190729140238d530215bf885b1b4434074f4a715bc3e
    Biarkan default seperti tampilan di atas, kemudian pilih OK. Maka akan ada dua pilihan androidTest atau test, untuk Instrumental Test pilihlah androidTest.
    201907291404012d15b0cbc2d2573c777f2b95d5511bba
    Selanjutnya akan terbentuk sebuah kelas seperti ini:
    201910020102149f2db5e228ab1bebaab17b50500891c4Anda bisa menghapus kelas ExampleInstrumentedTest dan ExampleUnitTest karena kelas tersebut tidak dibutuhkan dalam proyek Anda.
  5. Pertama Anda buka terlebih dahulu berkas MainActivityEspressoTest dan tambahkanlah kode-kode berikut:
    Kotlin
    @RunWith(AndroidJUnit4ClassRunner::class)
    class MainActivityTest {
    private val dummyVolume = "504.0"
    private val dummyCircumference = "100.0"
    private val dummySurfaceArea = "396.0"
    private val dummyLength = "12.0"
    private val dummyWidth = "7.0"
    private val dummyHeight = "6.0"
    private val emptyInput = ""
    private val fieldEmpty = "Field ini tidak boleh kosong"

    @get:Rule
    var mActivityRule = ActivityTestRule(MainActivity::class.java)

    }
    Java
    @RunWith(AndroidJUnit4ClassRunner.class)
    public class MainActivityTest {
    private final String dummyVolume = "504.0";
    private final String dummyCircumference = "100.0";
    private final String dummySurfaceArea = "396.0";
    private final String dummyLength = "12.0";
    private final String dummyWidth = "7.0";
    private final String dummyHeight = "6.0";
    private final String emptyInput = "";
    private final String fieldEmpty = "Field ini tidak boleh kosong";

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

    }
  6. Setelah itu, Anda akan melakukan pengujian keliling balok seperti yang ada di skenario di atas.
    Kotlin
    @RunWith(AndroidJUnit4ClassRunner::class)
    class MainActivityTest {
    ...

    @Test
    fun assertGetCircumference() {
    onView(withId(R.id.edt_length)).perform(typeText(dummyLength), closeSoftKeyboard())
    onView(withId(R.id.edt_width)).perform(typeText(dummyWidth), closeSoftKeyboard())
    onView(withId(R.id.edt_height)).perform(typeText(dummyHeight), closeSoftKeyboard())

    onView(withId(R.id.btn_save)).check(matches(isDisplayed()))
    onView(withId(R.id.btn_save)).perform(click())

    onView(withId(R.id.btn_calculate_circumference)).check(matches(isDisplayed()))
    onView(withId(R.id.btn_calculate_circumference)).perform(click())

    onView(withId(R.id.tv_result)).check(matches(isDisplayed()))
    onView(withId(R.id.tv_result)).check(matches(withText(dummyCircumference)))
    }

    }
    Java
    @RunWith(AndroidJUnit4ClassRunner.class)
    public class MainActivityTest {
    ...

    @Test
    public void assertGetCircumference() {
    onView(withId(R.id.edt_length)).perform(typeText(dummyLength), closeSoftKeyboard());
    onView(withId(R.id.edt_width)).perform(typeText(dummyWidth), closeSoftKeyboard());
    onView(withId(R.id.edt_height)).perform(typeText(dummyHeight), closeSoftKeyboard());

    onView(withId(R.id.btn_save)).check(matches(isDisplayed()));
    onView(withId(R.id.btn_save)).perform(click());

    onView(withId(R.id.btn_calculate_circumference)).check(matches(isDisplayed()));
    onView(withId(R.id.btn_calculate_circumference)).perform(click());

    onView(withId(R.id.tv_result)).check(matches(isDisplayed()));
    onView(withId(R.id.tv_result)).check(matches(withText(dummyCircumference)));
    }

    }
    Beberapa metode tidak akan ditawarkan auto-complete-nya oleh Android Studio. Untuk mengatasi ini, Anda dapat menekan Alt + Enter pada beberapa metode-metode tadi dan melakukan static import seperti contoh di bawah ini:
    20181120171101a2eab246d667211ced83bd02cd5ba2d8
  7. Setelah selesai, sekarang saatnya menjalankan kode pengujian. Jika sebelumnya Anda menjalankan aplikasi terlebih dahulu, maka sekarang pendekatannya dibalik. Anda harus menjalankan kode testnya terlebih dahulu. Caranya, klik kanan pada MainActivityTest dan pilih run ‘MainActivity…’ seperti pada gambar di bawah ini:
    20190912103634f1b372922bae734af65194c716d69da3
    Lalu pilih peranti yang terhubung dan akan menjadi tempat pengujian aplikasi.
    201811201712426e7d2c1d0efc7afcda3eca45945cb220
    Perhatikan peranti Anda selama pengujian berlangsung.
    201911251458339421bfb4eeecacf5e719e182a1d205b4.gifBeragam nilai inputan akan dimasukkan secara otomatis. Keren kan?
  8. Setelah berjalan, perhatikan monitor panel di bagian bawah Android Studio yang aktif. Panel tersebut akan berisi informasi progress pengujian dan jika semua pengujian yang dilakukan berhasil maka hasil sukses pada layar seperti di bawah ini akan tampil:
    20190729170517f9d909f2574d82cac1b1b4fedff983d4
  9. Selanjutnya tambahkan kode untuk melakukan pengujian menghitung volume, luas permukaan dan input kosong di dalam aplikasi.
    Kotlin
    @RunWith(AndroidJUnit4ClassRunner::class)
    class MainActivityTest {
    ...

    @Test
    fun assertGetSurfaceArea() {
    onView(withId(R.id.edt_length)).perform(typeText(dummyLength), closeSoftKeyboard())
    onView(withId(R.id.edt_width)).perform(typeText(dummyWidth), closeSoftKeyboard())
    onView(withId(R.id.edt_height)).perform(typeText(dummyHeight), closeSoftKeyboard())

    onView(withId(R.id.btn_save)).check(matches(isDisplayed()))
    onView(withId(R.id.btn_save)).perform(click())

    onView(withId(R.id.btn_calculate_surface_area)).check(matches(isDisplayed()))
    onView(withId(R.id.btn_calculate_surface_area)).perform(click())

    onView(withId(R.id.tv_result)).check(matches(isDisplayed()))
    onView(withId(R.id.tv_result)).check(matches(withText(dummySurfaceArea)))
    }

    @Test
    fun assertGetVolume() {
    onView(withId(R.id.edt_length)).perform(typeText(dummyLength), closeSoftKeyboard())
    onView(withId(R.id.edt_width)).perform(typeText(dummyWidth), closeSoftKeyboard())
    onView(withId(R.id.edt_height)).perform(typeText(dummyHeight), closeSoftKeyboard())

    onView(withId(R.id.btn_save)).check(matches(isDisplayed()))
    onView(withId(R.id.btn_save)).perform(click())

    onView(withId(R.id.btn_calculate_volume)).check(matches(isDisplayed()))
    onView(withId(R.id.btn_calculate_volume)).perform(click())

    onView(withId(R.id.tv_result)).check(matches(isDisplayed()))
    onView(withId(R.id.tv_result)).check(matches(withText(dummyVolume)))
    }

    //Pengecekan untuk empty input
    @Test
    fun assertEmptyInput() {
    // pengecekan input untuk length
    onView(withId(R.id.edt_length)).perform(typeText(emptyInput), closeSoftKeyboard())

    onView(withId(R.id.btn_save)).check(matches(isDisplayed()))
    onView(withId(R.id.btn_save)).perform(click())

    onView(withId(R.id.edt_length)).check(matches(hasErrorText(fieldEmpty)))
    onView(withId(R.id.edt_length)).perform(typeText(dummyLength), closeSoftKeyboard())

    // pengecekan input untuk width
    onView(withId(R.id.edt_width)).perform(typeText(emptyInput), closeSoftKeyboard())

    onView(withId(R.id.btn_save)).check(matches(isDisplayed()))
    onView(withId(R.id.btn_save)).perform(click())

    onView(withId(R.id.edt_width)).check(matches(hasErrorText(fieldEmpty)))
    onView(withId(R.id.edt_width)).perform(typeText(dummyWidth), closeSoftKeyboard())

    // pengecekan input untuk height
    onView(withId(R.id.edt_height)).perform(typeText(emptyInput), closeSoftKeyboard())

    onView(withId(R.id.btn_save)).check(matches(isDisplayed()))
    onView(withId(R.id.btn_save)).perform(click())

    onView(withId(R.id.edt_height)).check(matches(hasErrorText(fieldEmpty)))
    onView(withId(R.id.edt_height)).perform(typeText(dummyHeight), closeSoftKeyboard())

    onView(withId(R.id.btn_save)).check(matches(isDisplayed()))
    onView(withId(R.id.btn_save)).perform(click())
    }

    }
    Java
    @RunWith(AndroidJUnit4ClassRunner.class)
    public class MainActivityTest {
    ...

    @Test
    public void assertGetSurfaceArea() {
    onView(withId(R.id.edt_length)).perform(typeText(dummyLength), closeSoftKeyboard());
    onView(withId(R.id.edt_width)).perform(typeText(dummyWidth), closeSoftKeyboard());
    onView(withId(R.id.edt_height)).perform(typeText(dummyHeight), closeSoftKeyboard());

    onView(withId(R.id.btn_save)).check(matches(isDisplayed()));
    onView(withId(R.id.btn_save)).perform(click());

    onView(withId(R.id.btn_calculate_surface_area)).check(matches(isDisplayed()));
    onView(withId(R.id.btn_calculate_surface_area)).perform(click());

    onView(withId(R.id.tv_result)).check(matches(isDisplayed()));
    onView(withId(R.id.tv_result)).check(matches(withText(dummySurfaceArea)));
    }

    @Test
    public void assertGetVolume() {
    onView(withId(R.id.edt_length)).perform(typeText(dummyLength), closeSoftKeyboard());
    onView(withId(R.id.edt_width)).perform(typeText(dummyWidth), closeSoftKeyboard());
    onView(withId(R.id.edt_height)).perform(typeText(dummyHeight), closeSoftKeyboard());

    onView(withId(R.id.btn_save)).check(matches(isDisplayed()));
    onView(withId(R.id.btn_save)).perform(click());

    onView(withId(R.id.btn_calculate_volume)).check(matches(isDisplayed()));
    onView(withId(R.id.btn_calculate_volume)).perform(click());

    onView(withId(R.id.tv_result)).check(matches(isDisplayed()));
    onView(withId(R.id.tv_result)).check(matches(withText(dummyVolume)));
    }

    //Pengecekan untuk empty input
    @Test
    public void assertEmptyInput() {
    // pengecekan input untuk length
    onView(withId(R.id.edt_length)).perform(typeText(emptyInput), closeSoftKeyboard());

    onView(withId(R.id.btn_save)).check(matches(isDisplayed()));
    onView(withId(R.id.btn_save)).perform(click());

    onView(withId(R.id.edt_length)).check(matches(hasErrorText(fieldEmpty)));
    onView(withId(R.id.edt_length)).perform(typeText(dummyLength), closeSoftKeyboard());

    // pengecekan input untuk width
    onView(withId(R.id.edt_width)).perform(typeText(emptyInput), closeSoftKeyboard());

    onView(withId(R.id.btn_save)).check(matches(isDisplayed()));
    onView(withId(R.id.btn_save)).perform(click());

    onView(withId(R.id.edt_width)).check(matches(hasErrorText(fieldEmpty)));
    onView(withId(R.id.edt_width)).perform(typeText(dummyWidth), closeSoftKeyboard());

    // pengecekan input untuk height
    onView(withId(R.id.edt_height)).perform(typeText(emptyInput), closeSoftKeyboard());

    onView(withId(R.id.btn_save)).check(matches(isDisplayed()));
    onView(withId(R.id.btn_save)).perform(click());

    onView(withId(R.id.edt_height)).check(matches(hasErrorText(fieldEmpty)));
    onView(withId(R.id.edt_height)).perform(typeText(dummyHeight), closeSoftKeyboard());

    onView(withId(R.id.btn_save)).check(matches(isDisplayed()));
    onView(withId(R.id.btn_save)).perform(click());
    }

    }
  10. Jalankan kembali instrumental testing Anda, maka hasilnya akan jadi seperti ini:
    20191125142523fec1b4d05f8071799eeb6be7b134d7d5.png4 dari 4 skenario pengujian berhasil dilakukan. Luar biasa!
  11. Sekarang Anda sudah berhasil melakukan proses pengujian secara otomatis. Mari kita ulik lebih lanjut. Mari hilangkan baris ini pada kode di MainActivity.
    Kotlin
    edtWidth = findViewById(R.id.edt_width)
    Java
    edtWidth = findViewById(R.id.edt_width);
    Coba jalankan kode test seperti cara sebelumnya. Seharusnya proses pengujian akan gagal. Anda dapat mengetahui pesan dan penyebab kegagalan pada monitor panel seperti pada gambar di bawah ini:201907291716420cd2b0416ce223c207b76e49147d3fbe
    android.support.test.espresso.PerformException: Error performing 'single click - At Coordinates: 539, 947 and precision: 16, 16' on view 'Animations or transitions are enabled on the target device.
    For more info check: http://goo.gl/qVu1yV

    with id: com.dicoding.picodiploma.myunittest:id/btn_save'.
    at android.support.test.espresso.PerformException$Builder.build(PerformException.java:82)
    ...
    at android.support.test.espresso.ViewInteraction.perform(ViewInteraction.java:114)
    at com.dicoding.picodiploma.myunittest.MainActivityTest.assertSurfaceArea(MainActivityTest.java:58)
    at java.lang.reflect.Method.invoke(Native Method)
    ...
    at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2145)
    Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'android.text.Editable android.widget.EditText.getText()' on a null object reference
    at com.dicoding.picodiploma.myunittest.MainActivity.onClick(MainActivity.java:46)
    at android.view.View.performClick(View.java:6597)
    Bagaimana? Masih pusing? Daripada pusing, mari kita bedah kode.

Bedah Kode

Espresso

Sebelumnya, kita perlu ingat kembali tiga komponen utama dari Espresso:
  1. ViewMatchers (onView(ViewMatcher)), untuk menemukan elemen atau komponen antar muka yang diuji.
  2. ViewActions (perform(ViewAction)), untuk memberikan event untuk melakukan sebuah aksi pada komponen antar muka yang diuji.
  3. ViewAssertions(check(ViewAssertion)), untuk mengecek sebuah kondisi atau state dari komponen yang diuji.
Kita akan membedah kode yang baru saja kita buat berdasarkan skenario yang kita buat.
Kotlin
@get:Rule
var mActivityRule = ActivityTestRule(MainActivity::class.java)
Java
@Rule
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class);
Kode di atas digunakan untuk memerintahkan Activity mana yang akan dijalankan.

Selanjutnya perhatikan kode berikut:
Kotlin
onView(withId(R.id.edt_length)).perform(typeText(dummyLength), closeSoftKeyboard())
Java
onView(withId(R.id.edt_length)).perform(typeText(dummyLength), closeSoftKeyboard());
Perhatikan kode di atas, jika kode di atas dibaca maka jadi seperti ini: Sebuah view dengan id edt_length diberi tindakan input dengan sebuah teks dummyLength dan menutup secara berlahan keyboard Android. Jadi terdapat banyak aksi di dalam komponen Espresso.

Selanjutnya kode berikut:
Kotlin
onView(withId(R.id.btn_save)).check(matches(isDisplayed()))
Java
onView(withId(R.id.btn_save)).check(matches(isDisplayed()));
Jika kode di atas dibaca akan menjadi seperti ini: Memastikan sebuah view dengan id btn_save dalam keadaan tampil.

Lalu perhatikan kode berikut:
Kotlin
onView(withId(R.id.btn_save)).perform(click())
Java
onView(withId(R.id.btn_save)).perform(click());
Jika kode di atas dibaca maka jadi seperti ini: Sebuah view dengan id btn_save diberi aksi klik.
Dan yang paling penting adalah ini:
Kotlin
onView(withId(R.id.tv_result)).check(matches(withText(dummyCircumference)))
Java
onView(withId(R.id.tv_result)).check(matches(withText(dummyCircumference)));
Jika kode di atas dibaca akan menjadi seperti ini: Memastikan sebuah view dengan id tv_result mempunyai teks yang sama dengan dummyCircumference.

Perhatikan juga kode berikut yang berfungsi untuk mengecek eror pada EditText:
Kotlin
onView(withId(R.id.edt_length)).check(matches(hasErrorText(fieldEmpty)))
Java
onView(withId(R.id.edt_length)).check(matches(hasErrorText(fieldEmpty)));
Jika kode di atas dibaca akan menjadi seperti ini: Memastikan sebuah view dengan id edt_length mempunyai pesan eror yang sama dengan fieldEmpty.

Bagaimana? Mudah kan proses pengujian antarmuka dengan menggunakan Espresso? Tak hanya mudah tapi juga efisien jika Anda berhadapan dengan kasus yang lebih besar. Kebiasaan menuliskan kode pengujian akan melatih Anda lebih kritis terhadap beragam kondisi yang akan terjadi pada tiap fungsi di dalam aplikasi.

Apalagi bila Anda menulis kode test sebelum kode aplikasi. Proses pengembangan aplikasi Anda dijamin jauh lebih baik dan mampu menghasilkan produk yang lebih berkualitas! Proses pengujian otomatis di atas masih sangatlah sederhana. Kami tantang Anda untuk mengeksplorasi lebih jauh kemampuan Espresso lainnya!

Seperti biasa, agar Anda lebih paham tentang materi ini, kami sarankan Anda membaca tiap tautan di bawah ini:

Untuk source code materi ini silakan Anda unduh di: