|
觀察 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()方法驗證流程。
|