Friday, December 14, 2007

SevenMock 介紹

在做 Unit test 的人, 大概都知道要利用 Mock Object 來避開 dependencies, 但 Mock 這東西, 自己寫又很麻煩, test case 裡面每一個 test, Mock 要的反應也不太一樣, 每個 test 寫一個 Mock 的話, 既辛苦又難看. 所以才有各大 Mock Framework, 幫忙你能輕易的建立因應每一個 test 的 mock object.

出名的有 jMock, EasyMock. 但用過後, jMock 固然看得頭昏也看不明白搞什麼, EasyMock 用起來是簡單一點, 但對於 expected data 的檢查也是十分麻煩, 尤其是我只想檢查 expected input 的某些 attributes, EasyMock 做起來十分痛苦.

後來找到一個叫 SevenMock 的 mock framework, 用起來十分簡單, expected input 的 validation 的可讀性更是非常好.

下面是一個使用例子. 假設我有一個 TradeService的 實作要測:
public class TradeServiceImpl {
private tradeDao tradeDao;
private fundService fundService;

public void setTradeDao(TradeDao tradeDao) {
this.tradeDao = tradeDao;
}

public void setFundService(FundService fundService) {
this.fundService = fundService;
}

public boolean inputTrade(Trade trade) {
if (this.fundService.isEnoughBalance(trade.getPrice().mulplity(trade.getQty()))) {
this.fundService.deductBalance(trade.getPrice().mulplity(trade.getQty()));
trade.setStatus(Trade.Status.NEW);
this.tradeDao.createTrade(trade);
return true;
)
return false;
}
}

下面便是利用 SevenMock 的測試

public class TradeServiceImplTest {
@Test
public void testFoo() {
// create test target and mocks
TradeService tradeService = new TradeServiceImpl();
MockController mockControl = new MockController();
TradeDao tradeDao = (TradeDao)mockControl.getMock(TradeDao.class);
FundService fundService= (FundService)mockControl.getMock(FundService.class);

// Inject Mock objects to class-to-be-tested
tradeService.setTradeDao(tradeDao);
tradeService.setFundService(fundService);

// Set Expected Results
mockControl.expect(new FundServiceMockImpl() {
public boolean isEnoughBalance(BigDecimal amount){
// check if incoming value is correct
assertEquals(BigDecimal.valueOf(1000*400), amount);

// preset behavior: return true
return true;
}
});

mockControl.expect(new FundServiceMockImpl() {
public void deductBalance(BigDecimal amount){
// check if incoming value is correct
assertEquals(BigDecimal.valueOf(1000*400), amount);
}
});


mockControl.expect(new TradeDaoMockImpl() {
public void createTrade(Trade trade) {
assertEquals(Trade.Status.NEW,trade.getStatus());
return result;
}
});

// Finish setting expected behavior, then run test now
Trade myTrade = new Trade();
myTrade.setPrice(BigDecimal.valueOf(1000));
myTrade.setQty(BigDecimal.valueOf(400));
myTrade.setStatus(Trade.Status.NONE);

boolean result = tradeService.inputTrade(myTrade);

// verify results
assertTrue(result);
assertEquals(Trade.Status.NEW, myTrade.getStatus());

// Mock control will verify if there is missing step in invocation
mockControl.verify();
}
}
// dummy implementation class of mocked interfaces for use of SevenMock
class TradeDaoMockImpl implements TradeDao {
// all empty methods....
}

class FundServiceMockImpl implements FundService {
// all empty methods....
}



用起來十分直觀容易明白. 其中一個缺點是要 mock 的 interface, 需要一個 implementation class 來 override 為 anonymous class. 我這裡是弄一個 empty 的 implementation (TradeDaoMockImpl 及 FundServiceMockImpl), 靠 IDE 要弄這麼一個 empty implementation 很容易. 但要是你本身就有 implementations, 只要constructor 沒有什麼特別的 initialization, 直接用你自己的 implementation 來用也無不可, 反正不會真的 invoke 到它裡面的 method.

誠意推介.

No comments: