首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

善用 SCORE 法則複盤一次「偷懶」的單元測試實戰

     編輯:Java 綜合教學

前言

近期在專案中遇到單元測試覆蓋率不足的問題,剛好學習了 SCORE 法則,便嘗試用此框架梳理問題背景、衝突、方案選擇、結果與評價,分享給大家參考。

S - Situation(背景)

專案要求程式碼必須有單元測試,並以覆蓋率作為考核指標,開發人員需達到規定的最低門檻。

C - Conflict(衝突/問題)

專案採用模組化結構:將介面與實體類獨立為 core 模組,業務邏輯與持久層實現放在 service 模組(依賴 core)。由於 core 僅包含介面與實體,原本認為編寫單元測試價值不大(僅測試序列化或 JSR303 校驗),且 service 測試時已間接調用 core 程式碼,理應無需額外測試。

然而,代碼掃描工具按單一 Java 工程統計覆蓋率,無法將多模組視為整體。若只在 service 編寫測試,core 的程式碼行數不計入統計,導致整體覆蓋率難以達標,除非投入大量時間補寫測試。

O - Options(方案選擇)

針對實體類(主要為 getter/setter)的測試,比較三種方案:

  • 方案一:逐個實體編寫測試 — 精確控制屬性值,但需編寫大量測試,維護成本高,新增/刪除實體皆需同步修改。
  • 方案二:利用反射動態掃描實體,自動完成序列化/反序列化 — 少量程式碼覆蓋所有實體,新增實體無需修改測試;缺點是無法精確控制特定屬性賦值(特殊情況可單獨補充)。
  • 方案三:優化掃描工具 — 理論可行但難度大、靈活性低,工具規則固定,短期無法解決。

R - Result(結果)

最終選擇方案二這種「偷懶」做法:實現一個通用測試類,透過反射掃描指定包下所有實體類,自動執行序列化與反序列化,枚舉類型單獨處理。達到隨實體增減自動適配,極大節省時間。

E - Evaluation(評價)

在遵守公司規範的前提下,尋找高效實現方式,「偷懶」非逃避責任,而是將精力集中在核心業務邏輯測試上,讓工作更輕鬆愉快。

核心實現程式碼片段

1. 掃描包下所有類

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;
}

2. 判斷是否為自定義枚舉(實現 IEnumCodeName)

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"));
}

3. 枚舉與普通實體的測試邏輯

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));
}

4. 單元測試入口

@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(); }
        }
    }
}

專案結構圖 測試覆蓋率對比 實現流程圖

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved