# 养殖产业数据 — 测试用例

> 依据：`养殖产业数据功能需求.md`、`养殖产业数据技术方案.md`、`养殖产业数据前端技术方案.md`  
> **接口 Base Path**：`/dataModel/breedingIndustry`（含网关/context-path 时须补齐前缀）。鉴权：若依 Cookie / `Authorization` Token。

**通用前置**：已执行 `sql/biz_pasture.sql`、`sql/biz_grassland.sql`、`sql/biz_yak_asset.sql`、`sql/biz_yak_disease_warning.sql`；测试账号具备 `dataModel:breedingIndustry:query` 及菜单「养殖产业数据」。

**UI 工具**：**Playwright** + **Chromium**，`channel: 'chrome'`（本机 **Google Chrome**）。组件路径：`livestockIndustry/breedingIndustry/index`。

---

## 测试数据样本（可替换为环境真实 ID）

| 代号 | 说明 |
| --- | --- |
| **UA** | 仅可见牧场 **P1**（`id=101`） |
| **UB** | 仅可见 **P2**（`id=202`） |
| **ADMIN** | 县级全量，可见 P1+P2 |
| **U0** | 有模块权限但可见牧场 **[]** |
| **P-停** | `biz_status=2` 停业牧场，UA 不可见或全量不计入牧场数 |
| **Y-N** | P1、`asset_status=1`、`del_flag=0` |
| **Y-死/丢/栏** | `status` 2/3/4，不计当前存栏 |
| **Y-U** | `gender` 空，计入总量不计公母 |
| **Y-O** | `status=4`，`status_change_date` 在当年且 ≤ 统计日 |
| **Y-O0** | `status=4`，`status_change_date` 空 |
| **Y-P0** | `pasture_id` 空 |
| **Y-H** | `status=4`，`change_date` 在 3 月；2 月末趋势仍计入 |
| **Y-L** | `status=4`，`change_date` = 2 月最后一天；2 月末 **不计** |
| **W-in** | 当年 1/1 0:00 ≤ `alert_time` ≤ 统计日 23:59:59 |
| **W-out** | `alert_time` 为上年或统计日次日 |
| **G** | 草场 `del_flag=0`；退化 1～5、禁牧/轮牧/全年各造 1 条 |

**联调统计日**：固定 **2026-05-20**（`monthlyTrend` 含 1～5 月共 5 点；不含 6～12 月）。

---

## 覆盖说明

| 类别 | 覆盖要点 |
| --- | --- |
| **正常流程** | 菜单进入自动加载、刷新全页、四块指标与图表展示（§4.2、§5.1～§5.5） |
| **异常流程** | 未登录、无权限、聚合失败、空库、接口不存在维护能力（§3、§7.2、技术§3.1.8） |
| **业务约束** | §2.1～§2.10 口径；§2.9 权限三态；§3 本期不实现项（无 CRUD/筛选/导出） |

---

## 一、单元测试用例

| 用例编号 | 测试模块 | 测试项 | 测试类型 | 测试工具 | 测试目的 | 前置条件 | 测试步骤 | 预期结果 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| ZCZX-YZCYSJ-UT-001 | 数据权限 | 全量可见牧场 ID 列表 | 单元测试 | JUnit5 | §2.6、§2.9 | Mock ADMIN 数据范围 | 调用 `BreedingIndustryPastureScope.resolve()` | 返回全部 `biz_status=1` 且未删牧场 ID；非 null 非空 |
| ZCZX-YZCYSJ-UT-002 | 数据权限 | 可见牧场为空列表 | 单元测试 | JUnit5 | §2.6 空 `[]` | Mock U0 | `loadDashboard([])` | 牦牛/预警/牧场数=0；草场仍统计 |
| ZCZX-YZCYSJ-UT-003 | 数据权限 | 部分牧场 IN 过滤 | 单元测试 | JUnit5 | §2.9 | 造数 P1、P2 | `allowed=[101]` 统计牦牛 | 仅 P1 档案计入 |
| ZCZX-YZCYSJ-UT-004 | 存栏 | 仅正常状态 | 单元测试 | JUnit5 | §2.3 | 含 status 1/2/3/4 | `countInventory` | 仅 `asset_status=1` |
| ZCZX-YZCYSJ-UT-005 | 存栏 | 逻辑删除排除 | 单元测试 | JUnit5 | §2.3 | `del_flag=2` 档案 | 统计 | 不计入 |
| ZCZX-YZCYSJ-UT-006 | 存栏 | pasture_id 空排除 | 单元测试 | JUnit5 | §2.9 | Y-P0 | 统计 | 不计入任何牦牛类指标 |
| ZCZX-YZCYSJ-UT-007 | 出栏 | 当年且变更日非空 | 单元测试 | JUnit5 | §2.4 | Y-O、Y-O0 | `countAnnualOutbound` | 仅 Y-O；Y-O0 为 0 |
| ZCZX-YZCYSJ-UT-008 | 出栏 | 周期上界统计日 | 单元测试 | JUnit5 | §2.4 | `change_date=统计日` | 统计 | 计入；次日不计 |
| ZCZX-YZCYSJ-UT-009 | 预警 | 去重 yak_no | 单元测试 | JUnit5 | §2.5 | 同牛 2 条 W-in | `countWarning` | 计 1 |
| ZCZX-YZCYSJ-UT-010 | 预警 | 周期下界 1/1 0:00 | 单元测试 | JUnit5 | §2.5 | `alert_time=当年1/1 00:00:00` | 统计 | 计入 |
| ZCZX-YZCYSJ-UT-011 | 预警 | 周期外排除 | 单元测试 | JUnit5 | §2.5 | W-out | 统计 | 0 |
| ZCZX-YZCYSJ-UT-012 | 预警 | 须关联纳入档案 | 单元测试 | JUnit5 | §2.9 | 预警牛仅 P2、范围 P1 | `countWarning` | 0 |
| ZCZX-YZCYSJ-UT-013 | 性别 | 公+母+未标=总量 | 单元测试 | JUnit5 | §5.3.1 | Y-N 公母 + Y-U | 分项求和 | 等于 `inventoryTotal` |
| ZCZX-YZCYSJ-UT-014 | 性别 | trim 后匹配 | 单元测试 | JUnit5 | 技术§4 | `gender=' 母 '` | `normalizeGender` | 计母牛 |
| ZCZX-YZCYSJ-UT-015 | 月龄 | birth_date 回退 | 单元测试 | JUnit5 | §2.3 | `age_months` 空、有生日 | `resolveAgeMonths(statDate)` | 与 `TIMESTAMPDIFF` 一致；无生日为 0 |
| ZCZX-YZCYSJ-UT-016 | 年龄分档 | 边界 0/6/7/12/13 | 单元测试 | JUnit5 | §5.4 | 各月龄一头 | `bandOf` | M1_6、M7_12（含12）、M13_PLUS |
| ZCZX-YZCYSJ-UT-017 | 年龄分档 | 占比与零分母 | 单元测试 | JUnit5 | §5.4 | 存栏 0 | `calcRatio` | 三档 count=0、ratio=0 仍返回 |
| ZCZX-YZCYSJ-UT-018 | 月末趋势 | 变更日晚于月末仍计入 | 单元测试 | JUnit5 | §2.10 | Y-H，M=2 月末 | `countAtMonthEnd(2)` | 计入 |
| ZCZX-YZCYSJ-UT-019 | 月末趋势 | 变更日≤月末排除 | 单元测试 | JUnit5 | §2.10 | Y-L，M=2 月末 | `countAtMonthEnd(2)` | 不计 |
| ZCZX-YZCYSJ-UT-020 | 月末趋势 | 非正常且变更日空 | 单元测试 | JUnit5 | §2.10 | status=2、date 空 | 各月 | 恒 0 |
| ZCZX-YZCYSJ-UT-021 | 月末趋势 | 当前月用统计日 | 单元测试 | JUnit5 | §2.10、技术§4 | statDate=5-20 | 5 月点 vs `countInventory` | 相等 |
| ZCZX-YZCYSJ-UT-022 | 月末趋势 | 不生成未来月 | 单元测试 | JUnit5 | §2.2、§5.3.2 | 5 月环境 | `buildMonthlyTrend` | 仅 month 1～5 |
| ZCZX-YZCYSJ-UT-023 | 牧场 | 仅正常经营 | 单元测试 | JUnit5 | §2.6 | P-停、注销 | `pastureCount` | 不含停业/注销 |
| ZCZX-YZCYSJ-UT-024 | 草场 | 面积空不参与求和 | 单元测试 | JUnit5 | §2.6 | 一条 `area_mu` null | `sumArea` | 不计该条 |
| ZCZX-YZCYSJ-UT-025 | 草场 | 可用=未退化+轻度 | 单元测试 | JUnit5 | §2.7 | 退化 1、2、3 各 1 | `availableCount` | 2 |
| ZCZX-YZCYSJ-UT-026 | 草场 | 禁牧闭区间 | 单元测试 | JUnit5 | §2.7 | 禁牧期内/外；统计日在期内 | `inUseCount` | 仅期外+轮牧/全年计入 |
| ZCZX-YZCYSJ-UT-027 | 草场 | 退化五档合计 | 单元测试 | JUnit5 | §2.8、§5.5 | G 全集 | `degradationStats` | sum(count)=`grasslandCount` |
| ZCZX-YZCYSJ-UT-028 | 缓存 | 命中未过期 | 单元测试 | JUnit5 | §2.3 | 有 cache 且未过期 | `load(false)` | 不调用聚合；返回缓存 JSON |
| ZCZX-YZCYSJ-UT-029 | 缓存 | forceRefresh 跳过 | 单元测试 | JUnit5 | §3.1.1 | 有缓存 | `load(true)` | 强制实时计算并 UPSERT |

---

## 二、接口测试用例

| 用例编号 | 测试模块 | 测试项 | 测试类型 | 测试工具 | 测试目的 | 前置条件 | 测试步骤 | 预期结果 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| ZCZX-YZCYSJ-API-001 | 看板 | 首次加载全量结构 | 接口测试 | Postman/curl | §5.1 正常流程 | UA 登录；造数完整 | `GET /dataModel/breedingIndustry/dashboard` | `code=200`；`data` 含 `statDate`、`statYear`、`overview`、`inventoryChange`、`ageStructure`、`grassland` |
| ZCZX-YZCYSJ-API-002 | 看板 | 统计日与自然年 | 接口测试 | Postman | §2.2 | 已登录 | 同上 | `statDate`=`yyyy-MM-dd` 服务器当日；`statYear` 为公历年 |
| ZCZX-YZCYSJ-API-003 | 产业总览 | 六项指标非负 | 接口测试 | Postman | §5.2 | 已知样本 | 读 `overview` 六字段 | 与 UT-004～012、023～024 手工值一致；`grasslandAreaMu`≥0 |
| ZCZX-YZCYSJ-API-004 | 存栏变动 | 公母未标关系 | 接口测试 | Postman | §5.3.1 | 含 Y-U | `bullCount+cowCount+unknownGenderCount=inventoryTotal` | 成立 |
| ZCZX-YZCYSJ-API-005 | 存栏变动 | 月趋势仅至当前月 | 接口测试 | Postman | §5.3.2 | 统计日 5 月 | `monthlyTrend` | 长度=5；`month`∈{1..5}；无 6～12 |
| ZCZX-YZCYSJ-API-006 | 存栏变动 | 当前月=总览存栏 | 接口测试 | Postman | §5.3.2、技术§4 | 已登录 | 比 `monthlyTrend` 最后项与 `inventoryTotal` | 相等 |
| ZCZX-YZCYSJ-API-007 | 年龄结构 | 固定三档 | 接口测试 | Postman | §5.4 | 已登录 | `ageStructure` | 长度=3；`bandCode` 为 M1_6/M7_12/M13_PLUS；count=0 也返回 |
| ZCZX-YZCYSJ-API-008 | 年龄结构 | 占比之和容忍 | 接口测试 | Postman | §5.4 | 存栏>0 | sum(ratio) | 99.9～100.1（四舍五入容忍） |
| ZCZX-YZCYSJ-API-009 | 草场占比 | 可用/使用中 | 接口测试 | Postman | §5.5.1 | 已登录 | `grassland.availableCount/inUseCount` | 与 UT-025～026 一致 |
| ZCZX-YZCYSJ-API-010 | 草场占比 | 退化五档与合计 | 接口测试 | Postman | §5.5.2 | 已登录 | `degradationStats` | 长度=5；sum(count)=`grasslandCount` |
| ZCZX-YZCYSJ-API-011 | 刷新 | forceRefresh | 接口测试 | Postman | §5.1 | 已登录 | `GET .../dashboard?forceRefresh=true` | `code=200`；全节点更新；`statDate` 不变（同日） |
| ZCZX-YZCYSJ-API-012 | 刷新 | 同日再次加载一致性 | 接口测试 | Postman | 技术§4 一致性 | 后台数据未变 | 连续两次 `GET`（第二次 `forceRefresh=true`） | 指标一致（除非源数据变） |
| ZCZX-YZCYSJ-API-013 | 数据权限 | UA 隔离 P2 | 接口测试 | Postman | §2.9 | P2 牦牛+W2；UA 登录 | `GET /dashboard` | 不含 P2 牦牛；`warningYakCount` 不含 W2 |
| ZCZX-YZCYSJ-API-014 | 数据权限 | UB 仅 P2 | 接口测试 | Postman | §2.9 | UB 登录 | `GET /dashboard` | 仅 P2 牦牛；`pastureCount` 含 P2 不含 P1 |
| ZCZX-YZCYSJ-API-015 | 数据权限 | ADMIN 全量 | 接口测试 | Postman | §2.9 | ADMIN 登录 | `GET /dashboard` | P1+P2 牦牛合计 |
| ZCZX-YZCYSJ-API-016 | 数据权限 | U0 牦牛为零草场仍有 | 接口测试 | Postman | §2.6、§3.1.8 | U0 登录；有 G | `GET /dashboard` | 牦牛类=0；`grasslandCount`>0 |
| ZCZX-YZCYSJ-API-017 | 鉴权 | 未登录 | 接口测试 | curl | §7.2 异常 | 无 Token | `GET /dashboard` | 401 或统一未登录 |
| ZCZX-YZCYSJ-API-018 | 权限 | 无 query 权限 | 接口测试 | Postman | §7.1 | 角色无 `breedingIndustry:query` | `GET /dashboard` | 403 或 `msg` 无权限 |
| ZCZX-YZCYSJ-API-019 | 异常 | 聚合失败 | 接口测试 | Postman/Mock | §3.1.8 | Mock Mapper 抛错 | `GET /dashboard` | 失败；`data` 空；`msg` 含「加载失败」类文案 |
| ZCZX-YZCYSJ-API-020 | 异常 | 空库零值 | 接口测试 | Postman | §5.1 空态 | 新库无业务数据 | `GET /dashboard` | `code=200`；数值 0；`ageStructure` 长 3、`degradationStats` 长 5 |
| ZCZX-YZCYSJ-API-021 | 范围 | 无维护/同步接口 | 接口测试 | Postman | §3 本期不实现 | 已登录 | `POST/PUT/DELETE /dataModel/breedingIndustry/**` | 404/405 或无权限 |
| ZCZX-YZCYSJ-API-022 | 范围 | 无筛选参数 | 接口测试 | Postman | §3、§8 | 已登录 | 传 `pastureId`/`year` 等非法 Query | 忽略或 400；口径仍按 §2 默认 |
| ZCZX-YZCYSJ-API-023 | 出栏 | 无变更日出栏 | 接口测试 | Postman | §2.4 | 仅 Y-O0 | `annualOutbound` | 0 |
| ZCZX-YZCYSJ-API-024 | 预警 | 年内去重口径 | 接口测试 | Postman | §2.5 | W-in 双条同 yak_no | 对比 SQL `COUNT DISTINCT` | 与 `warningYakCount` 一致 |
| ZCZX-YZCYSJ-API-025 | 预警 | 周期外不计 | 接口测试 | Postman | §2.5 | 仅 W-out | `warningYakCount` | 0 |
| ZCZX-YZCYSJ-API-026 | 台账 | 已删档案不计 | 接口测试 | Postman | §2.3 | 牦牛 `del_flag=2` | `GET /dashboard` | `inventoryTotal` 不含该头 |

---

## 三、界面测试用例（Playwright + Google Chrome）

| 用例编号 | 测试模块 | 测试项 | 测试类型 | 测试工具 | 测试目的 | 前置条件 | 测试步骤 | 预期结果 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| ZCZX-YZCYSJ-UI-001 | 正常流程 | 菜单进入自动加载 | UI | Playwright+Chrome | §4.2、§5.1 | UA 有菜单；后端正常 | 登录→点击「养殖产业数据」 | 出现四块标题；`v-loading` 结束后六项卡片有数；无报错弹窗 |
| ZCZX-YZCYSJ-UI-002 | 产业总览 | 六项单位与只读 | UI | Playwright+Chrome | §5.2 | 有数据 | 检查首屏卡片 | 名称含存栏/出栏/预警/牧场/草场/面积；单位头/个/亩；卡片不可点击跳转 |
| ZCZX-YZCYSJ-UI-003 | 存栏变动 | 公母与未标注文案 | UI | Playwright+Chrome | §5.3.1 | `unknownGenderCount>0` | 查看存栏区文案 | 显示公牛、母牛数量；显示「未标注性别：N 头」 |
| ZCZX-YZCYSJ-UI-004 | 存栏变动 | 未标注为 0 隐藏 | UI | Playwright+Chrome | §5.3.1、前端§4 | 造数全公母 | 打开页 | 不展示「未标注性别」文案 |
| ZCZX-YZCYSJ-UI-005 | 存栏变动 | 折线止于当前月 | UI | Playwright+Chrome | §5.3.2 | 5 月环境 | 检查 ECharts 折线 X 轴与数据点 | 1～5 月有值；6～12 月无数据点/折线不延伸 |
| ZCZX-YZCYSJ-UI-006 | 存栏变动 | 当前月与总览联动 | UI | Playwright+Chrome | §5.3.2 | 有数据 | 比对折线最后一点与「牦牛存栏总量」卡片 | 数值一致 |
| ZCZX-YZCYSJ-UI-007 | 年龄结构 | 三档饼图与占比 | UI | Playwright+Chrome | §5.4 | 有数据 | 查看年龄结构图例/标签 | 含 1-6、7-12、12月龄以上（或等价文案）；显示 % |
| ZCZX-YZCYSJ-UI-008 | 草场占比 | 可用/使用中与退化 | UI | Playwright+Chrome | §5.5 | 有 G | 查看草场区 | 展示可用、使用中个数；退化列表/图五档；列表占比与图一致 |
| ZCZX-YZCYSJ-UI-009 | 刷新 | 成功更新全页 | UI | Playwright+Chrome | §5.1 | 已打开页 | 点击「刷新」 | 按钮 loading/disabled；完成后四块数据更新；可选轻提示「数据已更新」 |
| ZCZX-YZCYSJ-UI-010 | 刷新 | 防重复点击 | UI | Playwright+Chrome | §5.1、前端§4 | 已打开页 | 刷新未完成时连点 2 次 | 仅 1 次有效请求或第二次按钮不可用 |
| ZCZX-YZCYSJ-UI-011 | 异常 | 加载失败提示 | UI | Playwright+Chrome | §5.1、§7.2 | Mock `/dashboard` 返回 500 | 进入或点刷新 | 提示「加载失败，请稍后重试」类；页面不展示上一用户/会话缓存假数据 |
| ZCZX-YZCYSJ-UI-012 | 空态 | 无牦牛有草场 | UI | Playwright+Chrome | §5.1 | U0 或仅删光牦牛 | 打开页 | 牦牛相关为 0 或「—」；草场区仍有数或 0 空态不崩溃 |
| ZCZX-YZCYSJ-UI-013 | 权限 | 无菜单不可进 | UI | Playwright+Chrome | §7.1 | 无 `breedingIndustry:query` | 直接访问 `#/livestockIndustry/breedingIndustry/index` | 403/无权限页/重定向；不见四块看板 |
| ZCZX-YZCYSJ-UI-014 | 范围 | 无维护按钮 | UI | Playwright+Chrome | §3、§5.1 | UA 有 query | 扫描工具栏与页面 | 无新增/编辑/删除/同步/导出；仅有刷新（及返回） |
| ZCZX-YZCYSJ-UI-015 | 布局 | 纵向四块可滚动 | UI | Playwright+Chrome | §5.1 | 小屏或缩窗口 | 滚动页面 | 四块标题均可达；统计日/年可选展示 |
| ZCZX-YZCYSJ-UI-016 | 权限 | UB 数据隔离展示 | UI | Playwright+Chrome | §2.9 | UB 登录；P1、P2 均有牛 | 打开看板记存栏总量；换 UA 对比 | UB 与 UA 存栏总量不同且符合各自牧场范围 |

---

## 四、需求追溯矩阵

| 功能需求 | 单元 | 接口 | 界面 |
| --- | --- | --- | --- |
| §2.1 只读看板 | — | API-021 | UI-014 |
| §2.2 统计日/趋势月份 | UT-021～022 | API-002、005 | UI-005 |
| §2.3 存栏认定 | UT-004～006 | API-003、026 | UI-006 |
| §2.4 年出栏 | UT-007～008 | API-023 | API-003 |
| §2.5 预警 | UT-009～012 | API-024～025 | API-003 |
| §2.6 牧场草场总览 | UT-023～024 | API-003、010 | UI-002、008 |
| §2.7 可用/使用中 | UT-025～026 | API-009 | UI-008 |
| §2.8 退化占比 | UT-027 | API-010 | UI-008 |
| §2.9 数据权限 | UT-001～003 | API-013～016 | UI-016 |
| §2.10 月末趋势 | UT-018～022 | API-005～006 | UI-005～006 |
| §5.1 加载/刷新/空态 | UT-028～029 | API-001、011～012、019～020 | UI-001、009～012 |
| §5.2 产业总览 | UT-023～024 | API-003 | UI-002 |
| §5.3 存栏变动 | UT-013～014、018～022 | API-004～006 | UI-003～006 |
| §5.4 年龄结构 | UT-015～017 | API-007～008 | UI-007 |
| §5.5 草场占比 | UT-025～027 | API-009～010 | UI-008 |
| §7 权限提示 | — | API-017～018 | UI-013 |
| §3 本期不实现 | — | API-021～022 | UI-014 |

---

## 五、Playwright 执行说明（摘要）

```javascript
// playwright.config 片段
use: { channel: 'chrome', locale: 'zh-CN', baseURL: process.env.RUOYI_BASE_URL }
```

1. `beforeEach`：UI 用例先 `page.goto('/login')` 登录 UA/UB/U0/ADMIN。  
2. 菜单路径以环境为准：`page.getByText('养殖产业数据').click()` 或路由 `#/livestockIndustry/breedingIndustry/index`。  
3. 接口 Mock：UI-011 可用 `page.route('**/dataModel/breedingIndustry/dashboard', route => route.fulfill({ status: 500 }))`。  
4. 图表断言：对 ECharts 容器 `.breeding-trend-chart` 等（以实现 `data-testid` 为准）检查 DOM 或拦截 XHR 校验 `monthlyTrend` 长度。

**用例统计**：单元 **29**、接口 **26**、界面 **16**，合计 **71** 条。
