觀 察 Mock 物件 中的例 子,對於BookmarkService, 你真正想測試的是什麼?透過add()方 法新增書籤時,如果Bookmark不 存在...
- 呼叫BookmarkDAO的get()取得Bookmark清單
- 呼叫BookmarkDAO的add()方 法
這是add()方 法中一個可能的流程,另一個可能的流程是Bookmark已存在...
- 呼叫BookmarkDAO的get()取得Bookmark清單
針對測試而言,Mock物件只要模擬出以上的兩種可能流程,或說是可能的行為,這邊使用 EasyMock 來示範如何預先錄製好流程,以供後續測試時使用,直接先來看程式的撰寫:
package test.cc.openhome;
import static org.easymock.EasyMock.*;
import org.junit.*;
import java.util.Arrays;
import cc.openhome.dao.BookmarkDAO;
import cc.openhome.model.Bookmark;
import cc.openhome.model.BookmarkService;
public class BookmarkServiceTest {
private Bookmark bookmark1;
private Bookmark bookmark2;
private BookmarkDAO mockDAO;
private BookmarkService service;
@Before
public void setUp() {
bookmark1 = new Bookmark("testURL1", "testTitle1", "testCategory1");
bookmark2 = new Bookmark("testURL2", "testTitle2", "testCategory2");
// 動態建立Mock物件
mockDAO = createMock(BookmarkDAO.class);
service = new BookmarkService(mockDAO);
}
@Test
public void testAddSameBookmark() {
// 錄製預期被呼叫的方法、設定預期之傳回值
expect(mockDAO.get()).andReturn(Arrays.asList(bookmark1));
replay(mockDAO);
service.add(bookmark1);
}
@Test
public void testAddDifferentBookmark() {
expect(mockDAO.get()).andReturn(Arrays.asList(bookmark1));
mockDAO.add(bookmark2);
replay(mockDAO);
service.add(bookmark2);
}
@After
public void tearDown() {
// 驗證預期的流程是否完成
verify(mockDAO);
}
}
EasyMock的概念很簡單,你測試的對象要與另一個物件互動時,預期互動的物件在測試過程中應有什麼行為,先行錄製下來,接著對實際對象進行測試,最 後驗證Mock物件是不是預期的行為都有按照先前的錄製被呼叫,若是表示實際測試對象功能正確。
以上例而言,EasyMock是採用動態代理技術,可以透過EasyMock.createMock()來動態建立BookmarkDAO的Mock 實作物件,接著你對該物件的任何方法呼叫都會被錄製,如果Mock物件的某方法呼叫過預期會有傳回值,則可以使用EasyMock.expect(),這 會傳回IExpectationSetters實作物件,你可以呼叫andReturn()方法來設定預期的傳回值。在錄製完成後, 呼叫EasyMock.replay()來 重新播放所錄製的行為。你可以呼叫EasyMock.verify()來驗證所預期之行為是否都被呼叫。
再以 In-container 測試 中的LoginServlet測 試為例,如果採用EasyMock測試,可以如下:
package test.cc.openhome;
import static org.easymock.EasyMock.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.easymock.IMocksControl;
import org.junit.*;
import java.io.IOException;
import cc.openhome.LoginServlet;
class TestForLoginServlet extends LoginServlet {
public void doTest(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException, IOException {
doPost(req, resp);
}
}
public class LoginServletTest {
private TestForLoginServlet loginServlet;
private IMocksControl control;
private HttpServletRequest request;
private RequestDispatcher dispatcher;
@Before
public void setUp() {
loginServlet = new TestForLoginServlet();
control = createControl();
request = control.createMock(HttpServletRequest.class);
dispatcher = control.createMock(RequestDispatcher.class);
}
@Test
public void testLoginSuccess() throws Throwable {
expect(request.getParameter("user")).andReturn("justin");
expect(request.getParameter("passwd")).andReturn("1234");
expect(request.getRequestDispatcher("success.html"))
.andReturn(dispatcher);
dispatcher.forward(request, null);
control.replay();
loginServlet.doTest(request, null);
}
@Test
public void testLoginFail() throws Throwable {
expect(request.getParameter("user")).andReturn("someone");
expect(request.getParameter("passwd")).andReturn("1234");
expect(request.getRequestDispatcher("login.html"))
.andReturn(dispatcher);
dispatcher.forward(request, null);
control.replay();
loginServlet.doTest(request, null);
}
@After
public void tearDown() {
control.verify();
}
}
如果模擬的對象有相依性,可以使用EasyMock.createControl()來 建立Mock的控制物件,後續透過IMocksControl的replay()方 法來進行重播,使用verify()方 法驗證流程。