jMock用法簡介
總體上來說,jMock 是一個(gè)輕量級的模擬對象技術(shù)的實(shí)現(xiàn)。它具有以下特點(diǎn):
1.可以用簡單易行的方法定義模擬對象,無需破壞本來的代碼結(jié)構(gòu)表;
2.可以定義對象之間的交互,從而增強(qiáng)測試的穩(wěn)定性;
3.可以集成到測試框架;
4.易擴(kuò)充;
使用 jMock 模擬對象
我們首先必須引入 jMock 的類,定義我們的測試類,創(chuàng)建一個(gè) Mockery 的對象用來代表上下文。上下文可以模擬出對象和對象的輸出,并且還可以檢測應(yīng)用是否合法。
@SuppressWarnings("unchecked")

public class BookListTest
{

private final Mockery context = new JUnit4Mockery()
{

{
// 聲明針對類進(jìn)行mock,針對接口則會采用動態(tài)代理,不需要聲明
setImposteriser(ClassImposteriser.INSTANCE);
}
};
}


context 對象便可以用來創(chuàng)建Mock對象。
接下來的例子,我們模擬一個(gè)ServiceCall對象,我們以它的Map call(String target, Map dataMap)為例,針對此方法,設(shè)定預(yù)期值。然后我們在執(zhí)行用例的時(shí)候調(diào)用此方法,便可以得到預(yù)期值。
@SuppressWarnings("unchecked")

public class BookListTest
{

private final Mockery context = new JUnit4Mockery()
{

{
setImposteriser(ClassImposteriser.INSTANCE);
}
};

@Test

public void testExecuteNormal() throws Exception
{
final ServiceCall sCall = context.mock(ServiceCall.class);

context.checking(new Expectations()
{

{
one(sCall).call(JMockService.queryDtlInfo, null);
// 構(gòu)建預(yù)期結(jié)果
Map ret = new HashMap();
ret.put("OrderId", "9800000000");
ret.put("Data", new ArrayList());
// 設(shè)定預(yù)期值
will(returnValue(ret));
// 第二次被調(diào)用時(shí),返回null
one(sCall).call(JMockService.queryDtlInfo, new HashMap());
will(returnValue(null));
}
});

BookList bListAction = new BookList();
bListAction.setName("jnbzwm");
// 設(shè)定ServiceCall對象為Mock對象
bListAction.setServiceCall(sCall);

// 執(zhí)行Action方法
bListAction.execute();

Assert.assertEquals("9800000000", bListAction.getOrderId());
Assert.assertEquals(0, bListAction.getDataList().size());
}


校驗(yàn)expectations中的規(guī)則
使用jMock時(shí),一般會通過如下代碼指定expectations:

private final Mockery context = new JUnit4Mockery()
{

{
setImposteriser(ClassImposteriser.INSTANCE);
}
};

@Test

public void testExecuteNormal() throws Exception
{
final ServiceCall sCall = context.mock(ServiceCall.class);

context.checking(new Expectations()
{

{
one(sCall).call(JMockService.queryDtlInfo, null);
// 構(gòu)建預(yù)期結(jié)果
Map ret = new HashMap();
ret.put("OrderId", "9800000000");
ret.put("Data", new ArrayList());
// 設(shè)定預(yù)期值
will(returnValue(ret));
// 第二次被調(diào)用時(shí),返回null
one(sCall).call(JMockService.queryDtlInfo, new HashMap());
will(returnValue(null));
}
});
.
}
}

為了校驗(yàn)expectations中的規(guī)則是否都滿足,可以在測試完成后通過增加 context.assertIsSatisfied()方法來驗(yàn)證expectations是否滿足。
如下代碼:
@Test

public void testExecuteNormal() throws Exception
{
final ServiceCall sCall = context.mock(ServiceCall.class);

context.checking(new Expectations()
{

{
one(sCall).call(JMockService.queryDtlInfo, null);
// 構(gòu)建預(yù)期結(jié)果
Map ret = new HashMap();
ret.put("OrderId", "9800000000");
ret.put("Data", new ArrayList());
// 設(shè)定預(yù)期值
will(returnValue(ret));
// 第二次被調(diào)用時(shí),返回null
one(sCall).call(JMockService.queryDtlInfo, new HashMap());
will(returnValue(null));
}
});

BookList bListAction = new BookList();
bListAction.setName("jnbzwm");
// 設(shè)定ServiceCall對象為Mock對象
bListAction.setUpfServiceCall(sCall);

// 執(zhí)行Action方法
bListAction.execute();

Assert.assertEquals("9800000000", bListAction.getOrderId());
Assert.assertEquals(0, bListAction.getDataList().size());
context.assertIsSatisfied();
}

由于我定義了兩條規(guī)則,而第二條并未調(diào)用,所以此用例不會通過。
同一個(gè)方法連續(xù)調(diào)用時(shí)返回不同的值
有兩種方法,第一種就是直接通過多次調(diào)用 will(returnValue(X))來指定。如:
@Test

public void testExecuteNormal() throws Exception
{
final ServiceCall sCall = context.mock(ServiceCall.class);

context.checking(new Expectations()
{

{
one(sCall).call(JMockService.queryDtlInfo, null);
will(returnValue(0));

// 第二次被調(diào)用時(shí),返回1
one(sCall).call(JMockService.queryDtlInfo, null);
will(returnValue(1));

// 第三次被調(diào)用時(shí),返回2
one(sCall).call(JMockService.queryDtlInfo, null);
will(returnValue(2));
}
});

}

然而第一種方法會增加維護(hù)成本,且缺乏可控性。jMock提供了第二種方法,即通過onConsecutiveCalls的action來實(shí)現(xiàn)返回不同的返回值。如:
@Test

public void testExecuteNormal() throws Exception
{
final ServiceCall sCall = context.mock(ServiceCall.class);

context.checking(new Expectations()
{

{
atLeast(1).of (sCall).call(JMockService.queryDtlInfo, null);
will(onConsecutiveCalls( returnValue(0), returnValue(1), returnValue(2)));
}
});

}


指定mock的方法拋出異常
在will方法中直接使用throwException的action。參考如下語法:
one(sCall).call(JMockService.queryDtlInfo, null);
// 設(shè)定預(yù)期值,拋出異常
will(throwException(new BusinessException("~", "name can't empty.")));
結(jié)合測試異常一起使用,代碼如下:
@Test(expected=BusinessException.class)

public void testExecuteNormal() throws Exception
{
final ServiceCall sCall = context.mock(ServiceCall.class);

context.checking(new Expectations()
{

{
one(sCall).call(JMockService.queryDtlInfo, null);
// 構(gòu)建預(yù)期結(jié)果
Map ret = new HashMap();
ret.put("OrderId", "9800000000");
ret.put("Data", new ArrayList());
// 設(shè)定預(yù)期值
will(throwException(new BusinessException("~", "name can't empty.")));

// 第二次被調(diào)用時(shí),返回null
one(sCall).call(JMockService.queryDtlInfo, new HashMap());
will(returnValue(null));
}
});

BookList bListAction = new BookList();
bListAction.setName("");
// 設(shè)定ServiceCall對象為Mock對象
bListAction.setUpfServiceCall(sCall);

// 執(zhí)行Action方法
bListAction.execute();
}

