DevOps平台 — 第十七篇:测试管理的设计与实现

工作项解决了「做什么」,规划解决了「什么时候做」,迭代和里程碑解决了「做到什么程度算一个阶段」。但一个需求从提出到交付,还需要回答一个问题:「质量是否达标?」------用例沉淀测试设计,评审把控准入门槛,计划组织执行节奏,报告生成快照结论。这四者构成一条完整的测试质量闭环:设计 → 评审 → 执行 → 度量。本篇记录测试管理模块的数据模型、版本化机制、状态机驱动、评审策略、执行录入、报告快照、动态接入,以及前端抽屉化交互。

一、测试管理要解决什么问题

1.1 痛点:测试与研发割裂

在测试模块上线之前,平台的测试管理处于空白状态:

问题 表现
测试设计无处沉淀 用例散落在文档、Excel、本地脚本中,无法与需求/工作项关联
评审无流程 用例写完即用,无准入评审,质量参差不齐
执行无记录 测试执行是「跑一遍」的过程,结果无持久化,无法回溯
报告靠拼凑 手工统计通过率、缺陷数,数据滞后且易错

1.2 目标

用一条完整的测试质量闭环替代手工管理:

  • 用例沉淀:功能目录组织用例,版本化沉淀每一次变更,关联工作项
  • 评审准入:对用例版本发起评审,支持单人/多人(会签/或签),通过后冻结
  • 计划执行:排期容器关联迭代/里程碑,承载用例版本与执行记录,提缺陷联动
  • 报告度量:对 1~5 个计划生成统计快照,生成后数据不再变化

二、数据模型

2.1 实体总览

测试模块共 11 张表,覆盖四类实体及其关联关系:

javascript 复制代码
biz_test_case ──────── biz_test_case_version(版本快照)
     │                        │ 
     │                        │
biz_test_case_dir        biz_test_case_work_item(用例↔工作项关联)
(功能目录树)                    │
                                 │
biz_test_review ──── biz_test_review_item(评审项 = 版本×评审人)
                         │
                         │(评审通过后的版本进入计划)
                         │
biz_test_plan ──── biz_test_plan_case(计划内用例版本)
                    │
                    ├── biz_test_execution(执行记录)
                    │       │
                    │       └── biz_test_execution_step(步骤级结果)
                    │
                    └──(提缺陷 → 创建 biz_work_item + 写 biz_test_case_work_item)

biz_test_report(报告快照,含统计 JSON)

2.2 用例与版本

用例主表(biz_test_case)存储元信息,版本表(biz_test_case_version)存储每次变更的内容快照:

用例主表字段 说明
项目内编号 如 TC-0001,项目内唯一
功能目录 ID 关联 biz_test_case_dir,空为「未分类」
标题 / 类型 / 阶段 / 优先级 元信息
负责人 / 自动化状态 管理属性
当前版本号 指向最新版本
归档标记 归档后不在列表展示
版本表字段 说明
版本号 用例内自增,从 1 开始
步骤 JSON [{seq, action, expect}],JSONB 存储
前置条件 / 预期结果 / 补充说明 Markdown
状态机 ID + 发布版本号 创建时钉死,防状态机变更影响历史版本
当前状态码 pending_review / reviewing / approved / rejected
内容冻结标记 评审通过后冻结,编辑需创建新版本

设计要点 :用例编辑采用「写时复制」策略------当前版本为 approved(评审通过)时,编辑会创建新版本(版本号 +1),旧版本保持冻结不变;当前版本为 pending_review 时直接编辑当前版本;当前版本为 reviewing(评审中)时禁止编辑。

2.3 评审与评审项

评审单(biz_test_review)是对一批用例版本的准入评审:

字段 说明
评审编号 如 RV-0001,项目内唯一
评审模式 single(单人)/ multi(多人)
多人策略 countersign(会签-全部通过)/ any(或签-任一通过)
状态机 钉死状态机 ID + 版本号,驱动生命周期
当前状态码 not_started / in_progress / suspended / finished / cancelled

评审项(biz_test_review_item)是评审单与用例版本的交叉表:

字段 说明
评审单 ID 关联评审单
用例版本 ID 被评审的版本
评审人 username
结论 pending / pass / fail
评审备注 Markdown
给出结论时间 结论锁定时记录

设计要点 :多人会签场景下,同一用例版本按评审人拆成多条评审项。评审结束时按策略聚合判定------会签需全部 pass 才通过,或签只需一条 pass 即通过。未给结论的评审项在结束时自动记为 fail。

2.4 计划与执行

测试计划(biz_test_plan)是测试活动的排期容器:

字段 说明
计划编号 如 TP-0001
关联迭代 ID 敏捷场景关联迭代
关联里程碑 ID 瀑布场景关联里程碑
产品版本 ID 预留字段,本期不使用
计划起止日期 排期
状态机 驱动生命周期

计划用例(biz_test_plan_case)是计划内引用的用例版本:

字段 说明
计划 ID 所属计划
用例版本 ID 引用的版本(评审通过版本)
执行人 指派的执行者
最新执行结果 pass / fail / blocked / untested
执行/失败/受阻次数 统计冗余

执行记录(biz_test_execution)是每次用例执行的持久化:

字段 说明
计划用例 ID 关联计划用例
用例版本 ID 执行时钉死的版本
用例级结果 pass / fail / blocked / untested
实际结果 / 备注 Markdown

步骤级结果(biz_test_execution_step)记录每个步骤的执行情况:

字段 说明
执行记录 ID 所属执行记录
步骤序号 对应用例步骤
步骤结果 pass / fail / blocked / na
步骤实际结果 文本

2.5 报告快照

测试报告(biz_test_report)是生成时刻的统计快照:

字段 说明
关联计划 ID 列表 JSON 数组,1~5 个
报告总结 Markdown,可编辑
统计快照 JSON 含用例统计、缺陷统计、执行明细、工作项列表、测试人员等
生成时间 快照时间戳

设计要点:报告采用「快照模式」------生成时从关联计划中聚合所有用例执行情况、关联工作项和缺陷,序列化为 JSON 落库。此后即使计划内数据变化,报告内容不变,需重新生成才能获取最新数据。这与里程碑/迭代的实时统计形成互补:实时统计看「现在」,报告快照看「当时」。

2.6 用例与工作项关联

关联表(biz_test_case_work_item)记录用例与工作项的多对多关系:

字段 说明
用例 ID 关联用例
工作项 ID 关联工作项(含需求/任务/缺陷)
关联来源 manual(用例表单手动关联)/ execution(执行时提缺陷自动创建)
来源版本 ID 仅 execution 场景
来源执行记录 ID 仅 execution 场景

设计要点 :关联来源区分了两种关联路径------用例设计时手动关联需求/任务(manual),执行失败时提缺陷自动关联(execution)。报告快照聚合时按此来源区分展示。

三、状态机驱动

3.1 三套独立状态机

测试模块复用平台已有的状态机框架,为三类实体各配置一套独立状态机:

实体 状态机 状态流转
用例版本 test_case_version_sm 待评审 → 评审中 → 评审通过/不通过 → (编辑创建新版本回到待评审)
评审单 test_review_sm 未开始 → 进行中 → 已结束/已作废(可挂起/继续)
测试计划 test_plan_sm 未开始 → 进行中 → 已结束/已作废(可挂起/继续)

3.2 钉死机制

每个实体在创建时钉死状态机的 ID 和发布版本号(smMachineId + smVerNo),后续流转始终基于这个固定版本,防止状态机配置变更后历史实体的状态语义发生漂移。

scss 复制代码
┌──────────────────────────────────────────────────┐
│  创建评审单                                        │
│                                                    │
│  1. 从配置中心查询状态机已发布快照                    │
│     cfgSmMachineMapper + smRuntimeService.pin()  │
│                                                    │
│  2. 钉死 machineId + verNo + initialStateCode      │
│     review.setSmMachineId(pin.getMachineId())     │
│     review.setSmVerNo(pin.getVerNo())              │
│     review.setStateCode(pin.getInitialStateCode()) │
│                                                    │
│  3. 后续流转始终使用该版本                            │
│     smRuntimeService.fire(machineId, verNo, ...)  │
│     → 返回目标状态码                                │
│     → 更新实体 stateCode                            │
│                                                    │
│  4. 展示状态名从状态机元数据查询                      │
│     stateDisplayName(smRuntimeService, ...)       │
└──────────────────────────────────────────────────┘

3.3 用例版本状态联动

用例版本的状态机与评审单的生命周期联动:

评审操作 用例版本状态变化
添加用例版本到评审 pending_review/rejected → reviewing(冻结)
评审通过 reviewing → approved(冻结)
评审不通过 reviewing → rejected(解冻)
评审作废(撤回) reviewing → pending_review(解冻)
scss 复制代码
┌─────────────────────────────────────────────────────┐
│                  用例版本生命周期                       │
│                                                     │
│  ┌──────────┐     加入评审      ┌──────────┐        │
│  │ 待评审    │ ──────────────→ │ 评审中    │        │
│  │ pending  │                  │ reviewing │        │
│  │ _review  │ ←────────────── │  (冻结)   │        │
│  └────┬─────┘     评审作废      └────┬─────┘        │
│       │                              │              │
│       │                    ┌─────────┼─────────┐   │
│       │                    │         │         │   │
│       │              评审通过    评审不通过    │   │
│       │                │         │         │   │
│       │           ┌────────┐ ┌────────┐    │   │
│       │           │评审通过 │ │不通过  │    │   │
│       │           │approved│ │rejected│    │   │
│       │           │(冻结)  │ │(解冻)  │    │   │
│       │           └───┬────┘ └───┬────┘    │   │
│       │               │          │         │   │
│       │     编辑创建新版本    可再次编辑    │   │
│       │               │          │         │   │
│       └───────────────┴──────────┘         │   │
│                                            │   │
└─────────────────────────────────────────────────────┘

3.4 评审聚合判定

评审结束时,按模式与策略聚合评审项结论:

java 复制代码
// 单人评审或仅一个评审人
if (SINGLE.equals(mode) || items.size() == 1) {
    pass = PASS.equals(items.get(0).getConclusion());
}
// 或签:任一通过即通过
else if (ANY.equals(policy)) {
    pass = items.stream().anyMatch(i -> PASS.equals(i.getConclusion()));
}
// 会签:全部通过才通过
else {
    pass = items.stream().allMatch(i -> PASS.equals(i.getConclusion()));
}

聚合后触发用例版本状态机的 pass / fail 流转,同时设置冻结标记。

四、评审设计

4.1 评审模式与策略

模式 说明 适用场景
单人评审 每个用例版本只有一个评审人 小团队/快速迭代
多人会签 所有评审人都需通过才通过 正式发布/关键路径
多人或签 任一评审人通过即通过 时间紧迫/并行评审

4.2 评审准入校验

添加用例版本到评审时有多重校验:

校验规则 说明
版本状态校验 仅 pending_review / rejected 状态的版本可加入
已定稿校验 approved 版本不可加入评审
重复占用校验 reviewing 版本若已在其他进行中评审中,不可重复加入
冻结保护 加入评审后版本内容冻结,不可编辑

4.3 评审结束自动处理

python 复制代码
┌──────────────────────────────────────────────────┐
│  结束评审                                          │
│                                                    │
│  1. 遍历所有评审项                                  │
│     ├── 未给结论的 → 自动记为 fail                  │
│     └── 已给结论的 → 保持不变                       │
│                                                    │
│  2. 按用例版本分组聚合                              │
│     ├── 单人:取唯一结论                            │
│     ├── 会签:全部 pass 才 pass                     │
│     └── 或签:任一 pass 即 pass                     │
│                                                    │
│  3. 触发用例版本状态机流转                           │
│     ├── pass → reviewing → approved(冻结)        │
│     └── fail → reviewing → rejected(解冻)        │
│                                                    │
│  4. 触发评审单状态机 → finished                     │
│  5. 记录动态                                        │
└──────────────────────────────────────────────────┘

五、计划与执行

5.1 计划生命周期

计划通过状态机驱动完整生命周期:

markdown 复制代码
未开始 ──开始──→ 进行中 ──结束──→ 已结束
  │                │                 │
  │                │                 │
  │              挂起                不可回退
  │                │
  │              挂起中 ──继续──→ 进行中
  │
  └──作废──→ 已作废(终态)

约束:
  - 进行中才能执行用例
  - 挂起状态不可执行(需先恢复)
  - 已结束/已作废不可编辑结构

5.2 用例版本占用与冲突检测

同一用例版本在同一时间只能被一个未结束计划占用:

java 复制代码
private boolean isVersionInActivePlan(Long caseVersionId, Long excludePlanId) {
    List<TestPlanCase> planCases = testPlanCaseMapper.selectList(
        new LambdaQueryWrapper<TestPlanCase>()
            .eq(TestPlanCase::getCaseVersionId, caseVersionId));
    Set<Long> planIds = planCases.stream()
        .map(TestPlanCase::getPlanId)
        .collect(Collectors.toSet());
    if (excludePlanId != null) planIds.remove(excludePlanId);
    // 检查这些计划是否有未结束的
    for (TestPlan plan : listByIds(planIds)) {
        if (isPlanNonFinal(plan)) return true;
    }
    return false;
}

5.3 执行录入

执行录入支持用例级和步骤级两层结果:

层级 字段 必填 说明
用例级 结果 是 pass / fail / blocked / untested
用例级 实际结果 / 备注 否 Markdown
步骤级 步骤结果 否 pass / fail / blocked / na
步骤级 步骤实际结果 否 文本

设计要点:步骤级结果可留空,用例级结果必填。执行后只有最新一条记录可修改,历史记录只读。执行失败/受阻时可直接提缺陷,自动创建工作项并建立用例↔缺陷关联。

5.4 提缺陷联动

ini 复制代码
┌──────────────────────────────────────────────────┐
│  执行用例 → 结果为 fail/blocked                     │
│                                                    │
│  1. 用户点击「提缺陷」                              │
│     → 打开 WorkItemFormDrawer                      │
│     → 锁定项目 + 工作项类型=缺陷                     │
│     → 预填描述(前置条件/步骤/实际结果)              │
│                                                    │
│  2. 保存缺陷                                        │
│     → 创建 biz_work_item(缺陷)                    │
│     → 写 biz_test_case_work_item                   │
│       relSource = 'execution'                      │
│       caseVersionId = 执行时版本                    │
│       executionId = 当前执行记录                     │
│                                                    │
│  3. 缺陷出现在:                                    │
│     ├── 计划详情「关联缺陷」Tab                     │
│     ├── 用例详情「关联工作项」Tab                   │
│     └── 报告快照「缺陷列表」+「缺陷统计」           │
└──────────────────────────────────────────────────┘

六、报告快照

6.1 生成流程

bash 复制代码
┌──────────────────────────────────────────────────────┐
│  生成报告                                              │
│                                                       │
│  1. 校验:1~5 个计划,均属于当前项目                    │
│                                                       │
│  2. 聚合统计(buildSnapshot)                           │
│     ├── 遍历每个计划 → 加载计划用例 → 收集版本 ID       │
│     ├── 批量加载用例版本 + 用例主表                      │
│     ├── 统计执行结果分布(pass/fail/blocked/untested)  │
│     ├── 聚合关联工作项(从 biz_test_case_work_item)    │
│     │   ├── 区分 manual / execution 来源              │
│     │   └── 批量加载工作项 + 类型 + 状态机元数据        │
│     ├── 缺陷统计(总数/已解决/按用例分组计数)            │
│     ├── 测试人员(执行人去重)                          │
│     └── 测试时间范围(各计划最早开始/最晚结束)           │
│                                                       │
│  3. 序列化为 JSON → 落库 biz_test_report.snapshot      │
│                                                       │
│  4. 记录动态                                           │
└──────────────────────────────────────────────────────┘

6.2 报告组件

报告详情以多组件卡片形式展示,组件顺序固定:

组件 内容
基本信息 关联计划、负责人、周期
整体概览 用例总数、通过率、失败/受阻/未执行数
用例结果统计 按计划分组表 + 计划内目录分布
缺陷概览 严重度分布 + 修复状态分布
人员缺陷分布 横向柱状图
缺陷列表 编号/标题/严重度/状态/负责人
工作项列表 关联需求/任务
执行过程概览 执行次数、多次失败用例 Top
用例执行详情 明细表:用例/版本/执行人/结果/次数/备注
报告总结 Markdown 渲染,可编辑

6.3 快照不可变性

报告生成后,snapshot JSON 不可变,仅 title 和 summary(Markdown 总结)可编辑。详情页顶部信息条提示:「报告为生成时刻的快照,统计不再更新。需最新数据请重新生成一份。」

七、动态接入

测试域四类实体全量接入动态模块,通过独立的 ActivityTestSupport Bean 实现注解 + 切面埋点。

7.1 作用域细分

实体 scopeType 冗余字段
用例 TEST_CASE projectId
计划 TEST_PLAN projectId + iterationId + milestoneId
评审 TEST_REVIEW projectId
报告 TEST_REPORT projectId

设计要点 :scope_type 使用 TEST_CASE / TEST_PLAN / TEST_REVIEW / TEST_REPORT 而非单一 TEST,避免四类实体 ID 撞车。项目/企业动态通过冗余 project_id 归集。

7.2 跨域 Target 聚合

计划内「移除用例 / 指派 / 执行」与评审「提交结论」的 scope 仍属于计划/评审,但 targetType + targetId 指向用例:

ini 复制代码
┌──────────────────────────────────────────────────┐
│  用例详情动态 Tab                                    │
│                                                    │
│  查询条件:targetType = 'TEST_CASE'                 │
│           targetId = 当前用例 ID                     │
│                                                    │
│  聚合结果(跨作用域时间线):                          │
│  ├── scope=TEST_CASE   创建了用例「...」             │
│  ├── scope=TEST_CASE   编辑了用例「...」              │
│  ├── scope=TEST_REVIEW 评审结论改为通过               │
│  ├── scope=TEST_PLAN   执行了用例「...」结果为通过     │
│  ├── scope=TEST_PLAN   将用例「...」指派给张明         │
│  └── scope=TEST_PLAN   将用例「...」从计划中移除      │
│                                                    │
│  一条用例的完整生命周期在一个时间线内展示               │
└──────────────────────────────────────────────────┘

7.3 事件目录

实体 动作
用例 创建 / 编辑 / 新版本 / 复制 / 归档 / 恢复 / 回滚
计划 创建 / 更新 / 复制 / 添加用例 / 移除用例 / 指派 / 取消指派 / 开始 / 挂起 / 继续 / 结束 / 作废 / 执行 / 修改执行结果
评审 创建 / 更新 / 添加用例版本 / 提交结论 / 开始 / 挂起 / 继续 / 结束 / 作废
报告 生成 / 更新

八、前端交互设计

8.1 模块布局

测试模块采用「左侧模块导航 + 右侧内容区」的卡片式布局:

复制代码
┌──────────────────────────────────────────────────────┐
│ ┌ 模块导航 ──┐ ┌ 内容区 ────────────────────────────┐ │
│ │            │ │                                      │ │
│ │  测试       │ │  PageHeader(标题 + 说明 + 新建按钮) │ │
│ │  ├ 用例     │ │  ─────────────────────────────────  │ │
│ │  ├ 评审     │ │  列表 / 详情页内容                   │ │
│ │  ├ 计划     │ │                                      │ │
│ │  └ 报告     │ │                                      │ │
│ │            │ │                                      │ │
│ └────────────┘ └──────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘

导航项与 URL query tab 同步,切换 Tab 不刷新页面。进入测试模块时隐藏项目级左侧导航,仅展示测试模块导航。

8.2 全局抽屉化

测试模块大量使用右侧抽屉(AppDrawer)承载详情,避免整页路由切换:

场景 组件 说明
用例新建/编辑 TestCaseFormDrawer 720px 抽屉,含步骤编辑器
用例预览 TestCasePreviewDrawer 只读预览,评审/计划中点击用例编号弹出
执行录入 TestExecutionDrawer 步骤级结果对照 + 提缺陷联动
报告详情 AppDrawer + TestReportDetail 全屏抽屉,多组件卡片长页
提缺陷 WorkItemFormDrawer 复用全局工作项创建组件

8.3 用例列表左树右表

用例列表采用知识库风格的「左树右表」布局:

  • 左侧:功能目录树,支持新建/重命名/删除/搜索;树节点显示用例数
  • 右侧:表格 + 筛选栏 + 分段切换(全部/我负责的/待我评审)
  • 批量操作:勾选行后出现批量条------移动到目录 / 发起评审 / 归档

8.4 计划详情操作列统一下拉

计划详情的用例清单和执行记录操作列统一使用 el-dropdown,按状态动态显示可用操作:

复制代码
用例清单操作列:
┌──────────────────┐
│ ⋯ 执行            │ ← 打开执行抽屉
│   提缺陷          │ ← 打开 WorkItemFormDrawer
│   移除            │ ← 已有执行记录时禁用 + tooltip
└──────────────────┘

执行记录操作列:
┌──────────────────┐
│ ⋯ 查看详情        │ ← 打开只读执行抽屉
│   修改            │ ← 仅最新一条可修改
└──────────────────┘

8.5 报告详情抽屉模式

报告详情不再进入新路由,而是在报告列表页内通过 AppDrawer 全屏抽屉展示:

  • 返回按钮在左上角,关闭抽屉回到列表
  • 报告总结使用 MarkdownPreview 渲染,点击「编辑名称/总结」切换为 MarkdownEditor
  • 用例执行详情中点击用例编号弹出用例预览抽屉
  • 工作项列表展示关联计划中的工作项与缺陷

九、设计要点总结

javascript 复制代码
┌──────────────────────────────────────────────────────────────┐
│                      测试模块设计要点                            │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  1. 四域闭环:用例 → 评审 → 计划 → 报告                        │
│     设计沉淀 → 准入评审 → 排期执行 → 快照度量                   │
│                                                              │
│  2. 版本化机制:写时复制,评审通过冻结                          │
│     编辑已通过版本创建新版本,旧版本保持定稿                     │
│                                                              │
│  3. 三套状态机:用例版本 / 评审单 / 计划                        │
│     创建时钉死,状态语义不随配置变更漂移                         │
│     用例版本状态与评审单生命周期联动                            │
│                                                              │
│  4. 评审策略:单人 / 多人会签 / 多人或签                        │
│     结束时按策略聚合,未给结论自动记不通过                      │
│                                                              │
│  5. 执行双层级:用例级 + 步骤级                                │
│     用例级必填,步骤级可空                                     │
│     仅最新一条可修改,历史只读                                 │
│                                                              │
│  6. 提缺陷联动:执行失败 → 创建工作项 + 自动关联                │
│     relSource 区分手动关联与执行关联                            │
│                                                              │
│  7. 报告快照:生成时刻统计 JSON 落库                           │
│     1~5 个计划聚合,生成后不变,仅标题/总结可编辑               │
│                                                              │
│  8. 动态接入:ActivityTestSupport 独立 Bean                    │
│     scopeType 细分 TEST_*,target 跨域指向用例                 │
│     用例详情动态 Tab 聚合全生命周期                            │
│                                                              │
│  9. 全局抽屉化:用例/执行/报告/提缺陷均抽屉承载                 │
│     避免整页路由切换,交互原地展开                             │
│                                                              │
│  10. 冲突检测:用例版本同一时间仅一个未结束计划占用              │
│      执行记录存在时不可移除用例                               │
│                                                              │
└──────────────────────────────────────────────────────────────┘
相关推荐
今天早睡41 分钟前
用子图封装流程,设计清晰的父子状态接口
后端
用户EasyAdminBlazor44 分钟前
用 EasyAdminBlazor + OpenCode 开发进销存:实际做了什么,花了多久
后端
ByteRock44 分钟前
同样是 Decimal,为什么 MySQL 和 ClickHouse 会写成两套世界
后端
leo在掘金44 分钟前
一天155万亿Token,DeepInfer省在哪
后端·架构
阿良别做梦1 小时前
LangGraph 学习之路:让工作流返工,也让它及时停下
后端·开源
我的div丢了肿么办1 小时前
go语言抽离路由文件以及对路由进行分组
后端·go
zz在掘金337081 小时前
Django __init__.py 高级应用速记
后端
软件工程师文艺6661 小时前
Teamcenter FMS 文件管理系统深度解析:缓存策略、SSL 部署与性能调优
后端
Gopher_HBo1 小时前
类型系统 —— Type、rtype 与运行时类型元数据
后端