“Testing ?”, “Yes.. because every pro always testing !”.. *hhaha just kidding. So ceritanya di tempat kerja sekarang, setiap developer harus bikin Unit dan UI test untuk aplikasi yang lagi di’develop. Alhasil penulis yang sebelumnya ngga pernah bikin test jadi punya banyak PR deh hehe. Nah postingan kali ini akan coba bahas Unit Test dan UI / Instrument Test dengan JUnit, Mockito, dan Espresso Framework.
Android Studio menyediakan tools yang diperlukan untuk kegiatan testing, mengenai alasan kenapa testing itu diperlukan tidak akan dibahas di postingan ini, karena jika teman-teman yang di depan komputer lagi baca tulisan ini, itu artinya kita sama-sama setuju kalau testing itu penting 🙂
Tools untuk testing yang disediakan Android Studio ini dinamakan Testing Support Library. Di dalamnya terdapat :
- AndroidJUnitRunner -> JUnit 4-compatible test runner for Android.
- Espresso -> UI testing framework, suitable for functional UI testing within an app.
- UI Automator -> UI testing framework, suitable for cross-app functional UI testing accross system and installed apps.
Nah disini kita akan mencoba membuat aplikasi demo untuk Unit Test dan UI Test. So let’s fire our Android Studio !
SSH Gitlab : git@gitlab.com:bnctvns/unit_ui_test_demo.git
Download Zip : Gitlab
1. Create New Project.
2. Minimum SDK.
3. Pilih Tipe Activity.
4. Klik Next untuk dialog nama activity.
5. Setelah workspace Android Studio siap, ganti nama file “MainActivity.java” dengan “ButtonActivity.java”. Caranya adalah klik kanan di class “MainActivity.java”, kemudian pilih menu “Refactor” -> “Rename”. Setelah itu ganti juga nama file “activity_main.xml” dengan “activity_button.xml”, dengan cara yang sama.
6. Buat class baru dengan nama “EditActivity.java“, dan file layout dengan nama “activity_edit.xml”.
7. Sebelumnya kita harus menginstall terlebih dahulu tools yang dibutuhkan untuk proses testing, langkah-langkahnya adalah sebagai berikut :
- Buka Android SDK Manager.
- Buka tab “SDK Tools”.
- Centang “Android Support Repository”.
- “Apply” kemudian proses download dan install akan dimulai.
8. Setelah proses instalasi Android Support Repository selesai, maka berikutnya adalah modifikasi file build.gradle level module seperti berikut :
android { defaultConfig { .... testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:24.2.0' compile 'com.android.support:recyclerview-v7:24.2.0' compile 'com.android.support:design:24.2.0' // unit testing dependencies testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:1.10.19' // android testing dependencies androidTestCompile 'com.android.support:support-annotations:24.2.0' androidTestCompile 'com.android.support.test:runner:0.5' androidTestCompile 'com.android.support.test:rules:0.5' androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2' }
Kita harus mengatur konfigurasi dependencies dengan menggunakan API yang disediakan oleh JUnit 4 framework. Untuk menggunakan mock objek, kita akan menggunakan framework Mockito.
*Fungsi dari mock object adalah untuk mereplika objek yang digunakan oleh objek yang sedang kita tes. Dengan kata lain kita harus melakukan tes dengan mengisolasi objek yang sedang kita tes, dari dependencies yang objek tersebut gunakan. Alasannya adalah agar tes yang dilakukan hanya menguji hal-hal yang berada di dalam jangkauan objek yang sedang dites saja, dan tidak terpengaruh dari objek yang berada di luar itu. Nah oleh karena itu kita menggunakan mock framework.
9. Sync Gradle
Unit Testing
10. Pertama kita akan mencoba unit testing. Secara sederhana, unit testing adalah tes yang dijalankan di local JVM (di komputer kita). Google menyarankan untuk menggunakan unit testing jika method atau class yang ingin kita tes tidak membutuhkan atau hanya membutuhkan sedikit dependencies dari Android Framework (seperti class Context).
Kelebihan test ini adalah waktu yang digunakan untuk kegiatan tes akan lebih efisien, karena kita tidak perlu men’deploy aplikasi ke device atau emulator. Biasanya akan digunakan framework yang berfungsi sebagai mock object dalam kegiatan tes. Disini kita akan menggunakan framework Mockito.
Class test yang akan kita buat harus berada di direktori “module_name/src/test/java/”. Direktori ini dibuat otomatis ketika kita membuat project di Android Studio. *Jika karena beberapa sebab direktori ini tidak ada, kita bisa membuat direktori ini secara manual.
11. Buat interface ServiceCalculator.java dan class MyCalculator.java
Disini kita akan buat class MyCalculator yang akan digunakan oleh client (Activity) yang mempunyai fungsi-fungsi perhitungan sederhana. Dalam melakukan perhitungan, class MyCalculator ini akan menggunakan interface ServiceCalculator. Interface ini akan kita ganti dengan mock object dari framework Mockito.
*Penggunaan desain ini disebut dependency injection, yang dimaksudkan untuk mempermudah kegiatan testing.
package com.bonioctavianus.instrumenttestdemo; /** * Created by bonioctavianus on 9/12/16. */ public interface ServiceCalculator { int add(int num1, int num2); int substract(int num1, int num2); int multiply(int num1, int num3); int divide(int num1, int num2); }
package com.bonioctavianus.instrumenttestdemo; /** * Created by bonioctavianus on 9/12/16. */ public class MyCalculator { private ServiceCalculator serviceCalculator; private int num1; private int num2; public ServiceCalculator getServiceCalculator() { return serviceCalculator; } public void setServiceCalculator(ServiceCalculator serviceCalculator) { this.serviceCalculator = serviceCalculator; } public int getNum1() { return num1; } public void setNum1(int num1) { this.num1 = num1; } public int getNum2() { return num2; } public void setNum2(int num2) { this.num2 = num2; } public int add() { return serviceCalculator.add(num1, num2); } public int substract() { return serviceCalculator.substract(num1, num2); } public int multiply() { return serviceCalculator.multiply(num1, num2); } public int divide() { return serviceCalculator.divide(num1, num2); } }
12. Berikutnya adalah membuat class test untuk MyCalculator dengan nama “MyCalculatorTest.java“. Class ini harus berada di direktori “app/src/test/java/…/”.
package com.bonioctavianus.instrumenttestdemo; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.when; /** * Created by bonioctavianus on 9/12/16. */ @RunWith(MockitoJUnitRunner.class) public class MyCalculatorTest { @Mock private ServiceCalculator serviceCalculator; // object to test private MyCalculator myCalculator; private int num1 = 10; private int num2 = 5; @Before public void setUp() { myCalculator = new MyCalculator(); myCalculator.setServiceCalculator(serviceCalculator); myCalculator.setNum1(num1); myCalculator.setNum2(num2); } @Test public void addTest_shouldCorrect() { // mock the behaviour of service calculator when(serviceCalculator.add(num1, num2)).thenReturn(15); assertEquals("Result should be 15", 15, myCalculator.add()); } @Test public void substractTest_shouldCorrect() { // mock the behaviour of service calculator when(serviceCalculator.substract(num1, num2)).thenReturn(5); assertEquals("Result should be 5", 5, myCalculator.substract()); } @Test public void multiplyTest_shouldCorrect() { // mock the behaviour of service calculator when(serviceCalculator.multiply(num1, num2)).thenReturn(50); assertEquals("Result should be 50", 50, myCalculator.multiply()); } @Test public void divideTest_shouldCorrect() { // mock the behaviour of service calculator when(serviceCalculator.divide(num1, num2)).thenReturn(2); assertEquals("Result should be 2", 2, myCalculator.divide()); } }
Disini kita menggunakan beberapa annotation, diantaranya :
- “@RunWith(MockitoJUnitRunner.class)” di awal class untuk proses initialize Mockito.
- “@Mock” untuk membuat objek mock yang akan menggantikan objek yang asli. Pada contoh ini objek ServiceCalculator.
- “@Before” untuk proses initialize sebelum melakukan tes. Method yang diberi annotation “@Before” ini akan dijalankan sebelum menjalankan semua method dengan annotation “@Test”.
- “@Test” untuk method yang akan dites.
Selain annotation, kita juga menggunakan beberapa fungsi dari Mockito dan JUnit, seperti :
- “when()” untuk menandakan event dimana kita ingin memanipulasi behaviour dari mock objek.
- “thenReturn()” untuk memanipulasi output dari mock objek.
- “assertEquals()” untuk validasi output yang diharapkan, dan output yang sebenarnya.
Ok, next jalankan tes dengan cara klik kanan di class “MyCalculatorTest.class” pada project structure panel, kemudian pilih “Run MyCalculatorTest“. Setelah proses build selesai, maka hasil tes akan muncul pada console.
Yup kira-kira demikian contoh sederhana unit testing dengan JUnit dan Mockito. Sebenarnya masih ada banyak fungsi lain yang bisa digunakan untuk kegiatan unit testing, yang bisa dilihat di Docs Mockito.
Nah berikutnya adalah untuk UI / Instrument Test. Tes ini akan dijalankan di device atau emulator. Penggunaan tes ini adalah menguji interaksi user ketika menggunakan aplikasi kita, selain itu kita juga bisa menggunakan tes ini jika tes yang akan kita buat memerlukan dependencies dari framework Android yang tidak bisa diganti oleh mock objek.
UI Testing
13. UI testing ini dijalankan di device atau emulator. Pertama non-aktifkan fitur animasi pada device. Langkah ini adalah optional, karena jika fitur animasi aktif, maka tes terkadang gagal dan memberikan hasil yang tidak diharapkan (biasanya saat transisi Activity akan muncul exception PerformException).
Opsi untuk menonaktifkan animasi ada pada “Settings” di halaman “Developer Options“. Fitur yang perlu dinonaktifkan adalah sebagai berikut :
- Window animation scale
- Transition animation scale
- Animator duration scale
14. Modifikasi file “strings.xml” :
<resources> <string name="app_name">Instrument Test Demo</string> <string name="btn_show_toast">Show Toast</string> <string name="btn_show_snackbar">Show Snackbar</string> <string name="btn_show_dialog">Show Dialog</string> <string name="btn_show_edit_activity">Open Edit Activity</string> <string name="hint_edit_text_name">What\'s your name ?</string> <string name="btn_enter">Enter</string> </resources>
15. Modifikasi layout “activity_button.xml“.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout android:id="@+id/root_container" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical" android:padding="16dp"> <Button android:id="@+id/btn_toast" android:layout_width="300dp" android:layout_height="wrap_content" android:text="@string/btn_show_toast"/> <Button android:id="@+id/btn_snackbar" android:layout_width="300dp" android:layout_height="wrap_content" android:text="@string/btn_show_snackbar"/> <Button android:id="@+id/btn_dialog" android:layout_width="300dp" android:layout_height="wrap_content" android:text="@string/btn_show_dialog"/> </LinearLayout>
16. Modifikasi layout “activity_edit.xml“.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout android:id="@+id/root_container" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical" android:padding="16dp"> <EditText android:id="@+id/et_name" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/hint_edit_text_name" android:imeOptions="actionDone" android:inputType="textCapWords" android:maxLines="1" android:padding="10dp" android:textSize="18sp"/> <Button android:id="@+id/btn_enter" android:layout_width="300dp" android:layout_height="wrap_content" android:layout_marginTop="32dp" android:text="@string/btn_enter"/> </LinearLayout>
17. Modifikasi class “ButtonActivity.java“.
package com.bonioctavianus.instrumenttestdemo; import android.os.Bundle; import android.support.design.widget.Snackbar; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.Toast; public class ButtonActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_button); initViews(); } private void initViews() { findViewById(R.id.btn_toast).setOnClickListener(new ClickHandler()); findViewById(R.id.btn_snackbar).setOnClickListener(new ClickHandler()); findViewById(R.id.btn_dialog).setOnClickListener(new ClickHandler()); } private void showToast() { Toast.makeText(this, R.string.app_name, Toast.LENGTH_SHORT).show(); } private void showSnackBar() { Snackbar.make(findViewById(R.id.root_container), R.string.app_name, Snackbar.LENGTH_SHORT).show(); } private void showDialog() { new AlertDialog.Builder(this) .setMessage(R.string.app_name) .setCancelable(false) .setPositiveButton(android.R.string.ok, null).create().show(); } private class ClickHandler implements View.OnClickListener { @Override public void onClick(View view) { switch (view.getId()) { case R.id.btn_toast: showToast(); break; case R.id.btn_snackbar: showSnackBar(); break; case R.id.btn_dialog: showDialog(); break; } } } }
Class ini akan menampilkan tiga buah button, yang masing-masing button akan menampilkan informasi melaui Toast, Snackbar, dan Alert dialog.
18. Modifikasi class “EditActivity.java”
package com.bonioctavianus.instrumenttestdemo; import android.os.Bundle; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.EditText; /** * Created by bonioctavianus on 9/12/16. */ public class EditActivity extends AppCompatActivity { private EditText etName; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_edit); initViews(); } private void initViews() { etName = (EditText) findViewById(R.id.et_name); findViewById(R.id.btn_enter).setOnClickListener(new ClickHandler()); } private void showWelcomeDialog(String message) { new AlertDialog.Builder(this) .setMessage(message) .setCancelable(false) .setPositiveButton(android.R.string.ok, null).create().show(); } private class ClickHandler implements View.OnClickListener { @Override public void onClick(View view) { if (view.getId() == R.id.btn_enter) { String message = "Welcome " + etName.getText().toString(); showWelcomeDialog(message); } } } }
Class ini akan menampilkan edittext untuk mengisi nama, dan ketika button enter diclick, maka akan muncul alert dialog yang menampilkan pesan selamat datang.
19. Buat class test “ButtonActivityTest.java” di direktori “module_name/src/androidTest/java/”.
package com.bonioctavianus.instrumenttestdemo; import android.os.SystemClock; import android.support.test.espresso.action.ViewActions; import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.assertion.ViewAssertions.matches; import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; import static android.support.test.espresso.matcher.ViewMatchers.withId; import static android.support.test.espresso.matcher.ViewMatchers.withText; /** * Created by bonioctavianus on 9/12/16. */ @RunWith(AndroidJUnit4.class) public class ButtonActivityTest { @Rule public ActivityTestRule<ButtonActivity> mActivityRule = new ActivityTestRule<>(ButtonActivity.class); /** * Confirm that all views in view hierarchy is valid and displayed */ @Test public void checkAllViewsIsValid_sameActivity() { onView(withId(R.id.btn_toast)).check(matches(isDisplayed())); onView(withId(R.id.btn_snackbar)).check(matches(isDisplayed())); onView(withId(R.id.btn_dialog)).check(matches(isDisplayed())); } /** * Test button toast click action */ @Test public void showToast_sameActivity() { // perform click and show Toast onView(withId(R.id.btn_toast)).perform(ViewActions.click()); // for delay purposes SystemClock.sleep(1000); } /** * Test button snackbar click action */ @Test public void showSnackBar_sameActivity() { // perform click and show snackbar onView(withText("Show Snackbar")).perform(ViewActions.click()); // for delay purposes SystemClock.sleep(1000); } /** * Test button dialog click action */ @Test public void showDialog_sameActivity() { // perform click and show dialog onView(withId(R.id.btn_dialog)).perform(ViewActions.click()); } }
Test class ini akan menjadi class dimana kita akan melakukan tes pada activity ButtonActivity.
20. Buat class test “EditActivityTest.java” di direktori “module_name/src/androidTest/java/”.
package com.bonioctavianus.instrumenttestdemo; import android.support.test.espresso.action.ViewActions; import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.assertion.ViewAssertions.matches; import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; import static android.support.test.espresso.matcher.ViewMatchers.withId; import static android.support.test.espresso.matcher.ViewMatchers.withText; /** * Created by bonioctavianus on 9/12/16. */ @RunWith(AndroidJUnit4.class) public class EditActivityTest { private String inputText = "bonioctavianus"; @Rule public ActivityTestRule<EditActivity> mActivityRule = new ActivityTestRule<>(EditActivity.class); /** * Confirm that all views in view hierarchy is valid and displayed */ @Test public void checkAllViewsIsValid_sameActivity() { onView(withId(R.id.et_name)).check(matches(isDisplayed())); onView(withId(R.id.btn_enter)).check(matches(isDisplayed())); } @Test public void typeText_sameActivity() { // Type text then press the button onView(withId(R.id.et_name)).perform(ViewActions.typeText(inputText), ViewActions.closeSoftKeyboard()); // Validate the text was changed onView(withId(R.id.et_name)).check(matches(withText(inputText))); // Perform click action on button onView(withId(R.id.btn_enter)).perform(ViewActions.click()); } }
Test class ini akan menjadi class dimana kita akan melakukan tes pada activity EditActivity.
21.Ok, next jalankan tes dengan cara klik kanan di test class yang diinginkan pada project structure panel, kemudian pilih “Run…” Setelah proses build selesai, maka aplikasi akan diinstall di device atau emulator, dan tes akan dilakukan. Hasil tes akan ditampilkan di console.
Menggunakan View Matcher
Untuk mendapatkan referensi / menemukan view yang ingin ditest, kita menggunakan method “onView()” dari class “ViewMatchers”. Method onView() ini bisa menerima argumen String atau int. Pencarian yang dilakukan method “onView()” hanya dilakukan pada view hierarchy yang sedang aktif (layout yang sedang aktif). Jika view tidak ditemukan, maka kita akan mendapatkan exception “NoMatchingViewException”.
Contoh onView() dengan argumen String, digunakan untuk mencari view yang mempunyai teks sesuai dengan argumen yang diberikan (case sensitive) :
- onView(withText(“Show Toast”));
*note : bila terdapat lebih dari satu view yang mempunyai teks yang sama, maka view yang akan digunakan adalah view pertama yang ditemukan pada hirarki viewgroup.
Contoh onView() dengan argumen int, digunakan untuk mencari view yang mempunyai ID sesuai dengan argumen yang diberikan :
- onView(withId(R.id.btn_toast));
Menggunakan View Interaction
Untuk melakukan action pada view, kita bisa menggunakan method “perform()” dari class ViewInteraction. Argumen yang diterima adalah objek ViewAction. Argumen yang diberikan bisa lebih dari satu *dipisahkan dengan koma, dan Espresso akan mengeksekusinya sesuai dengan urutan argumen.
Beberapa contoh action yang disediakan :
- ViewActions.click() : untuk melakukan action click pada view.
- ViewActions.typeText() : untuk menuliskan text pada view.
Contoh penggunaan :
- onView(withId(R.id.btn_enter)).perform(ViewActions.click());
- onView(withId(R.id.et_name)).perform(ViewActions.typeText(inputText), ViewActions.closeSoftKeyboard());
Verifikasi output
Untuk melakukan verifikasi output pada view, kita bisa menggunakan method “check()” dari class ViewInteraction. Argumen yang diterima adalah objek ViewAssertion. Jika verifikasi gagal, maka Espresso akan memberikan exception “AssertionFailedError”.
Beberapa contoh method verifikasi yang disediakan :
- doesNotExist() : untuk validasi tidak ada view yang cocok dengan kriteria tes yang digunakan.
- matches() : untuk validasi ada view yang cocok dengan kriteria tes yang digunakan.
Selain itu kita juga bisa membuat “Suite class” untuk menjalankan beberapa class test secara berurutan. Contoh :
Buat class “AppTestSuite.java” di direktori “module_name/src/androidTest/java/” :
package com.bonioctavianus.instrumenttestdemo; import org.junit.runner.RunWith; import org.junit.runners.Suite; /** * Created by bonioctavianus on 9/12/16. */ @RunWith(Suite.class) @Suite.SuiteClasses({ButtonActivityTest.class, EditActivityTest.class}) public class AppTestSuite { }
Setelah itu klik kanan class AppTestSuite.java, kemudian pilih “Run ‘AppTestSuite’“.
Dan kira-kira begitu sedikit cerita mengenai Unit Testing dan UI / Instrument Testing, mohon maaf apabila ada kesalahan atau informasi yang kurang tepat. Saran-sarannya bisa diberikan di kolom komentar 🙂
Sebenarnya masih ada satu tipe tes lagi yaitu testing for Multiple Apps dengan UI Automator. Secara sederhananya tes ini menguji interaksi user ketika menggunakan aplikasi kita, dengan aplikasi lain yang ada di ponsel user. Contohnya ketika aplikasi kita memanggil aplikasi SMS atau telepon. Manual mengenai UI Automator ada disini.
Thanks for reading 🙂
References :
Image Android Banner : Android Test
Android Testing
unit testing ini gunanya apa ya om ?
SukaSuka
Terima kasih sudah berkunjung kesini 🙂
Unit testing sangat berguna untuk refactoring code atau update feature apps.. karena ketika proses refactoring, kita bisa menjalankan unit testing kita untuk memastikan apakah code yang sedang kita refactor masih berfungsi atau tidak, saat kita melakukan beberapa perubahan
SukaSuka
Masih bingung dengan testing ini
SukaSuka
Terima kasih sudah berkunjung 🙂
Bingung di bagian yang mana?
SukaSuka