接 續 EasyMock 簡介, 這邊改用另一套也為人所接受的Mock框架 JMock 來進行BookmarkDAO的 模擬,如果搭配JUnit 3來進行撰寫:
package test.cc.openhome;
import org.jmock.Expectations;
import org.jmock.Mockery;
import java.util.Arrays;
import junit.framework.TestCase;
import cc.openhome.dao.BookmarkDAO;
import cc.openhome.model.Bookmark;
import cc.openhome.model.BookmarkService;
public class BookmarkServiceTest extends TestCase {
private Bookmark bookmark1;
private Bookmark bookmark2;
private BookmarkDAO mockDAO;
private BookmarkService service;
private Mockery context;
public void setUp() {
bookmark1 = new Bookmark("testURL1", "testTitle1", "testCategory1");
bookmark2 = new Bookmark("testURL2", "testTitle2", "testCategory2");
context = new Mockery();
// 建立Mock物件
mockDAO = context.mock(BookmarkDAO.class);
service = new BookmarkService(mockDAO);
}
public void testAddSameBookmark() {
// 設定預期行為
context.checking(new Expectations() {{
// 會呼叫DAO的get()
oneOf(mockDAO).get();
will(returnValue(Arrays.asList(bookmark1))); // 預期傳回值
}});
service.add(bookmark1);
}
public void testAddDifferentBookmark() {
context.checking(new Expectations() {{
oneOf(mockDAO).get();
will(returnValue(Arrays.asList(bookmark1)));
oneOf(mockDAO).add(bookmark2);
}});
service.add(bookmark2);
}
public void tearDown() {
// 斷言是否滿足預期行為
context.assertIsSatisfied();
}
}
最主要的是在設定預期行為時,會遵照以下的形式:
呼
叫次數(mock物件).方法(參數);
inSequence(sequence名稱);
when(狀態機.is(狀態));
will(動作);
then(狀態機.is(狀態));
inSequence(sequence名稱);
when(狀態機.is(狀態));
will(動作);
then(狀態機.is(狀態));
呼 叫次數如oneOf、atLeast.of等,例如至少呼叫過一次mockDAO的get,則可以這麼撰寫:
atLeast(1).of(mockDAO).get();
方法呼叫過後, inSequence、when、will、then都是可選擇性指定,以下例而言:
context.checking(new Expectations() {{
oneOf(mockDAO).get();
will(returnValue(Arrays.asList(bookmark1)));
}});
表示預期mockDAO會被呼叫get()一次,傳回包括bookmark1的List。在下面的程式碼中:
context.checking(new Expectations() {{
oneOf(mockDAO).get();
will(returnValue(Arrays.asList(bookmark1)));
oneOf(mockDAO).add(bookmark2);
}});
呼叫get()、add()的順序並不要緊,如果你希望呼叫的順序一定是get()或add(),則可以設定Sequence,例如:
final Sequence addDifferentBookmark =
context.sequence("addDifferentBookmark");
context.checking(new Expectations() {{
oneOf(mockDAO).get();
inSequence(addDifferentBookmark);
will(returnValue(Arrays.asList(bookmark1)));
oneOf(mockDAO).add(bookmark2);
inSequence(addDifferentBookmark);
}});
如果必須根據狀態而有對應的預期行為或切換狀態,則可以設定狀態機, 以 JMock 官網文件上的例子來說:
final States pen =
context.states("pen").startsAs("up");
...
oneOf(turtle).penDown();
then(pen.is("down")); // penDown()呼叫過後狀態為down
oneOf(turtle).forward(10);
when(pen.is("down")); // 只有在狀態為down時才呼叫forward()
oneOf(turtle).turn(90);
when(pen.is("down"));
oneOf(turtle).forward(10);
when(pen.is("down"));
oneOf(turtle).penUp();
then(pen.is("up"));
...
oneOf(turtle).penDown();
then(pen.is("down")); // penDown()呼叫過後狀態為down
oneOf(turtle).forward(10);
when(pen.is("down")); // 只有在狀態為down時才呼叫forward()
oneOf(turtle).turn(90);
when(pen.is("down"));
oneOf(turtle).forward(10);
when(pen.is("down"));
oneOf(turtle).penUp();
then(pen.is("up"));
如果搭配JUnit 4,可以如下撰寫:
package test.cc.openhome;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.Sequence;
import org.jmock.integration.junit4.JMock;
import org.jmock.integration.junit4.JUnit4Mockery;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Arrays;
import cc.openhome.dao.BookmarkDAO;
import cc.openhome.model.Bookmark;
import cc.openhome.model.BookmarkService;
@RunWith(JMock.class)
public class BookmarkServiceTest {
private Bookmark bookmark1;
private Bookmark bookmark2;
private BookmarkDAO mockDAO;
private BookmarkService service;
private Mockery context;
@Before
public void setUp() {
bookmark1 = new Bookmark("testURL1", "testTitle1", "testCategory1");
bookmark2 = new Bookmark("testURL2", "testTitle2", "testCategory2");
context = new JUnit4Mockery();
// 建立Mock物件
mockDAO = context.mock(BookmarkDAO.class);
service = new BookmarkService(mockDAO);
}
@Test
public void testAddSameBookmark() {
// 設定預期行為
context.checking(new Expectations() {{
// 會呼叫DAO的get()
oneOf(mockDAO).get();
// 預期傳回值
will(returnValue(Arrays.asList(bookmark1)));
}});
service.add(bookmark1);
}
@Test
public void testAddDifferentBookmark() {
final Sequence addDifferentBookmark =
context.sequence("addDifferentBookmark");
context.checking(new Expectations() {{
oneOf(mockDAO).get();
inSequence(addDifferentBookmark);
will(returnValue(Arrays.asList(bookmark1)));
oneOf(mockDAO).add(bookmark2);
inSequence(addDifferentBookmark);
}});
service.add(bookmark2);
}
}
使用JMock作 為Runner,並搭配JUnit4Mockey,會在測試方法執行過後自動驗證行為是否正確。
JMock 官方網站提供了不少的文件可作為參考。