หลาย ๆ คนคงรู้จักคำว่า mock-up คำนี้มีความหมายว่า แบบจำลอง หรือวัตถุปลอม ๆ ที่เอาไว้สำหรับแสดงให้ลูกค้าดูว่าเมื่อผลิตภัณฑ์สร้างเสร็จแล้วหน้าตามันจะเป็นอย่างไร คำนี้มีเป็นคำผสมที่มีพื้นฐานจากคำว่า mock ที่แปลว่าการล้อเลียน
แล้วไอ้การล้อเลียนที่ว่านี้มันใช้ทำอะไรในการพัฒนาโปรแกรมได้ ?
คืองี้ครับ หลาย ๆ ครั้งที่เราต้องเขียนโปรแกรมที่ซับซ้อน และต้องอาศัย parameter จากคลาสที่มีความซับซ้อนมาก ๆ ซึ่งการทดสอบโปรแกรมนั้น ๆ ด้วยการสร้างตัวแปรขึ้นมาเป็น parameter ส่งเข้าไปนั้นอาจจะเป็นเรื่องที่ทำได้ยาก
อย่างเช่นสมมติว่าเราพัฒนาโปรแกรมที่เป็น Servlet โดยเรามีคลาสที่สืบทอดมาจาก HTTPServlet
และเรากำลังจะเขียนโปรแกรมทดสอบเจ้าเมธอด doGet()
ที่มีหน้าตาแบบนี้
class MyServlet extends HTTPServlet {
@Override
protected void doGet(HttpServletRequest req,
HttpServletResponse resp) {
/* Implementations */
}
}
คำถามคือ แล้วเราจะสร้างตัวแปรที่มีประเภทเป็น HttpServletRequest
และ HttpServletResponse
ได้อย่างไร ? เพราะเจ้า type ทั้งสองตัวนี้เป็น Interface
ครับ
คำตอบน่ะเหรอ … เขียนคลาสใหม่ขึ้นมา implement เจ้าสองคลาสนี้ไปเลยครับ
class MockHttpServletRequest implments HttpServletRequest{
/* implements every method here*/
}
class MockHttpServletResponse implements HttpServletResponse {
/* implements every method here*/
}
เขียน implementation ของแต่ละ method ข้างในให้ง่ายที่สุดครับ อันไหน get ก็คืนค่าคงที่ไปเลย อันไหน set ก็สร้างตัวแปรมารับ อะไรทำนองนี้ เช่น
class MockHttpServletRequest implments HttpServletRequest{
String getContextPath() {
return "/";
}
}
ทั้งนี้ถ้าเป็น method ที่ใช้ในการทดสอบด้วย ก็ต้องเขียนให้สอดคล้องกับ test case ด้วยนะครับ
ทีนี้เราก็เขียนโปรแกรม unit test ได้ง่าย ๆ แบบนี้ครับ
class MyHttpServletTest {
@Test
void testDoGetRootPath() {
HttpServletRequest req = new MockHttpServletRequest();
HttpServletResponse resp = new MockHttpServletResponse();
MyHttpServlet servlet = new MyHttpServlet();
servlet.service(req, resp);
}
}
อันนี้พอดีว่า doGet()
มันเป็น private method ครับ ไปเรียกตรง ๆ ไม่ได้ ผมเลี่ยงไปเรียก service()
แทน เพราะผมรู้ว่ามันจะไปเรียก doGet()
แต่ถ้าอยากทดสอบเจ้า doGet()
ก็มีอีกวิธี ก็คือเขียนชุดโปรแกรมทดสอบเป็น class ที่สืบทอดเจ้า MyHttpServletTest
อีกต่อไปเลยโลด
class MyHttpServletTest extends MyHttpServlet{
@Test
void testDoGetRootPath() {
HttpServletRequest req = new MockHttpServletRequest();
HttpServletResponse resp = new MockHttpServletResponse();
MyHttpServletTest servlet = new MyHttpServlet();
servlet.doGet(req, resp);
}
}
แค่นี้เอง
(อีกวิธีคือเอาตัว test ใส่ลงไปในคลาสที่จะ test ไปเลย ซึ่งไม่แนะนำครับ เพราะว่าตัว test จะติดไปในโค๊ดที่จะเอาไปรันจริงด้วย ทำให้มันกินแรมมากขึ้นเปล่า ๆ)
ปัญหาของการใช้ Mock คือ … ในกรณีที่แย่ที่สุดเราอาจจะต้องสร้างคลาสขึ้นมาสำหรับแต่ละเทสต์เคสเลย ซึ่งมันเยอะมาก และมันก็อาจจะไม่ชัดเจนพอที่เอาไปใส่ใน unit test (ซึ่งควรจะเรียบง่ายที่สุดไม่งั้นมันจะอ่านยาก) ที่แย่กว่านั้นเราต้อง implement เมธอดที่เราไม่ได้ใช้ทดสอบด้วย ทำให้โค๊ดยาวโดยใช้เหตุ
สำหรับจุดนี้เราสามารถใช้ mock framework เข้ามาช่วยได้ครับ ในภาษา Java นั้นมี Mock Framework ที่ได้รับความนิยมอยู่หลายตัว (ลอง Google ดูนะครับ) ผมจะลองใช้ Mockito ดู เจ้าโปรแกรมทดสอบมันก็จะเหลือแค่นี้
import static org.mockito.Mockito.*;
class MyHttpServletTest extends MyHttpServlet{
@Test
void testDoGetRootPath() {
HttpServletRequest req = mock(HttpServletRequest.class);
when(req.getContextPath()).thenReturn("/");
HttpServletResponse resp = mock(HttpServletResponse.class);
MyHttpServletTest servlet = new MyHttpServlet();
servlet.doGet(req, resp);
}
}
แค่นี้เอง ผมไม่ต้องสร้างคลาสใหม่ขึ้นมาเพื่อใช้ทดสอบด้วย ยอดไปเลย 🙂
สุดท้ายนี้ถ้าถามว่าทำไมถึงเรียกว่า mock ผมคิดว่ามันเกิดจากการล้อเลียนเจ้า class จริง ๆ น่ะครับ เราสร้าง class ใหม่ขึ้นมาล้อเลียนตัวเดิมที่ใช้งานได้ กลายเป็นคลาสง่อย ๆ ตัวนึง อะไรทำนองนี้ ที่จริงการสร้าง mock-up ก็คือการสร้างวัตถุชิ้นหนึ่งที่มีหน้าตาเหมือนของจริงแต่ทำอะไรไม่ได้ ใช่ไหมล่ะครับ 😉 เจ้า mock object นี่ก็เหมือนกันแหละ
ปล.โค๊ดข้างบนยังไม่ได้เทสต์ครับ อาจจะมีจุดผิดได้ 555 ลองดูก็แล้วกัน
โปรแกรมเมอร์ C++ และผู้นิยมดนตรี