# Common Module 公共模块测试模式

## 适用范围

`<模块组>-common` 工程中的所有类，按类型分为三种测试模式：
- **POJO / DTO / Bean 类**：纯数据对象，getter/setter 测试
- **枚举类（Enum）**：含业务方法的枚举
- **工具类（Utils）**：含静态方法的工具类

## 测试文件位置

```
<模块组>-common/src/test/java/kd/<包路径>/common/{subpackage}/XxxTest.java
```

与被测类保持相同的包路径。

---

## POJO / DTO / Bean 测试模式 {#POJO}

### 适用类型

纯数据对象类，只包含属性字段和 getter/setter 方法。
如：`InvokeResult`、`ExecuteTaskModel`、`PlanPushTransferInfo`、`DailyPlanBean` 等。

### 基类选择

| 场景 | 基类 |
|------|------|
| 纯 POJO（无框架依赖） | 无需基类，或继承 `AbstractJunitNoDependenciesResManagerTest` |
| 含 Builder 模式 | 同上 |

### 模板结构

```java
package kd.<包路径>.common.pojo;

import kd.bos.test.ext.annotaions.UnittestCaseInfo;
import org.junit.Before;
import org.junit.Test;

import static org.junit.Assert.*;

/**
 * XxxModel 单元测试
 * @author 作者
 * @date YYYY/MM/DD
 */
public class XxxModelTest {

    private XxxModel model;

    @Before
    public void setUp() {
        model = new XxxModel();
    }

    @UnittestCaseInfo(
        author = "<name> <<name>@kingdee.com>",
        title = "Test get set fieldName",
        targetClass = "kd.<包路径>.common.pojo.XxxModel",
        targetMethod = "getsetfieldname",
        lastUpdateTime = "yyyy-MM-dd HH:mm:ss",
        lastUpdateAuthor = "<name> <<name>@kingdee.com>",
        methodSignature = "public String getFieldName() / public void setFieldName(String value)",
        testPoints = {"Functionality"},
        description = "Test get set fieldName"
    )
    @Test
    public void testGetSetFieldName() {
        // step 设置属性
        String value = "testValue";
        model.setFieldName(value);

        // assert 验证 getter 返回值
        assertEquals(value, model.getFieldName());
    }

    @Test
    public void testSuccessFactory() {
        // step 测试静态工厂方法
        XxxModel result = XxxModel.success("data");

        // assert 验证工厂方法结果
        assertTrue(result.isSuccess());
        assertEquals("data", result.getData());
    }

    @Test
    public void testToString() {
        // step 设置属性
        model.setFieldName("test");

        // assert 验证 toString
        String str = model.toString();
        assertTrue(str.contains("fieldName=test"));
    }
}
```

### POJO 测试要点

1. **不需要任何 Mock** — 纯 Java 对象，直接 new 和调用
2. **每个 getter/setter 一个测试方法**
3. **静态工厂方法**（如 `success()`/`failure()`）单独测试
4. **Builder 模式**需测试链式调用和 `build()` 结果
5. **toString()** 验证包含关键属性

### Builder 模式测试示例

```java
@Test
public void testBuilder() {
    XxxModel model = new XxxModel.Builder()
        .setField1("value1")
        .setField2(100L)
        .build();

    assertEquals("value1", model.getField1());
    assertEquals(Long.valueOf(100L), model.getField2());
}
```

---

## 枚举类测试模式 {#Enum}

### 适用类型

含业务方法的枚举类（不是纯值枚举），如 `PushOperationCodeEnum`、`ExecuteTaskStatusEnum` 等。
纯值枚举（只有常量值没有方法）一般不需要写测试。

### 基类选择

继承 `AbstractJunitNoDependenciesTest`（不需要 ResManager）

### 模板结构

```java
package kd.<包路径>.common.enums;

import kd.bos.test.ext.annotaions.UnittestCaseInfo;
import kd.bos.unittest.AbstractJunitNoDependenciesTest;
import org.junit.Test;

public class XxxEnumTest extends AbstractJunitNoDependenciesTest {

    @UnittestCaseInfo(
        author = "<name> <<name>@kingdee.com>",
        title = "Test getByCode with valid code",
        targetClass = "kd.<包路径>.common.enums.XxxEnum",
        targetMethod = "getbycode",
        lastUpdateTime = "yyyy-MM-dd HH:mm:ss",
        lastUpdateAuthor = "<name> <<name>@kingdee.com>",
        methodSignature = "public static XxxEnum getByCode(String code)",
        testPoints = {"Functionality"},
        description = "Test getByCode with valid code"
    )
    @Test
    public void testGetByCode_ValidCode() {
        // step 测试有效的枚举 code
        XxxEnum result = XxxEnum.getByCode("A");

        // assert
        assertEquals(XxxEnum.SOME_VALUE, result);
    }

    @Test
    public void testGetByCode_InvalidCode() {
        // step 测试无效的枚举 code
        XxxEnum result = XxxEnum.getByCode("INVALID");

        // assert 应返回 null
        assertNull(result);
    }

    @Test
    public void testGetByCode_EmptyCode() {
        // step 测试空字符串
        String result = XxxEnum.matchMethod("");

        // assert
        assertEquals("", result);
    }
}
```

### 枚举测试要点

1. 测试**有效值**、**无效值**、**空值**三种场景
2. 测试枚举的 `getCode()` / `getName()` 等访问器
3. 测试枚举的匹配/转换方法（如 `matchPushTargetBill()`）

---

## 工具类测试模式 {#Utils}

### 适用类型

`<模块组>-common/src/main/java/kd/<包路径>/common/utils/` 下的工具类，
如 `BillPushUtil`、`FormPluginUtil`、`ApiInterfaceUtil`、`MetadataUtils` 等。

### 基类选择

| 场景 | 基类 |
|------|------|
| 依赖多个常见服务 | 继承 common 包的 `BaseTest`（已封装 10 个常用 Mock） |
| 依赖少量特定服务 | 继承 `AbstractJunitNoDependenciesResManagerTest`，自行管理 Mock |

### common 包 BaseTest 预置的 Mock

common 包的 `BaseTest`（`kd.<包路径>.common.BaseTest`，实际路径需在 `<模块组>-common/src/test/java/` 下查找）预置了：
- OrgUnitServiceHelper
- BillTypeParamHelper
- ResManager（thenAnswer 返回第一个参数）
- SystemParamServiceHelper
- QueryServiceHelper
- BusinessDataServiceHelper
- OperationServiceHelper
- BaseDataServiceHelper
- SaveServiceHelper
- **EntityMetadataCache**（比 business 工程多了这个）

### 模板结构

```java
package kd.<包路径>.common.utils;

import kd.bos.test.ext.annotaions.UnittestCaseInfo;
import kd.bos.unittest.mock.DynamicObjectMocker;
import kd.<包路径>.common.BaseTest;  // common 包自带的 BaseTest（实际包路径需查找）
import org.junit.Test;
import org.mockito.MockedStatic;

import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

public class XxxUtilTest extends BaseTest {

    // 如需额外 Mock（BaseTest 未覆盖的），在此声明
    // MockedStatic<SpecialHelper> specialHelper;

    @UnittestCaseInfo(
        author = "<name> <<name>@kingdee.com>",
        title = "Test method with valid input",
        targetClass = "kd.<包路径>.common.utils.XxxUtil",
        targetMethod = "methodname",
        lastUpdateTime = "yyyy-MM-dd HH:mm:ss",
        lastUpdateAuthor = "<name> <<name>@kingdee.com>",
        methodSignature = "public static ReturnType methodName(params)",
        testPoints = {"Functionality"},
        description = "Test method with valid input"
    )
    @Test
    public void testMethod_ValidInput() {
        //step 配置 Mock 返回值（直接使用 BaseTest 的成员变量）
        queryServiceHelper.when(() -> QueryServiceHelper.query(anyString(), anyString(), any()))
            .thenReturn(new DynamicObjectMocker().add("id", 1L).getCollection());

        //step 执行被测方法
        Object result = XxxUtil.targetMethod(param);

        //assert 断言结果
        assertNotNull(result);
    }

    @Test
    public void testMethod_EmptyInput() {
        //step 测试空输入
        Object result = XxxUtil.targetMethod(null);

        //assert
        assertNull(result);
    }
}
```

### 工具类特殊技巧

#### 1. MockedConstruction（Mock 构造函数）

> 此为通用技巧，详见 SKILL.md 2.8 节。此处展示 common 工程的典型用法。

当工具类内部 new 了某个对象时，使用 `Mockito.mockConstruction`：

```java
@Test
public void testDoPush() {
    try (MockedStatic<ConvertServiceHelper> convertServiceHelper = mockStatic(ConvertServiceHelper.class);
         MockedConstruction<PushArgs> pushArgsMock = Mockito.mockConstruction(PushArgs.class)) {

        //step 执行
        BillPushUtil.doPush("src", "target", "ruleId", params, rows);

        //assert
        convertServiceHelper.verify(() -> ConvertServiceHelper.push(any()));
    }
}
```

#### 2. Mock 表单控件

测试 FormPluginUtil 等涉及 UI 控件的方法：

```java
@Test
public void testGetSelectedRowData() {
    // step Mock 表单控件
    IFormView view = mock(IFormView.class);
    EntryGrid entryGrid = mock(EntryGrid.class);
    IDataModel model = mock(IDataModel.class);

    when(view.getControl("entryKey")).thenReturn(entryGrid);
    when(entryGrid.getSelectRows()).thenReturn(new int[]{0, 1});

    DynamicObjectCollection entries = new DynamicObjectCollection();
    entries.add(new DynamicObjectMocker().add("id", 1).getObject());
    entries.add(new DynamicObjectMocker().add("id", 2).getObject());
    when(model.getEntryCurrentRowIndex("entryKey")).thenReturn(0);

    // step 执行
    List<DynamicObject> result = FormPluginUtil.getSelectedRowData(model, view, "entryKey");

    // assert
    assertEquals(2, result.size());
}
```

#### 3. try-with-resources 管理方法级 Mock

当某些 MockedStatic 只在单个测试方法中使用时：

```java
@Test
public void testMethod() {
    try (MockedStatic<SpecialHelper> specialHelper = mockStatic(SpecialHelper.class)) {
        specialHelper.when(() -> SpecialHelper.doSomething(any())).thenReturn(result);
        // ... 测试逻辑
    }
    // 自动关闭，无需 @After
}
```

---

## common 工程类型分布参考

| 类型 | 数量 | 是否需要测试 | 测试模式 |
|------|------|------------|---------|
| 常量类（Const） | ~21 | 通常不需要 | — |
| 枚举类（Enum） | ~24 | 含业务方法时需要 | Enum 模式 |
| POJO/DTO/Bean | ~25 | 需要 | POJO 模式 |
| 工具类（Utils） | ~19 | 需要 | Utils 模式 |
| 业务模块类 | ~12 | 需要 | Utils 模式 |
