Unit Test: Mock Objek dengan EasyMock

Unit test menurut gw adalah hal yang wajib dilakukan oleh para developer modern, karena memang tidak ada alasan untuk tidak melakukan itu. Kenapa harus melakukan unit testing?

Unit test meningkatkan kualitas kode, semakin banyak kode seharusnya bug semakin sedikit. Pada fase bug fixing misalnya, prinsip “mati satu tumbuh seribu” ini hal yg lumrah jika tidak menggunakan unit test. Kualitas kode ini dapat kita peroleh karena dengan unit test akan mudah melakukan refactoring dan penambahan fitur, kenapa? karena developer bisa confident bahwa perubahan atau penambahan fitur yang dilakukan tidak berdampak terhadap modul lain. Dengan prinsip Test Driven Development, yaitu menulis test dulu baru membuat implementasi kode, membuat setiap unit terkecil (method) dari kode akan mempunyai unit test. Sehingga ketika kode sudah banyak -unit testnya juga banyak- dan pada saat developer melakukan perubahan di satu tempat, dia akan yakin apakah test2 yang sebelumnya sukses atau gagal. Dan kalopun gagal, bugnya lgsng ketahuan -tidak pada saat testing atau UAT :P- . Hal ini tentu berdampak pada cepatnya development cycle. Walopun saya setuju pada saat awal2 akan terasa lambat kodingnya tapi ini investasi yang besar seiring bertambahnya kode aplikasi.

Menurut, andrew hunt pengarang pragmatic unit testing untuk membuat unit test yang baik harus memenuhi prinsip2 utama (A-TRIP):

  • Automatic, testing harus otomatis, setiap kali perubahan developer lgsng tahu apakah kode perubahan tsb error atau impact ke modul lain.
  • Through, testing harus mencakup seluruh kode aplikasi.
  • Repeatable, diulang berapa kali pun hasilnya tetap sama
  • Independent, tidak bergantung ke objek atau modul lain
  • Professional, menulis test harus sama bagusnya dengan menulis kode.

Pada kali ini gw akan bahas syarat yang ke empat yaitu Independent. lalu bagaimana menghilangkan dependensi terhadap objek2 lain dengan mock object. Sebenarnya ada hal2 lain kenapa harus me-mock (memalsukan) objek dalam unit test.

  • Objek belum jadi, bisa saja tim development dibagi 2 (UI dan application) dan ternyata tim app belum menyediakan object userDao yang dibutuhkan untuk autentikasi login di UI layer
  • Objek susah di-instansiasi
  • Objek tsb akses ke resource lain sehingga aksesnya jadi lambat
  • Objek tsb perilakunya tidak pasti, undeterministic
  • Objek diluar kekuasaan kita, contoh HttpServletRequest, objek yg disediakan adalah servlet container atau app server.

Contoh kali ini adalah LoginServlet, untuk autentikasi user, dan apabila benar maka diredirect ke inbox.html. mari kita lihat,

[sourcecode language=’java’]
public class LoginServlet extends HttpServlet {

private UserDao userDao;
public void setUserDao(final UserDao userDao) {
this.userDao = userDao;
}

@Override
public void doPost(final HttpServletRequest req, final HttpServletResponse resp)
throws ServletException, IOException {
final String username = req.getParameter(“username”);
final String password = req.getParameter(“password”);

if(userDao.isRegistered(username, password)) {
resp.sendRedirect(“/inbox.html”);
} else {
resp.sendRedirect(“/login.html?err=username_not_registered”);
}
}
}[/sourcecode]
UserDao belum dibuat oleh tim backend
[sourcecode language=’java’]
public interface UserDao {
public boolean isRegistered(String username, String password);
}[/sourcecode]
Jadi, apa saja yang harus dimock?

  • HttpServletRequest
  • HttpServletResponse
  • UserDao

Kita gunakan EasyMock untuk melakukan mock. Ini adalah contoh kodenya,
[sourcecode language=’java’]
import org.easymock.EasyMock;
import static org.easymock.EasyMock.*;

public class LoginServletTest {
String userReg = “java”;
String passReg = “123”;
String userNotReg = “dhiku”;
String passNotReg = “123”;

@Test
public void loginSuccess() throws IOException, ServletException, SQLException {
// Buat Mock
HttpServletRequest req = EasyMock.createMock(HttpServletRequest.class);
HttpServletResponse resp = EasyMock
.createMock(HttpServletResponse.class);
UserDao userDao = EasyMock.createMock(UserDao.class);

// Record
expect(req.getParameter(“username”)).andReturn(userReg);
expect(req.getParameter(“password”)).andReturn(passReg);
expect(userDao.isRegister(userReg, passReg)).andReturn(true);
resp.sendRedirect(“/inbox.html”);

// Replay
replay(req, resp, userDao);

// Call Method
LoginServlet ls = new LoginServlet();
ls.setUserDao(userDao);
ls.doPost(req, resp);

//Verify
verify(req, resp, userDao);
}
}[/sourcecode]
Jalankan testnya, dan hasilnya ‘ijo’ atau sukses. silahkan dicoba kombinasi2 lainya.Dalam melakukan mock ada beberapa hal yg harus diperrhatikan:

  1. Buat mock objek
  2. Rekam perilaku dari mock objek, misalnya krn kita mau test login sukses berarti method userDao.isRegister(userReg, passReg) harus mengembalikan nilai true, lalu kita rekam juga perilaku response yang akan redirect ke inbox.html
  3. Replay, atau siapkan mock objek tsb untuk dijalankan. Ingat, pada langkah sebelumnya kita baru merekam perilakunya.
  4. Jalankan method yang ingin kita test (yg tentunya punya dependensi ke mock objek yg sudah dibuat).
  5. Verifikasi apakah objek2 mock tadi sudah berprilaku sesuai dengan yang kita harapkan.

Setelah kita perhatikan ternyata semua objek yang kita mock adalah interface, bagaimana dengan yang class? bagaimana apabila 3rd party library tidak menyediakan interface? bagaimana kalo tim backend yang tidak mengerti konsep “Programming By Interface” hanya memberikan class?

Easy mock menyediakan extension class untuk memock objek bertipe class. Ini contoh kode UserDaoJdbc,
[sourcecode language=’java’]
public class UserDaoJdbc {
public boolean isRegistered(String username, String password){
return true;
}
}[/sourcecode]
Dan sebagian kode LoginServletnya kita ubah menjadi,
[sourcecode language=’java’]
private UserDaoJdbc userDaoJdbc;
public void setUserDao(final UserDaoJdbc userDaoJdbc) {
this.userDaoJdbc = userDaoJdbc;
}[/sourcecode]
Untuk kode testnya yang perlu kita lakukan hanya merubah import package,import static
[sourcecode language=’java’]
org.easymock.classextension.EasyMock.*;
import org.easymock.classextension.EasyMock;Dan kode testnya menjadi,
//Create mock
UserDaoJdbc userDaoJdbc = EasyMock.createMock(UserDaoJdbc.class);

//Replay

//Call Method
LoginServlet ls = new LoginServlet();
ls.setUserDao(userDaoJdbc);
ls.doPost(req, resp);

//Verify[/sourcecode]
Setelah itu, jalankan testnya, dan hasilnya ‘ijo’ lagi atau sukses.Mungkin pada bingung kalo kodenya banyak bgt dependensinya berarti kita harus buat banyak mock? betul! dan itu berati kode kita tidak memenuhi prinsip baik “loosely coupled” dan harus direfactor. Oke sekian penggunaan mock objek dengan EasyMock, mudahkan? so kenapa ga dari sekarang pake unit test??