1、為什么要寫(xiě)單測(cè)?
單測(cè)即單元測(cè)試(Unit Test),是對(duì)軟件的基本組成單元進(jìn)行的測(cè)試,比如函數(shù)、過(guò)程或者類(lèi)的方法。其意義是:
- 功能自測(cè),發(fā)現(xiàn)功能缺陷
- 自我Code Review
- 測(cè)試驅(qū)動(dòng)開(kāi)發(fā)
- 促進(jìn)代碼重構(gòu)并提升代碼質(zhì)量
1.1、代碼覆蓋率
單測(cè)質(zhì)量最直接表現(xiàn)的指標(biāo)就是代碼覆蓋率,分為語(yǔ)句覆蓋(Statement coverage)、分支覆蓋(Branch coverage)、條件覆蓋(Condition converage)、路徑覆蓋(Path coverage)
1.2、單元測(cè)試 VS 集成測(cè)試
系統(tǒng)上線前都會(huì)做回歸測(cè)試和集成測(cè)試,但為什么還要加單元測(cè)試呢?
指標(biāo)對(duì)象 | 單元測(cè)試 | 集成測(cè)試 |
---|---|---|
測(cè)試對(duì)象 | 程序單元 | 模塊組合 |
測(cè)試方法 | 白盒測(cè)試 | 黑盒測(cè)試 |
測(cè)試時(shí)間 | 開(kāi)發(fā)階段 | 集成階段 |
測(cè)試內(nèi)容 | 代碼邏輯 | 接口功能 |
測(cè)試粒度 | 較細(xì)粒度 | 較粗粒度 |
2、如何寫(xiě)好單測(cè)?
2.1、單測(cè)規(guī)約
可以參考阿里巴巴 的Java開(kāi)發(fā)規(guī)范,以下幾點(diǎn)在單測(cè)中要特別關(guān)注:
- 【強(qiáng)制】好的單測(cè)必須遵守AIR原則。說(shuō)明:?jiǎn)卧獪y(cè)試在線上運(yùn)行時(shí),像空氣一樣感覺(jué)不到,但在測(cè)試的質(zhì)量保證上,卻是非常關(guān)鍵的。好的單元測(cè)試宏觀上說(shuō),具體有自動(dòng)化(Automatic)、獨(dú)立性(Idependent)、可重復(fù)執(zhí)行(Repeatable)的特點(diǎn)。
- 【強(qiáng)制】單元測(cè)試應(yīng)該是全自動(dòng)執(zhí)行的,并且非交互式的。測(cè)試用例通常是被定期執(zhí)行的,執(zhí)行過(guò)程必須完全自動(dòng)化才有意義。輸出結(jié)果需要人工檢查的測(cè)試不是一個(gè)好的單元測(cè)試。單元測(cè)試中不準(zhǔn)使用System.Out來(lái)進(jìn)行人肉驗(yàn)證,必須使用Assert來(lái)驗(yàn)證。
- 【強(qiáng)制】單元測(cè)試是可以重復(fù)執(zhí)行的,不能受到外界環(huán)境的影響。
- 【推薦】編寫(xiě)單元測(cè)試代碼遵守BCDE原則,以保證被測(cè)試模塊的交付質(zhì)量。
- B: Border,邊界值測(cè)試,包括循環(huán)邊界、特殊取值、特殊時(shí)間點(diǎn)、數(shù)據(jù)順序等。
- C: Correct,正確的輸入,并得到預(yù)期的結(jié)果。
- D: Design,與設(shè)計(jì)文檔相結(jié)合,來(lái)編寫(xiě)單元測(cè)試。
- E: Error,強(qiáng)制錯(cuò)誤信息輸入(如:非法數(shù)據(jù)、 異常流程、業(yè)務(wù)允許外等),并得到預(yù)期的結(jié)果
2.2、一把好工具
寫(xiě)單側(cè)首先要有好的單測(cè)工具,常用工具: Mockito、PowerMock、 EasyMock、JMockito等,Mock可以解決:
- 解除對(duì)外部服務(wù)依賴(lài)
- 減少全鏈路測(cè)試的數(shù)據(jù)準(zhǔn)備
- 模擬一些非正常的流程
- 不用加載項(xiàng)目環(huán)境配置
- 實(shí)現(xiàn)模塊之間的并行開(kāi)發(fā)
2.3、編寫(xiě)單元測(cè)試
可以把單元測(cè)試編寫(xiě)流程分為四大步驟,八大操作。
定義對(duì)象階段
定義測(cè)試對(duì)象
在編寫(xiě)單元測(cè)試時(shí),首先需要定義被測(cè)對(duì)象,或直接初始化、或通過(guò)Spy包裝…實(shí)例化。
- 直接構(gòu)建對(duì)象
UserService userService = new UserService(); - 利用Mockito.spy方法
UserService userService = Mockito.spy(new UserService());
UserService userService = Mockito.spy(UserService.class); - 利用@Spy注解
@RunWith(PowerMockRunner.class)
public class CompanyServiceTest {
@Spy
private UserService userService = new UserService();
}
- 利用@InjectMocks注解
@RunWith(PowerMockRunner.class)
public class UserServiceTest {
@InjectMocks
private UserService userService;
}
模擬依賴(lài)對(duì)象
在編寫(xiě)單元測(cè)試用例時(shí),需要模擬各種依賴(lài)對(duì)象——類(lèi)成員、方法參數(shù)和方法返回值。
- 直接構(gòu)建對(duì)象
UserDO user = new User(1L, “test”);
List<Long> userIdList = Arrays.asList(1L, 2L, 3L);
- 反序列化對(duì)象
UserDO user = JSON.parseObject(text, UserDO.class);
List<UserDO> userList = JSON.parseArray(text, UserDO.class);
Map<Long, UserDO> userMap = JSON.parseObject(text, new TypeReference<Map<Long, UserDO>>() {});
- 利用Mockito.mock方法
MockClass mockClass = Mockito.mock(MockClass.class);
List<Long> userIdList = (List<Long>)Mockito.mock(List.class);
- 利用@Mock注解 @Mock
private UserDAO userDAO; - 利用Mockito.spy方法
UserService userService = Mockito.spy(new UserService()); - 利用@Spy注解
@Spy
private UserService userService = new UserService(); // 必須初始化
注入依賴(lài)對(duì)象
在編寫(xiě)單元測(cè)試用例時(shí),需要模擬各種依賴(lài)對(duì)象——類(lèi)成員、方法參數(shù)和方法返回值。
- 利用Setter方法注入
userService.setMaxCount(100);
userService.setUserDAO(userDAO); - 利用ReflectionTestUtils.setField方法注入
ReflectionTestUtils.setField(userService, “maxCount”, 100);
ReflectionTestUtils.setField(userService, “userDAO”, userDAO); - 利用Whitebox.setInternalState方法注入
Whitebox.setInternalState(userService, “maxCount”, 100);
Whitebox.setInternalState(userService, “userDAO”, userDAO); - 利用@InjectMocks注解注入
@Mock
private UserDAO userDAO; @InjectMocks
private UserService userService; - 設(shè)置靜態(tài)常量字段值
FieldHelper.setStaticFinalField(UserService.class, “l(fā)og”, log);
舉個(gè)例子
@RunWith(PowerMockRunner.class)
public class UserSericeTest {
// 模擬依賴(lài)對(duì)象(類(lèi)成員)
@Mock
private UserDAO userDAO;
// 定義測(cè)試對(duì)象
@InjectMocks
private UserService userService;
@Before
public void before() {
// 輸入依賴(lài)對(duì)象(類(lèi)成員)
Whitebox.setInternalState(userService, "canModify", true);
}
}
模擬方法階段
在編寫(xiě)單元測(cè)試用例時(shí),需要模擬方法指定參數(shù)并返回指定值。
舉個(gè)例子
模擬依賴(lài)對(duì)象的數(shù)據(jù)可以自己構(gòu)建、Mock或者可以從資源文件里讀取。
@Test
public void testCreateUserWithCreate {
// 模擬依賴(lài)對(duì)象方法:getIdByName
Mockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());
Long mockUserId = 2L;
// 從資源文件加載
String jsonData = ResourceHelper.getResouceAsString(getClass(), path + "/data.json")
UserDO userDO = JSON.parseObject(jsonData, UserDO.class);
//
Long userId = userService.createUser(userDO);
Assert.assertEquals("用戶標(biāo)識(shí)不一致", mockUserId, userId);
// 驗(yàn)證依賴(lài)方法
Mockito.verify(userDAO).getIdByName(userDO.getUserName());
}
調(diào)用方法階段
驗(yàn)證方法階段
- 驗(yàn)證依賴(lài)方法
- 驗(yàn)證數(shù)據(jù)對(duì)象
- 驗(yàn)證依賴(lài)對(duì)象
3、 如何做的更好?
寫(xiě)代碼不只是亂寫(xiě)一通,覆蓋率上去了就可以了,它本質(zhì)也是代碼,也要符合代碼規(guī)約。一個(gè)好的單測(cè)命名可以幫助理清單測(cè)Case 也可以便于他人Review。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-740358.html
3.1、規(guī)范命名
- 測(cè)試類(lèi)命名
按照行業(yè)慣例,測(cè)試類(lèi)的命名應(yīng)以被測(cè)試類(lèi)名開(kāi)頭并以Test結(jié)尾。 比如:UserServiceTest(用戶服務(wù)測(cè)試類(lèi)) - 測(cè)試方法命名
按照行業(yè)規(guī)范,測(cè)試方法命名應(yīng)以test開(kāi)頭并以被測(cè)試方法結(jié)尾。 a) 按照結(jié)果命名
? testBatchCreateWithSuccess(測(cè)試:批量創(chuàng)建-成功)
? testBatchCreateWithFailure(測(cè)試:批量創(chuàng)建-失敗)
? testBatchCreateWithException(測(cè)試:批量創(chuàng)建-異常)
b) 按照參數(shù)命名
? testBatchCreateWithListNull(測(cè)試:批量創(chuàng)建-列表為NULL)
? testBatchCreateWithListEmpty(測(cè)試:批量創(chuàng)建-列表為空)
? testBatchCreateWithListNotEmpty(測(cè)試:批量創(chuàng)建-列表不為空)
c) 按照意圖命名
? testBatchCreateWithNormal(測(cè)試:批量創(chuàng)建-正常)
? testBatchCreateWithGray(測(cè)試:批量創(chuàng)建-灰度)
? testBatchCreateWithException(測(cè)試:批量創(chuàng)建-異常) - 測(cè)試資源命名-語(yǔ)義化 建議優(yōu)先使用這些參數(shù)和變量的名稱(chēng),并加后綴“.json”標(biāo)識(shí)文件格式。 比如:userCreateList.json
3.2、各環(huán)節(jié)做好驗(yàn)證
- 不驗(yàn)證返回值 不驗(yàn)證返回值,怎么能保證方法返回了正確值?
- 不驗(yàn)證方法調(diào)用 不驗(yàn)證方法調(diào)用,怎么能保方法被正確的調(diào)用?
? 不驗(yàn)證方法參數(shù) 不驗(yàn)證方法參數(shù),怎么能保證傳遞數(shù)據(jù)的正確性?
? 不驗(yàn)證異常信息 不驗(yàn)證異常信息,怎么能保證拋出異常的正確性?
4、常見(jiàn)單測(cè)問(wèn)題
在編寫(xiě)單元測(cè)試用例時(shí),或多或少會(huì)遇到一些問(wèn)題,大多數(shù)是由于對(duì)測(cè)試框架特性不熟悉導(dǎo)致,比如:文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-740358.html
- Mockito不支持對(duì)靜態(tài)方法、構(gòu)造方法、final方法、私有方法的模擬,應(yīng)該使用PowerMock功能;
- Mockito的any相關(guān)的參數(shù)匹配方法并不支持可空參數(shù)和空參數(shù),應(yīng)該使用nullable方法;
- 未Mock方法或Mock方法參數(shù)不匹配時(shí),會(huì)返回默認(rèn)值(基礎(chǔ)類(lèi)型為0,對(duì)象類(lèi)型為null);
- 采用Mockito的參數(shù)匹配方法時(shí),其它參數(shù)不能直接用常量或變量,應(yīng)該使用Mockito的eq方法;
- 采用Argument的captor方法時(shí),其它參數(shù)不能直接用常量或變量,應(yīng)該使用Mockito的eq方法;
- 使用when-then語(yǔ)句模擬Spy對(duì)象方法會(huì)先執(zhí)行真實(shí)方法,應(yīng)該使用do-when語(yǔ)句;
- PowerMock對(duì)靜態(tài)方法、構(gòu)造方法、final方法、私有方法的模擬需要把對(duì)應(yīng)的類(lèi)添加到
@PrepareForTest注解中; - PowerMock模擬JDK的靜態(tài)方法、構(gòu)造方法、final方法、私有方法時(shí),需要把使用這些方法的類(lèi)
加入到@PrepareForTest注解中,但會(huì)導(dǎo)致單元測(cè)試覆蓋率不被統(tǒng)計(jì); - PowerMock使用自定義的類(lèi)加載器來(lái)加載類(lèi),可能導(dǎo)致系統(tǒng)類(lèi)加載器認(rèn)為有類(lèi)型轉(zhuǎn)化問(wèn)題;需要加上@PowerMockIgnore({“javax.crypto.*”})注解。
到了這里,關(guān)于如何寫(xiě)好單測(cè)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!