# 牦牛存栏数据填报 — 测试用例

> 依据：`牦牛存栏数据填报功能需求.md`、`牦牛存栏数据填报技术方案.md`  
> **接口 Base Path**：`/dataModel/yakHerdInventory`  
> **模板文件**：`yak_herd_inventory_import_template.xlsx` / 测试样例 `yak_herd_inventory_2026.xlsx`  
> **组织样本**：`sys_dept` 乡镇（`parent_id=100`，`order_num>12`）及其下村节点

**通用前置**：已登录且具备模块权限；`sys_dept` 已导入乡镇与村；测试库无冲突数据或已隔离。

---

## 一、单元测试用例

| 用例编号 | 测试模块 | 测试项 | 测试类型 | 测试工具 | 测试目的 | 前置条件 | 测试步骤 | 预期结果 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| ZCZX-MYCLTB-UT-001 | 年份 | 可选年份范围 | 单元测试 | JUnit5 | §2.2 | 固定时钟 2026 | `YakHerdInventoryYearSupport` | 返回 2026～2021 共 6 年 |
| ZCZX-MYCLTB-UT-002 | 乡镇 | 字典过滤 | 单元测试 | JUnit5 | §2.3 | Mock dept | `listReportTowns()` | 仅合法乡镇 |
| ZCZX-MYCLTB-UT-003 | 校验 | 必填年份村 | 单元测试 | JUnit5 | §5.2 | 无 | `validateRowForSave` 缺 `villageDeptId` | 抛 `ServiceException` |
| ZCZX-MYCLTB-UT-004 | 校验 | 负数拒绝 | 单元测试 | JUnit5 | §2.5 | 无 | `breedingBullCount=-1` | 校验失败 |
| ZCZX-MYCLTB-UT-005 | 唯一性 | 同年同月同村 | 单元测试 | JUnit5+Mockito | §5.2 | 已存在同键 | `insert` 同键 | 失败「该年该月该村已填报」 |
| ZCZX-MYCLTB-UT-006 | 新增 | 新增成功 | 单元测试 | JUnit5+Mockito | §5.2 | 无同键 | `insert` | 成功；乡镇由村反推 |
| ZCZX-MYCLTB-UT-007 | 导入解析 | 识别表头行 | 单元测试 | JUnit5 | §3.8.1 | 测试 xlsx | `detectHeaderRow` | 定位「序号」「乡镇」 |
| ZCZX-MYCLTB-UT-008 | 派生 | 合计计算 | 单元测试 | JUnit5 | §2.5 | 明细样本 | `recalculateDerived` | bull/cow/dairy/yak/grand 正确 |
| ZCZX-MYCLTB-UT-009 | 导入解析 | 未知乡镇 | 单元测试 | JUnit5 | §5.6 | 行「不存在乡」 | 导入 | 记入 `failMessages` |
| ZCZX-MYCLTB-UT-010 | 导入解析 | 不含合计行 | 单元测试 | JUnit5 | §5.6 | 测试 xlsx | `parse` | 无乡镇名含「合计」的行 |
| ZCZX-MYCLTB-UT-011 | 导入 | 唯一键 upsert | 单元测试 | JUnit5+Mockito | §5.6 | 已有同年月同村数据 | 再导入 | 更新既有行，不删同月其他村 |
| ZCZX-MYCLTB-UT-012 | 校验 | Excel 合计警告 | 单元测试 | JUnit5 | §2.5 | 合计列不一致 | `buildExcelTotalWarns` | 返回警告文案 |
| ZCZX-MYCLTB-UT-013 | 修改 | 记录不存在 | 单元测试 | JUnit5+Mockito | §5.3 | id 不存在 | `update` | 抛 `ServiceException` |
| ZCZX-MYCLTB-UT-014 | 删除 | 物理删除 | 单元测试 | JUnit5+Mockito | §5.4 | 有效 id | `deleteByIds` | 调用 Mapper 物理删除 |
| ZCZX-MYCLTB-UT-015 | 村树 | 树结构 | 单元测试 | JUnit5 | §2.4 | Mock dept | `buildVillageTree` | 县→乡镇→村 |
| ZCZX-MYCLTB-UT-016 | 校验 | 村反推乡镇 | 单元测试 | JUnit5 | §2.4 | 合法村 | `validateRowForSave` | 写入 `townDeptId` |
| ZCZX-MYCLTB-UT-017 | 校验 | 非法村 | 单元测试 | JUnit5 | §2.4 | 非法 id | `validateRowForSave` | 失败 |
| ZCZX-MYCLTB-UT-018 | 导出 | 按年月填充 | 单元测试 | JUnit5 | §5.7 | 模板+村样本 | `export` | 返回 xlsx；含存栏数 |
| ZCZX-MYCLTB-UT-019 | 导出 | 合计行求和 | 单元测试 | JUnit5 | §5.7 | ≥2 村数据 | `export` | 合计行村居及后列=各列之和；序号列合并为「合计」 |
| ZCZX-MYCLTB-UT-020 | 月份 | 未来月拒绝 | 单元测试 | JUnit5 | §2.2.1 | 当前年 | `validateStatPeriod` 未来月 | 抛异常 |
| ZCZX-MYCLTB-UT-021 | 月份 | 月份选项 | 单元测试 | JUnit5 | §2.2.1 | 无 | `resolveMonthOptions()` | 返回 1～12 |

## 二、接口测试用例

| 用例编号 | 测试模块 | 测试项 | 测试类型 | 测试工具 | 测试目的 | 前置条件 | 测试步骤 | 预期结果 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| ZCZX-MYCLTB-API-001 | 列表 | 默认分页 | 接口测试 | Postman | §5.1 | 有样本 | `GET /list` | `code=200`；含年/月/乡镇/村/存栏等 |
| ZCZX-MYCLTB-API-002 | 列表 | 按年份筛选 | 接口测试 | Postman | §5.1 | 多年数据 | `statYear=2026` | 仅 2026 |
| ZCZX-MYCLTB-API-002a | 列表 | 按年月筛选 | 接口测试 | Postman | §5.1 | 同 year 多 month | `statYear=2026&statMonth=7` | 仅该月 |
| ZCZX-MYCLTB-API-003 | 列表 | 按乡镇筛选 | 接口测试 | Postman | §5.1 | 已知 dept | `townDeptId=` | 仅该乡镇下村记录 |
| ZCZX-MYCLTB-API-003a | 列表 | 按村筛选 | 接口测试 | Postman | §5.1 | 已知村 | `villageDeptId=` | 仅该村 |
| ZCZX-MYCLTB-API-004 | 列表 | 组合筛选 | 接口测试 | Postman | §5.1 | 已登录 | 年+月+乡镇+村 | 交集 |
| ZCZX-MYCLTB-API-005 | 详情 | 全字段 | 接口测试 | Postman | §5.5 | 有效 id | `GET /{id}` | 含明细与派生合计 |
| ZCZX-MYCLTB-API-006 | 新增 | 必填成功 | 接口测试 | Postman | §5.2 | 无重复 | `POST` 含 `villageDeptId` | 成功 |
| ZCZX-MYCLTB-API-007 | 新增 | 缺年份 | 接口测试 | Postman | §5.2 | 已登录 | 缺 `statYear` | 失败 |
| ZCZX-MYCLTB-API-008 | 新增 | 缺村 | 接口测试 | Postman | §5.2 | 已登录 | 缺 `villageDeptId` | 失败 |
| ZCZX-MYCLTB-API-009 | 新增 | 重复同年月村 | 接口测试 | Postman | §5.2 | 已有记录 | 再 `POST` 同键 | 失败 |
| ZCZX-MYCLTB-API-010 | 修改 | 更新指标 | 接口测试 | Postman | §5.3 | 有效 id | `PUT` 改明细 | 成功；派生重算 |
| ZCZX-MYCLTB-API-011 | 删除 | 物理删除 | 接口测试 | Postman | §5.4 | 有效 id | `DELETE /{id}` | 记录移除 |
| ZCZX-MYCLTB-API-012 | 下拉 | 年份选项 | 接口测试 | Postman | §2.2 | 已登录 | `GET /yearOptions` | 6 个年份 |
| ZCZX-MYCLTB-API-013 | 下拉 | 乡镇选项 | 接口测试 | Postman | §2.3 | 已登录 | `GET /townOptions` | 合法乡镇 |
| ZCZX-MYCLTB-API-013a | 下拉 | 村树 | 接口测试 | Postman | §2.4 | 已登录 | `GET /villageTree` | 树含乡镇与村 |
| ZCZX-MYCLTB-API-014 | 导入 | 全量村行 | 接口测试 | Postman | §5.6 | 合法 Excel | `POST /importData` | `insertCount>0`；`failCount=0` |
| ZCZX-MYCLTB-API-015 | 导入 | 覆盖同月 | 接口测试 | Postman | §5.6 | 已有同月 | 再导入 | 旧数据被替换 |
| ZCZX-MYCLTB-API-016 | 导入 | 缺填报年份 | 接口测试 | Postman | §5.6 | 已登录 | 仅上传文件 | 失败 |
| ZCZX-MYCLTB-API-017 | 导入 | 非 xlsx | 接口测试 | Postman | §5.6 | 已登录 | 上传 `.csv` | 失败 |
| ZCZX-MYCLTB-API-017a | 导入 | 无有效行 | 接口测试 | Postman | §5.6 | 空数据区 | 导入 | 「未解析到有效数据行」 |
| ZCZX-MYCLTB-API-018 | 权限 | 无 add | 接口测试 | Postman | §6 | 仅 list | `POST /` | 403 |
| ZCZX-MYCLTB-API-019 | 权限 | 无 import | 接口测试 | Postman | §6 | 无 import | `POST /importData` | 403 |
| ZCZX-MYCLTB-API-020 | 端到端 | 导入-查-详情 | 接口测试 | Postman | §4 | 空库 | 导入→list→detail | 数据一致 |
| ZCZX-MYCLTB-API-021 | 导出 | 按年月导出 | 接口测试 | Postman | §5.7 | 有数据 | `GET /export?statYear=&statMonth=` | `200`；xlsx |
| ZCZX-MYCLTB-API-021a | 导出 | 按乡镇过滤 | 接口测试 | Postman | §5.7 | 有数据 | 附加 `townDeptId` | 仅该乡镇村行 |
| ZCZX-MYCLTB-API-022 | 导出 | 缺 statMonth | 接口测试 | Postman | §5.7 | 已登录 | 仅 `statYear` | 失败 |
| ZCZX-MYCLTB-API-023 | 导出 | 非法年份 | 接口测试 | Postman | §5.7 | 已登录 | `statYear=1999` | 失败 |
| ZCZX-MYCLTB-API-024 | 权限 | 无 export | 接口测试 | Postman | §6 | 无 export | `GET /export` | 403 |
| ZCZX-MYCLTB-API-025 | 端到端 | 导出-再导入 | 接口测试 | Postman | §5.7 | 有数据 | 导出→导入同月 | upsert 成功 |

---

## 三、界面测试用例（UI）

**工具**：Playwright + Chrome。

| 用例编号 | 测试模块 | 测试项 | 测试类型 | 测试工具 | 测试目的 | 前置条件 | 测试步骤 | 预期结果 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| ZCZX-MYCLTB-UI-001 | 列表 | 默认加载 | UI | Playwright | §5.1 | 有数据 | 进入菜单 | 列含年月、乡镇、村…；年月筛选为空 |
| ZCZX-MYCLTB-UI-002 | 列表 | 筛选 | UI | Playwright | §5.1 | 有数据 | 选年月/乡镇/村搜索 | 结果正确 |
| ZCZX-MYCLTB-UI-003 | 列表 | 重置 | UI | Playwright | §5.1 | 已筛选 | 点重置 | 条件清空 |
| ZCZX-MYCLTB-UI-004 | 新增 | 必填校验 | UI | Playwright | §5.2 | 有 add | 不选村提交 | 表单提示 |
| ZCZX-MYCLTB-UI-005 | 新增 | 年份下拉 | UI | Playwright | §2.2 | 打开表单 | 查看年份 | 当年起前 5 年 |
| ZCZX-MYCLTB-UI-006 | 新增 | 村树 | UI | Playwright | §2.4 | 打开表单 | 展开树 | 仅可选村叶子 |
| ZCZX-MYCLTB-UI-007 | 修改 | 回显保存 | UI | Playwright | §5.3 | 有记录 | 改牧户数保存 | 列表更新 |
| ZCZX-MYCLTB-UI-008 | 删除 | 二次确认 | UI | Playwright | §5.4 | 有记录 | 删除确认 | 行消失 |
| ZCZX-MYCLTB-UI-009 | 详情 | 含派生字段 | UI | Playwright | §5.5 | 有完整数据 | 点查看 | 展示公牛/母牛/奶牛/总合计等 |
| ZCZX-MYCLTB-UI-010 | 新增 | 无派生输入 | UI | Playwright | §5.2 | 打开新增 | 查看表单 | 无公牛合计等计算项输入框 |
| ZCZX-MYCLTB-UI-011 | 导入 | 选年月上传 | UI | Playwright | §5.6 | 有 import | 上传模板 | 成功摘要 |
| ZCZX-MYCLTB-UI-012 | 导入 | 未选月份 | UI | Playwright | §5.6 | 有 import | 缺月份 | 校验失败 |
| ZCZX-MYCLTB-UI-013 | 权限 | 无导入按钮 | UI | Playwright | §6 | 无 import | 进入页 | 无导入入口 |
| ZCZX-MYCLTB-UI-014 | 导出 | 按年月导出 | UI | Playwright | §5.7 | 有 export | 选年月导出 | 下载对应文件名 |
| ZCZX-MYCLTB-UI-015 | 导出 | 未选月份 | UI | Playwright | §5.7 | 有 export | 缺月份 | 校验失败 |
| ZCZX-MYCLTB-UI-016 | 权限 | 无导出按钮 | UI | Playwright | §6 | 无 export | 进入页 | 无导出入口 |

---

## 四、覆盖对照

| 需求章节 | 要点 | 主要用例 |
| --- | --- | --- |
| §2.2～2.2.1 | 年月 | UT-001、020～021；API-012 |
| §2.3～2.4 | 乡镇/村 | UT-002、015～017；API-013、013a；UI-006 |
| §2.5 派生 | 合计计算 | UT-008、012；UI-009～010 |
| §5.1 列表 | 乡镇+村列 | API-001～004；UI-001～003 |
| §5.2～5.4 CRUD | 村唯一、物理删 | UT-003～006、013～014；API-006～011 |
| §5.6 导入 | 唯一键 upsert | UT-007、010～011；API-014～017a |
| §5.7 导出 | 合计按列求和 | UT-018～019；API-021～025；UI-014 |
| §6 权限 | 按钮级 | API-018～019、024；UI-013、016 |

---

## 五、修订记录

| 版本 | 说明 |
| --- | --- |
| 1.0～1.5 | 乡镇粒度、按年月、四档年龄等历史版本 |
| 1.6 | 同步村粒度与新模板：唯一键村、村树、派生合计、导入全量覆盖、导出合计求和；用例编号延续 ZCZX-MYCLTB |
