單測的本質
- 是要去發現代碼中的問題。
現實中,寫單測環節中可能存在的問題
效率方面
- 手動代碼低效,特別是代碼重構的時候
- 有些情況對象稍微大一些,我們就得不停的手動set,耗費大量時間
質量方面
- 應付:為了單測而單測
- 單測不嚴謹,等於沒有
對標
該項目對標JUnitGenerator V2.0
優劣勢
- 優勢
- 較JUnitGenerator V2.0顯著提升研發效率。生成代碼的調用,和猜測的驗證
本插件的特色
- 自動的生成方法的調用,幫你自動的創建變量、方法的的參數,已隨機值的方式生成,無需手動(set)
- 針對調用完成的方法,根據是否有返回值以及變量自動生成Assert的代碼,選擇性的使用
目前支持的功能
- 針對於單純的javaBean的code生成,適用於util類、DDD中的領域層等等
- mock類
- 生成Jmockit風格的單測代碼
未來要支持的功能
- 更好的去猜測如何設置調用的case、需要多少case以及如何Assert
- 支持更多風格的單測生成,比如Spock等
插件的思路
- 方向1:將人工的單測經驗,通過java代碼來實現(目前的方案)
- 方向2:通過優質的單測例子,使用機器學習手段,不斷地訓練,生產單測(可能是未來的方案)
過程中的問題思考
如何指望代碼不嚴謹的人,單測寫的嚴禁?
矛盾:代碼寫的不好,單測的代碼寫的也可能會不好,所以需要通過代碼按時,足夠充分的Assert幫助提升質量
如何區分單測的代碼風格?比如普通的Junit or Jmockit
- 標識
- 文件名結尾,一般的以Service、ServiceImpl、Application等結尾的一般都是需要mock的,故使用mock風格,但是具體哪種mock的組件,后面會提供配置的頁面
如何寫調用的case?
- 調用參數生成
- 基本類型 隨機生成
- 引用類型 隨機生成
- 支持代碼隨機過程可視化,這樣可以隨意修改想要的值
- 方法調用
- 如何做多case調用,去猜測真正的調用case
- 私有方法處理
- 通過反射將access變成true,調用
- 常規方法處理
如何寫斷言
- 根據返回值類型
- void
- 方法沒有入參 無需斷言
- 方法有入參 驗證當前對象屬性的改變
- boolean 驗證返回值
- 其他基本類型 驗證返回值
- 引用類型,驗證非空,驗證返回值屬性
- void
想法
- 業務驅動單元測試,業務語言轉換成測試用例,測試用例轉換成代碼
- 將基本的api開放,通過集成,使用者根據自己的情況編寫模板
- 如何做成一款產品,賣出去
插件獲取
- 源代碼生成插件(推薦):
- 下載代碼:git clone git地址(下面有)
- 進入根目錄:cd unit-test
- 執行命令:./generatePlugin.sh
- 得到結果:
```
請確保已經安裝了Gradle
Deprecated Gradle features were used in this build, making it incompatible with Gradle 7.0.
Use '--warning-mode all' to show the individual deprecation warnings.
See https://docs.gradle.org/6.0.1/userguide/command_line_interface.html#sec:command_line_warnings
BUILD SUCCESSFUL in 4s
10 actionable tasks: 1 executed, 9 up-to-date
cp: build/distributions/ is a directory (not copied).
插件生成成功:unit.test-0.0.2.zip
```
- 直接下載(可能會有版本問題,導致不兼容,用下面的):https://plugins.jetbrains.com/plugin/download?pluginId=org.xiaogang.unit.test&version=0.0.2
插件安裝
- idea本地安裝就可以
如何使用
- 添加pom:配套的pom:主要用來配合生成單測的代碼
<dependency>
<groupId>com.alibaba.cro</groupId>
<artifactId>unit-test-api</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
- 在要測試的代碼中,右鍵,選擇【generate...】,然后選擇【Auto Generation Test Code】,然后會彈出一個對話框,選擇要生成單測的方法
業務代碼:
public Exam paramStr(String str, Exam e) {
if (viewAnswer == null) {
return null;
}
return this;
}
public boolean canViewAnswer() {
if (viewAnswer == null) {
return false;
}
return viewAnswer == 1;
}
生成后的單測如下:
@Test
public void testParamStr() {
// Initialize params of the method;
String str = ObjectInit.random(String.class);
Exam e = ObjectInit.random(Exam.class);
Exam invokeResult = exam.paramStr(str, e);
// Write the Assert code
//Assert.assertEquals(expected, actual);
//Assert.assertEquals(expected, invokeResult);
}
@Test
public void testCanViewAnswer() {
Boolean invokeResult = exam.canViewAnswer();
// Write the Assert code
//Assert.assertEquals(expected, actual);
//Assert.assertEquals(expected, invokeResult);
}
- 針對於需要mock的代碼,比如xxService
業務代碼:對第三方的api調用是需要mock的
public boolean canGoWorld(String batchId) {
SimpleResultDTO<SimpleBatchInfoDTO> resultDTO = inspectHsfService.searchBatch(batchId);
if (!resultDTO.isSuccess() || resultDTO.getData() == null) {
log.error("batchId [" + batchId + "] searchBatch fail,detail:" + JSON
.toJSONString(resultDTO));
throw new RuntimeException("調用RCP異常");
}
if (StringUtils.equals(BatchStatusEnum.NO_COMPLETE.name(), resultDTO.getData().getStatus())) {
return true;
}
return false;
}
單測代碼: 這里會把需要mock的數據,猜測出來,然后開發可以自由的去調整,目前是基於Jmockit來實現的
@Test
public void testCanGoWorld() {
// Initialize params of the method;
String batchId = ObjectInit.random(String.class);
new Expectations() {{
//inspectHsfService.searchBatch(batchId);
//result = ObjectInit.random(SimpleResultDTO<SimpleBatchInfoDTO>.class);// mock的返回值,這里可以手動的修改,ObjectInit是工具類,隨機初始化bean
}};
Boolean invokeResult = taskService.canGoWorld(batchId);// 如果有調用的返回值,下面的Assert也會提示要驗證這個值
//new Verifications() {{
//}};
// Write the Assert code
//Assert.assertEquals(expected, actual);
//Assert.assertEquals(expected, invokeResult);
}
源碼
- gitlab: git@gitlab.alibaba-inc.com:shishang.fxg/unit-test.git
- github: git@github.com:xiaogangfan/unit-test.git