剛進公司,寫兩個星期的單元測試。公司主要用Jmockit ,感覺用起來還不錯,於是乎,把一些寫單測的時候,常遇到的寫法寫了下來。
凡是單元測試,都需要做以下幾件事情
1, 明確要測試的類,和方法
2, 明確測試的實現,需要哪些依賴
3, 針對這些外部依賴 進行 mock
4, 驗證是否根據自己的輸入取得自己的輸出
那麼在JMOCKIT中。
做到1 這點,使用@Tested這個 註解
做到2這點,使用@injected 這個註解
做到3這點,可以使用 Expectations 和 NonStrictExpectations
做到4這點,則可以使用junit 的Assert 或者 Verifications
那麼常見的寫法就貼個出來:
@Tested
@Mocked(methods = "updateEditNum")
private CustomerServiceImpl customerServiceImpl;
@Injectable
private CustomerDao customerDao;
@Injectable
private CustOptionalDao custOptionalDao;
@Injectable
private CustAccountInfoDao custAccountInfoDao;
@Injectable
private CustContactPhoneDao custContactPhoneDao;
@Injectable
private CustContactDao custContactDao;
@Injectable
private CustRecipDao custRecipDao;
@Injectable
private CustAutoAuditInfoDao custAutoAuditInfoDao;
// 需要修改monogo數據
@Test
public void testUpdateCustomer_need_update_monogo() {
CustomerVO customerVO = buildCustomer();
nonstrictExpectations();
customerServiceImpl.updateCustomer(customerVO);
new Verifications() {
{
mongoUpdateService.modSaleData(anyLong, anyString);
times = 1;
mongoCoreWordFacade.modCoreWordData(anyLong, anyString, anyLong);
times = 1;
}
};
}
private void nonstrictExpectations() {
new NonStrictExpectations() {
{
customerServiceImpl.updateEditNum((CustomerVO) any, (Map<String, Short>) any);
result = null;
customerDao.findById(anyLong);
Customer customer = new Customer();
customer.setFullName("unit test fullname change");
customer.setStat1(StatMachineService.STAT1_IN_FOLLOWING_04);
result = customer;
custContactDao.saveOrUpdate((CustContact) any);
CustContact c = new CustContact();
c.setContactId(11l);
result = c;
custContactPhoneDao.saveOrUpdate((CustContactPhone) any);
CustContactPhone p = new CustContactPhone();
p.setPhoneId(22l);
result = p;
}
};
}
最常用的有了,但單元測試可不是那麼簡單。我這邊碰到第一個情景是,測試一個service.doXXX() 。
但這個 doXXX()其實裏面就調用了2個private方法,那麼其實問題就轉變成分別測試這兩個private 方法:
主要使用的是:Deencapsulation
// 測試私有方法獲取該類型的para數據字典 (不傳 單位崗位ID)
@Test
public void testFindByTypeAndPosIdWithCache_NO_POS() {
new NonStrictExpectations() {
{
paraDao.findByTypeAndPosid(ParaTypeEnum.auditAbandReason);
List<Para> pararet = new ArrayList<Para>();
Para p = new Para();
p.setDelFlag((short) 0);
p.setShare((short) 0);
p.setName("這個客戶不是火星來的麼,所以不能審覈通過的!");
pararet.add(p);
returns(pararet);
}
};
List<Para> rets = Deencapsulation.invoke(paramServiceImpl, "findByTypeAndPosIdWithCache",
ParaTypeEnum.auditAbandReason, new Long[] {});
Assert.assertTrue(rets.get(0).getName().equals("這個客戶不是火星來的麼,所以不能審覈通過的!"));
new Verifications() {
{
calloutMemcachedClient.get(anyString);
times = 1;
calloutMemcachedClient.set(anyString, anyInt, anyString);
times = 1;
}
};
}
但是查看源碼 發現 繼承於 Invocations的 Expectations 和 Verifications 也都是有invoke 的反射方法已經提供好了。
第二種情況來了,我要測試的@Tested 類,裏面本身也有需要mock 的方法,但只 mock 其中一個方法就夠了
@Tested
@Mocked(methods = "updateEditNum")
private CustomerServiceImpl customerServiceImpl;
第三種情況來了,有個實現的地方需要mock ,但這個實現是這樣的 A.doAA().doBB().doCC()
難道要先mock A.doAA(),然後在mock這個返回值的doBB() 。。。。 太麻煩了
來看下:
// 測試用戶信息比較全,並且需要優先審覈,會更新優先統計數
@Test
public void testAddCustomer(@Cascading final ServiceLocator ServiceLocator) {
long custId = 38888888l;
CustomerVO vo = new CustomerVO();
vo.setFullName("單元測試用戶");
vo.setCustId(custId);
。。。。。
new NonStrictExpectations() {
{
ServiceLocator.getInstance().getBean("saleSetFacade");
saleSetFacade = new SaleSetFacade() {
public SaleSet findSaleSetById(Long id) {
return null;
}
。。。。。。
};
result = saleSetFacade;
}
};