近期在專案中遇到單元測試覆蓋率不足的問題,剛好學習了 SCORE 法則,便嘗試用此框架梳理問題背景、衝突、方案選擇、結果與評價,分享給大家參考。
專案要求程式碼必須有單元測試,並以覆蓋率作為考核指標,開發人員需達到規定的最低門檻。
專案採用模組化結構:將介面與實體類獨立為 core 模組,業務邏輯與持久層實現放在 service 模組(依賴 core)。由於 core 僅包含介面與實體,原本認為編寫單元測試價值不大(僅測試序列化或 JSR303 校驗),且 service 測試時已間接調用 core 程式碼,理應無需額外測試。
然而,代碼掃描工具按單一 Java 工程統計覆蓋率,無法將多模組視為整體。若只在 service 編寫測試,core 的程式碼行數不計入統計,導致整體覆蓋率難以達標,除非投入大量時間補寫測試。
針對實體類(主要為 getter/setter)的測試,比較三種方案:
最終選擇方案二這種「偷懶」做法:實現一個通用測試類,透過反射掃描指定包下所有實體類,自動執行序列化與反序列化,枚舉類型單獨處理。達到隨實體增減自動適配,極大節省時間。
在遵守公司規範的前提下,尋找高效實現方式,「偷懶」非逃避責任,而是將精力集中在核心業務邏輯測試上,讓工作更輕鬆愉快。
public static Set<Class<?>> getClasses(String pack) {
Set<Class<?>> classes = new LinkedHashSet<>();
boolean recursive = true;
String packageDirName = pack.replace('.', '/');
Enumeration<URL> dirs;
try {
dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
while (dirs.hasMoreElements()) {
URL url = dirs.nextElement();
if ("file".equals(url.getProtocol())) {
String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
findAndAddClassesInPackageByFile(pack, filePath, recursive, classes);
}
}
} catch (IOException e) {
e.printStackTrace();
}
return classes;
}
private boolean isEnumCodeNameByObj(Class<?> classObj) {
Class<?>[] interfaces = classObj.getInterfaces();
if (interfaces == null || interfaces.length == 0) return false;
return Arrays.stream(interfaces)
.anyMatch(i -> i.getName().contains("IEnumCodeName"));
}
private void testEnum(Class<?> classObj) throws Exception {
EnumHelper.IEnumCodeName enumCodeName = ClassloadHelper.getFirstEnumByClass(classObj);
if (enumCodeName != null) {
classObj.getMethod("getByCode", int.class).invoke(null, enumCodeName.getCode());
classObj.getMethod("getByName", String.class).invoke(null, enumCodeName.getName());
}
}
private void testObj(Class<?> classObj) throws Exception {
Object obj = classObj.newInstance();
String jsonString = JsonHelper.toJsonString(obj);
Object objNew = JsonHelper.json2Object(jsonString, classObj);
Assert.isTrue(objNew != null);
Assert.isTrue(!StringUtils.isBlank(jsonString));
}
@Test
public void testPojo() throws Exception {
Set<Class<?>> classes = ClassloadHelper.getClasses("xxx.core.model");
if (classes != null) {
for (Class<?> clazz : classes) {
try {
if (isEnumCodeNameByObj(clazz)) testEnum(clazz);
else testObj(clazz);
} catch (Exception e) { e.printStackTrace(); }
}
}
}
