第56篇:mock数据生成与测试

概述

在软件开发过程中,特别是在演示(Demo)阶段,高质量的演示数据是展示系统功能和验证业务逻辑的关键。星办OA 在 ApprovalDomain.ts 中设计了 createDemoApprovalscreateDemoMessages 两个工厂函数,用于生成 7 条审批数据和 5 条消息数据,覆盖了不同的审批类型、状态和紧急程度。本文将深入分析演示数据的设计思想、工厂函数的实现以及演示数据在开发测试中的价值。

一、演示数据的重要性

1.1 为什么需要演示数据

在企业级应用的开发过程中,演示数据扮演着多重角色:

  • 功能展示:向客户和 stakeholders 展示系统的完整功能
  • 开发自测:开发人员在编码过程中验证功能正确性
  • UI 调试:测试不同状态下的 UI 展示效果
  • 边界测试:验证极值条件和异常场景
  • 演示环境:在没有真实数据的环境中进行产品演示

1.2 演示数据的设计原则

星办OA 的演示数据遵循以下设计原则:

  • 场景覆盖:覆盖所有审批类型、状态和紧急程度
  • 数据真实性:模拟真实企业场景,数据可读性强
  • 关系关联:审批数据与消息数据之间存在关联关系
  • 可测试性:支持自动化测试的断言验证

二、审批数据工厂

2.1 辅助函数

function newApproval(id: string, type: string, title: string, applicant: string,
  department: string, summary: string): ApprovalRequest {
  let item: ApprovalRequest = new ApprovalRequest()
  item.id = id
  item.type = type
  item.title = title
  item.applicant = applicant
  item.applicantDepartment = department
  item.summary = summary
  return item
}

newApproval 是一个精简的工厂函数,只初始化审批对象的核心字段(ID、类型、标题、申请人、部门、业务明细),其他字段由调用方根据具体场景补充设置。这种设计避免了构造函数参数过多的问题,提高了代码的可读性。

2.2 7 条审批数据的完整分析

export function createDemoApprovals(): ApprovalRequest[] {
  // 1. 报销 - 待审批(紧急)
  let expense: ApprovalRequest = newApproval('A20260824001', '报销',
    '客户拜访交通费报销', '周婷', '销售一部', '¥286.50 · 3 张票据')
  expense.reason = '拜访重点客户产生的往返交通费用。'
  expense.currentNode = '直属主管审批'
  expense.approver = '李明'
  expense.submittedAt = '今天 09:20'
  expense.updatedAt = '今天 09:20'
  expense.urgency = '紧急'
  expense.pendingForMe = true

  // 2. 请假 - 待审批(多级审批)
  let leave: ApprovalRequest = newApproval('A20260823007', '请假',
    '陈昊的年假申请', '陈昊', '研发中心', '8月26日—8月27日 · 2天')
  leave.reason = '返乡处理家庭事务,工作已完成交接。'
  leave.currentNode = '直属主管审批'
  leave.approver = '李明'
  leave.submittedAt = '8月23日 16:40'
  leave.updatedAt = '8月23日 16:40'
  leave.pendingForMe = true
  leave.hasNextNode = true    // 多级审批,需要继续流转

  // 3. 出差 - 我发起的(审批中)
  let trip: ApprovalRequest = newApproval('A20260822012', '出差',
    '上海客户现场支持', '李明', '产品运营部', '8月27日—8月29日 · 预算 ¥3,200')
  trip.reason = '配合销售团队完成方案确认和现场交付。'
  trip.currentNode = '部门负责人审批'
  trip.approver = '王芳'
  trip.submittedAt = '8月22日 14:18'
  trip.updatedAt = '8月22日 14:18'
  trip.isMine = true

  // 4. 采购 - 已驳回(我发起的)
  let purchase: ApprovalRequest = newApproval('A20260821005', '采购',
    '产品部显示器采购', '李明', '产品运营部', '27 英寸显示器 · ¥2,499')
  purchase.reason = '设计评审与多窗口办公设备升级。'
  purchase.status = ApprovalStatus.REJECTED
  purchase.currentNode = '流程已结束'
  purchase.approver = '王芳'
  purchase.submittedAt = '8月21日 10:05'
  purchase.updatedAt = '8月21日 17:32'
  purchase.comment = '请补充三家供应商询价记录。'
  purchase.isMine = true

  // 5. 报销 - 已通过(我发起的)
  let teamMeal: ApprovalRequest = newApproval('A20260818009', '报销',
    '团队午餐费用', '李明', '产品运营部', '团队午餐 · ¥468.00')
  teamMeal.reason = '季度复盘会议工作餐。'
  teamMeal.status = ApprovalStatus.APPROVED
  teamMeal.currentNode = '流程已完成'
  teamMeal.approver = '王芳'
  teamMeal.submittedAt = '8月18日 13:24'
  teamMeal.updatedAt = '8月19日 09:12'
  teamMeal.comment = '费用合理,同意报销。'
  teamMeal.isMine = true

  // 6. 加班 - 已通过(已处理)
  let overtime: ApprovalRequest = newApproval('A20260817003', '加班',
    '版本发布支持加班', '赵悦', '研发中心', '8月18日 19:00—22:00 · 3小时')
  overtime.reason = '配合移动端版本发布。'
  overtime.status = ApprovalStatus.APPROVED
  overtime.currentNode = '流程已完成'
  overtime.approver = '李明'
  overtime.submittedAt = '8月17日 18:10'
  overtime.updatedAt = '8月17日 18:35'
  overtime.comment = '已确认发布安排,同意。'
  overtime.handledByMe = true

  // 7. 用印 - 已驳回(已处理)
  let seal: ApprovalRequest = newApproval('A20260816011', '用印',
    '渠道合作协议用印', '孙浩', '商务拓展部', '合作协议 · 2 份')
  seal.reason = '渠道年度合作协议签署。'
  seal.status = ApprovalStatus.REJECTED
  seal.currentNode = '流程已结束'
  seal.approver = '李明'
  seal.submittedAt = '8月16日 11:06'
  seal.updatedAt = '8月16日 15:20'
  seal.comment = '合同编号与法务备案记录不一致。'
  seal.handledByMe = true

  // 添加事件记录
  addSubmitEvent(expense)
  addSubmitEvent(leave)
  addSubmitEvent(trip)
  addSubmitEvent(purchase)
  purchase.events.push(newApprovalEvent('部门负责人审批', '王芳',
    purchase.comment, purchase.updatedAt, '驳回'))
  addSubmitEvent(teamMeal)
  teamMeal.events.push(newApprovalEvent('部门负责人审批', '王芳',
    teamMeal.comment, teamMeal.updatedAt, '同意'))
  addSubmitEvent(overtime)
  overtime.events.push(newApprovalEvent('直属主管审批', '李明',
    overtime.comment, overtime.updatedAt, '同意'))
  addSubmitEvent(seal)
  seal.events.push(newApprovalEvent('直属主管审批', '李明',
    seal.comment, seal.updatedAt, '驳回'))

  return [expense, leave, trip, purchase, teamMeal, overtime, seal]
}

2.3 场景覆盖度分析

审批类型覆盖(6/6):

类型 演示数据 覆盖状态

请假 陈昊的年假申请 审批中
报销 客户拜访交通费报销、团队午餐费用 审批中、已通过
出差 上海客户现场支持 审批中
采购 产品部显示器采购 已驳回
加班 版本发布支持加班 已通过
用印 渠道合作协议用印 已驳回

审批状态覆盖(4/4):

状态 数据条数 代表数据

审批中 3 报销、请假、出差
已通过 2 团队午餐、加班
已驳回 2 采购、用印
已撤回 0 无(演示数据不含撤回状态)

用户角色覆盖(3/3):

角色 数据条数 代表数据

待我审批 2 报销(紧急)、请假
我发起的 3 出差、采购、团队午餐
已处理 2 加班、用印

紧急程度覆盖: 普通(6 条)+ 紧急(1 条)

三、消息数据工厂

3.1 消息工厂函数

function newMessage(id: string, category: string, title: string, content: string,
  approvalId: string, createdAt: string, isRead: boolean): ApprovalMessage {
  let item: ApprovalMessage = new ApprovalMessage()
  item.id = id
  item.category = category
  item.title = title
  item.content = content
  item.approvalId = approvalId
  item.createdAt = createdAt
  item.isRead = isRead
  return item
}

3.2 5 条消息数据

export function createDemoMessages(): ApprovalMessage[] {
  return [
    newMessage('M001', '待办提醒', '你有一笔报销待审批',
      '周婷提交了"客户拜访交通费报销"。', 'A20260824001', '09:20', false),
    newMessage('M002', '待办提醒', '你有一条请假申请待审批',
      '陈昊申请 8 月 26 日至 27 日年假。', 'A20260823007', '昨天 16:40', false),
    newMessage('M003', '审批结果', '采购申请被驳回',
      '产品部显示器采购需要补充询价记录。', 'A20260821005', '8月21日', false),
    newMessage('M004', '审批结果', '报销申请已通过',
      '团队午餐费用已完成审批。', 'A20260818009', '8月19日', true),
    newMessage('M005', '系统通知', '移动办公安全提醒',
      '请勿将审批截图和企业数据转发到外部平台。', '', '8月18日', true),
  ]
}

3.3 消息数据的关联关系

消息数据与审批数据通过 approvalId 字段关联:

消息ID 分类 关联审批ID 关联审批名称 已读状态

M001 待办提醒 A20260824001 客户拜访交通费报销 未读
M002 待办提醒 A20260823007 陈昊的年假申请 未读
M003 审批结果 A20260821005 产品部显示器采购 未读
M004 审批结果 A20260818009 团队午餐费用 已读
M005 系统通知 (无关联) 安全提醒 已读

四、事件数据构建

4.1 提交事件统一添加

function addSubmitEvent(item: ApprovalRequest): void {
  item.events.push(newApprovalEvent('提交申请', item.applicant,
    '已提交申请', item.submittedAt, '提交'))
}

4.2 审批事件按场景添加

每条审批数据的事件构建方式不同:

审批 事件数 事件序列

报销(待审批) 1 提交
请假(待审批-多级) 1 提交
出差(我发起的) 1 提交
采购(已驳回) 2 提交 → 驳回
团队午餐(已通过) 2 提交 → 同意
加班(已处理-已通过) 2 提交 → 同意
用印(已处理-已驳回) 2 提交 → 驳回

五、演示数据的测试价值

5.1 自动化测试验证

approval-domain.test.mjs 使用演示数据进行了全面的自动化测试:

const seed = createDemoApprovals()
const stats = getDashboardStats(seed, createDemoMessages())
assert.deepEqual(
  { pending: stats.pending, mine: stats.mine, handled: stats.handled, unread: stats.unread },
  { pending: 2, mine: 3, handled: 2, unread: 3 },
)

// 过滤引擎测试
assert.equal(filterApprovals(seed, ApprovalView.PENDING, '全部', '').length, 2)
assert.equal(filterApprovals(seed, ApprovalView.MINE, '报销', '').length, 1)
assert.equal(filterApprovals(seed, ApprovalView.PENDING, '全部', '周婷').length, 1)

// 同意操作测试
const finalApproval = approveApproval(seed, 'A20260824001', '', '测试时间')
assert.equal(finalApproval.success, true)
assert.equal(getApprovalById(finalApproval.approvals, 'A20260824001')?.status, ApprovalStatus.APPROVED)

// 转发测试(多级审批)
const forwardedApproval = approveApproval(seed, 'A20260823007', '工作已交接', '测试时间')
assert.equal(forwardedApproval.success, true)
assert.equal(getApprovalById(forwardedApproval.approvals, 'A20260823007')?.status, ApprovalStatus.PENDING)
assert.equal(getApprovalById(forwardedApproval.approvals, 'A20260823007')?.approver, '王芳')

// 驳回测试(空原因验证)
const emptyReject = rejectApproval(seed, 'A20260824001', '   ', '测试时间')
assert.equal(emptyReject.success, false)
assert.equal(emptyReject.message, '请填写驳回原因')

// 撤回测试
const withdrawn = withdrawApproval(seed, 'A20260822012', '测试时间')
assert.equal(withdrawn.success, true)
assert.equal(getApprovalById(withdrawn.approvals, 'A20260822012')?.status, ApprovalStatus.WITHDRAWN)

测试覆盖的核心业务逻辑:

  • 仪表盘统计:验证 getDashboardStats 的待办、我发起的、已处理、未读数统计
  • 过滤引擎:验证视图过滤、类型过滤、关键词搜索的正确性
  • 同意操作:验证单级审批(通过)和多级审批(流转)两种场景
  • 驳回操作:验证驳回原因必填的规则
  • 撤回操作:验证撤回条件和状态变更
  • 提交操作:验证表单验证和单号重复检测
  • 消息处理:验证待办转审批结果的消息状态变更

六、演示数据的重置机制

6.1 ApprovalStore 中的数据重置

resetDemoData(): void {
  this.approvals = createDemoApprovals()
  this.messages = createDemoMessages()
}

resetDemoData 方法允许用户在一键操作后将所有审批数据和消息数据恢复到初始状态。这在演示环境中非常有用——当演示过程中数据被各种操作"污染"后,可以一键恢复到干净状态。

6.2 初始化的数据流向

AppStorageV2 初始化
  → ApprovalStore 构造函数
    → createDemoApprovals() → 7 条审批数据
    → createDemoMessages()  → 5 条消息数据
    → EmployeeProfile       → 默认用户信息

七、演示数据的设计价值

7.1 开发阶段的效率提升

  • 即时可用:应用启动后立即展示完整数据,无需手动创建
  • 场景丰富:覆盖 6 种类型、4 种状态、3 种角色视角
  • 所见即所得:UI 开发人员可以立即看到各种状态下的 UI 效果

7.2 测试阶段的可靠性

  • 可复现性:每次调用 createDemoApprovals() 都生成相同的数据
  • 确定性:测试用例基于固定数据,结果可预测
  • 独立性:测试用例之间互不干扰,每次调用都生成新的对象

7.3 演示阶段的专业度

  • 数据真实感:申请人、部门、时间等信息都模拟真实企业场景
  • 流程完整:审批中的数据有对应的事件记录,流程树清晰可见
  • 关联性:审批数据与消息数据之间的关联关系,展示了系统各模块的联动

八、总结

通过 createDemoApprovalscreateDemoMessages 两个工厂函数,星办OA 构建了一个覆盖全面、数据真实、可测试性强的演示数据体系。7 条审批数据涵盖了 6 种审批类型、4 种审批状态和 3 种用户角色,配合 5 条消息数据,形成了一个完整的演示数据生态系统。

演示数据的设计不仅仅是简单的数据填充,而是融合了业务场景分析、测试驱动开发、用户体验设计等多方面的考量。它是连接开发、测试和演示三个环节的桥梁,在加速开发效率、提高测试覆盖率和提升演示效果方面发挥着不可替代的作用。

源文件路径: commons/common/src/main/ets/model/ApprovalDomain.ts

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐