
HarmonyOS 应用能通过编译,只能证明语法、类型和资源引用满足构建要求;能打开首屏,也不能证明空数据、异常路由参数、重复点击、窗口缩放、数据写入失败和页面二次进入都不会出错。对学习类应用来说,真正影响审核和用户体验的往往不是主流程,而是"第二次执行同一个动作"或"数据恰好为空"。
本文基于句匠项目 D:\huawei\one18-11 的真实源码,核对 entry/src/test、entry/src/ohosTest、EntryAbility.ets、AICorrectPage.ets、BankDetailPage.ets、CategoryPage.ets、DailyCheckInPage.ets、ExamResultPage.ets、PracticePage.ets 与 UserDataManager.ets。
本文唯一源码标识:com.jiaweikang.one18。
真实边界必须先说明:工程已经引入 @ohos/hypium 与 @ohos/hamock,入口和两个库模块也有 src/test、src/ohosTest 目录;但现有测试主要是 DevEco 模板,只断言字符串 abc 包含 b,没有覆盖业务页面、数据服务、路由参数或重复操作。本文给出的是基于源码风险设计的回归方案,不声称这些用例已经自动执行、真机通过或进入 CI。
一、先把"测试存在"和"业务有覆盖"分开
entry/src/test/LocalUnit.test.ets 当前核心用例是:
ts
it('assertContain', 0, () => {
let a = 'abc'
let b = 'b'
expect(a).assertContain(b)
expect(a).assertEqual(a)
})
entry/src/ohosTest/ets/test/Ability.test.ets 也是相同模板。它能证明测试框架入口可被组织,但不能证明句匠的任何功能正确。
业务回归覆盖至少需要回答:
text
测试对象是谁?
输入状态是什么?
执行什么动作?
期望页面、内存和磁盘如何变化?
失败时如何定位?
是否在目标设备上执行?
如果这些问题没有答案,就不能把"有 test 目录"写成"测试体系完善"。
二、从真实风险建立分层测试金字塔
句匠适合分为四层:
| 层级 | 目标 | 示例 |
|---|---|---|
| 纯逻辑单测 | 快速验证确定性函数 | 纠错规则、日期差、统计、格式化 |
| 服务单测 | 验证状态转换与存储 | 收藏切换、清空、进度更新 |
| 页面/组件测试 | 验证状态与交互 | 空输入、空题库、按钮禁用 |
| 设备回归 | 验证系统环境 | 启动、TTS、窗口缩放、安全区 |
纯逻辑用例数量可以多,运行频繁;设备回归数量少,但必须覆盖发布阻断路径。不能只靠一端。

三、启动链路是第一条阻断用例
EntryAbility.onCreate() 会:
- 设置浅色模式;
- 初始化
UserDataManager; - 创建多个
AppStorage键; - 注册
BreakpointSystem。
onWindowStageCreate() 获取主窗口、读取安全区、注册监听,然后加载 pages/SplashPage。
启动回归至少包含:
text
首次安装后启动
已有本地数据后启动
Preferences 内容异常后启动
主窗口避让区读取失败后启动
SplashPage 加载失败日志检查
后台返回前台
销毁后重新进入
源码在 Preferences 初始化失败时会创建空数组和默认设置,因此"损坏数据不白屏"是可验证目标,而不是默认认为一定成功。
四、空数据不只是显示一张空状态图
BankDetailPage 在 bank === undefined 时展示"未找到题库"。这条分支会在路由参数缺失、bankId 不存在或固定题库映射失败时触发。
需要验证:
text
无 params 进入
params 为空对象
bankId 为空字符串
bankId 不存在
bankId 类型异常
正常 bankId
期望是页面可返回、不崩溃、不调用 this.bank! 的后续逻辑。空状态上的返回操作也要实际可达。
五、AI 纠错页适合先做纯逻辑单测
AICorrectPage 内部有多条确定性正则规则,例如:
text
I very like → like ... very much
I no idea → I have no idea
interested on → interested in
recieve → receive
I yesterday go → I went yesterday
页面对空输入有明确判断:
ts
if (text.trim().length === 0) {
this.results = []
this.analyzed = true
return
}
建议把规则匹配从页面抽到纯函数,再覆盖:
text
空字符串
全空格
正常无错误句子
单个错误
同一句多个错误
大小写差异
错误词位于标点前
极长输入
重复点击分析
清空后再次分析
当前规则定义在页面文件中,尚未拆成可直接导入的测试模块,这是自动化前需要做的最小结构调整。
六、空输入与"没有发现错误"必须区分
空输入和正确句子都可能得到 results.length === 0,但产品语义不同:
- 空输入:用户没有提供可分析内容;
- 正确句子:已经分析,但没有命中规则。
源码使用 analyzed 状态控制结果区。回归需要确认空输入不会误导用户为"句子完全正确",正确句子则展示明确的无错误结果。
状态表可以写成:
| input | analyzed | results | 期望 |
|---|---|---|---|
| 空 | false | 0 | 初始输入态 |
| 空格 | true | 0 | 空输入提示/不误判 |
| 正确句 | true | 0 | 无明显错误 |
| 错误句 | true | >0 | 建议列表 |
七、每日打卡重点测重复点击
DailyCheckInPage.checkIn() 首先判断:
ts
if (this.isCheckedToday()) {
this.message = '今天已经打卡啦'
return
}
这是典型幂等保护。回归需要执行:
text
当天无记录 → 点击一次
当天已记录 → 再点击
快速连续点击两次
跨天后点击
记录只有一天
记录连续两天
记录中存在重复日期
期望当天日期只出现一次,积分与累计次数不重复增加,提示文本与按钮状态一致。
仅看代码中的 return 不够。ArkUI 事件在快速点击下是否会在状态更新前重复进入,还需真机或组件测试验证。
八、日期逻辑要测月末、年末与时区
源码将日期格式化为本地 YYYY-MM-DD,并通过毫秒差计算天数。普通日期容易通过,边界包括:
text
1月31日 → 2月1日
2月28/29日 → 3月1日
12月31日 → 1月1日
夏令时地区跨日
设备时间被手动修改
记录顺序被打乱
如果测试直接使用 new Date(),用例会随执行日期变化。更可测的设计是把"当前时间"作为参数注入纯函数。
九、ExamResultPage 存在重复写入风险
ExamResultPage.aboutToAppear() 读取路由参数,并在考试模式下追加历史:
ts
this.examHistory =
UserDataManager.addExamHistory(
this.examHistory,
history
)
如果同一页面因为生命周期再次进入 aboutToAppear(),可能重复追加同一场考试。源码没有可见的结果唯一 ID 或"已保存"标记。
必须覆盖:
text
考试结束首次进入
切后台再回来
跳转其他页后返回
旋转/窗口变化
快速点击查看结果
结果页重复构建
如果历史增加两条,就是实际缺陷。解决方案可以是生成 attemptId 并去重,或页面内保存一次性标记,但本文不声称已经修复。
十、异常路由参数不能依赖类型断言
源码常见写法:
ts
const params =
router.getParams() as ExamResultParams | undefined
as 只改变编译器视角,不会在运行时验证字段。测试要传入:
text
undefined
{}
缺少 bankId
score 为字符串
records 不是数组
负数 durationSec
correct > total
超长 bankId
页面应采用默认值、空状态或返回路径,而不是崩溃。真正严格的路由参数需要运行时守卫,不是更复杂的 TypeScript 接口。
十一、收藏、笔记和错题要测状态闭环
UserDataManager 的操作通常返回新数组,页面重新赋值给 @StorageLink。一条完整用例包含:
text
初始内存状态
执行保存/删除
返回数组
AppStorage 页面变化
Preferences 刷盘
重启恢复
收藏切换应覆盖:
text
空数组新增
已有记录删除
不同题库同题号
重复快速点击
写盘失败
清空后重启
当前 persist() 静默吞掉异常,因此写盘失败的可观察性不足。自动化可以通过存储适配器注入失败,验证页面是否需要错误状态。
十二、清除全部数据要测部分失败
设置页提供二次确认清除,但底层是多个键分别写入,并非事务。
测试不能只检查"按钮变成已清除",还要检查每个键:
text
favoriteRecords
noteRecords
wrongRecords
bankProgress
chapterProgress
examHistory
dailyReminderTime
examDurationSec
autoNextQuestion
当第三个键写入失败时,页面是否显示成功?重启后哪些数据回来?这些问题只有故障注入才能回答。
十三、TTS 回归要覆盖能力降级
练习页优先离线 TTS,失败后尝试在线引擎,并始终展示发音弹窗。设备测试矩阵包括:
| 场景 | 预期 |
|---|---|
| 离线引擎可用 | 正常朗读 |
| 离线失败、在线可用 | 在线兜底 |
| 完全无网络 | 弹窗和文本仍可用 |
| 引擎忙碌 | 停止前一次再朗读 |
| 快速重复播放 | 不叠音、不崩溃 |
| 离开页面 | stop + shutdown |
| 返回页面再播放 | 可重新初始化 |
这类能力依赖设备服务,纯单测不能替代真机验证。
十四、响应式布局要测临界宽度
句匠定义 sm/md/lg,部分页面又以 700vp 或 720vp 切换网格。回归点不应只选 360vp 和 1200vp,还要紧贴边界:
text
599 / 600 / 601vp
839 / 840 / 841vp
699 / 700 / 701vp
719 / 720 / 721vp
验证题库列表列数、详情页双栏、分类页卡片宽度、滚动区域和长标题。前文还发现 Index.ets 的根导航条件覆盖全部合法断点,侧边栏分支不可达,这应成为明确回归用例。
十五、重复点击要按操作类型分类
并不是所有按钮都用同一种防抖:
| 操作 | 风险 | 策略 |
|---|---|---|
| 打卡 | 重复记账 | 幂等判断 |
| 提交考试 | 重复生成结果 | 提交锁 + attemptId |
| 保存笔记 | 高频写盘 | 合并/保存中状态 |
| 播放 TTS | 多路叠音 | stop 前一次 |
| 路由跳转 | 重复页面 | 点击锁或路由策略 |
| 清除数据 | 破坏性重复操作 | 二次确认 + 结果校验 |
测试要验证业务结果,而不是只验证按钮暂时变灰。
十六、建议先抽离五类纯函数
当前很多逻辑位于页面结构体内部。优先抽离:
text
analyzeSentence(text)
calculateStreak(records, now)
normalizeExamParams(raw)
selectLayout(bp, width)
buildExamHistory(params)
纯函数输入输出明确,能快速覆盖异常值和边界值。页面测试再只关注状态绑定、显示与点击。
示例测试结构:
ts
it('empty input returns no suggestions', 0, () => {
const result = analyzeSentence(' ')
expect(result.length).assertEqual(0)
})
这只是建议代码,当前工程没有该函数。
十七、测试数据必须稳定且可重置
回归测试最怕共享真实 Preferences。上一个用例留下的收藏和考试记录会污染下一个用例。
每个用例应明确:
text
Arrange:准备独立存储或内存仓库
Act:执行一次操作
Assert:检查返回值、AppStorage和持久层
Cleanup:恢复初始状态
设备测试可使用专门测试包、测试存储名称或测试前清除应用数据。不要把用户真实学习数据作为测试输入。
十八、发布前烟测必须使用候选包
DevEco Preview 或单元测试通过,不能替代发布候选包:
text
安装
首次启动
进入主页
打开题库详情
完成一道练习
触发听音题
收藏与笔记
完成考试
查看结果和历史
清除数据
切换窗口尺寸
退出
卸载
需要记录实际包版本、设备类型、系统版本、结果和证据。未执行就标记 not run,不能写成通过。

十九、把回归结果变成发布门槛
建议使用三种结果:
text
passed:已执行且符合预期
failed:已执行且发现问题
not run:未执行或环境不具备
发布阻断项包括启动崩溃、核心流程不可达、数据重复写入、清除失效、权限循环、关键布局遮挡和候选包无法安装。非阻断视觉问题也要记录,但不能掩盖核心失败。
二十、结语:从模板测试走向风险测试
句匠已经有 Hypium、Hamock 和测试目录,但当前内容仍是模板断言,不能证明业务回归覆盖。真实源码已经提供了清晰的测试入口:启动初始化与容错、AI纠错空输入、题库详情异常参数、每日打卡幂等、考试结果重复写入、本地状态闭环、TTS能力降级和多设备临界宽度。
高价值回归测试不是追求用例数量,而是把最可能导致崩溃、重复数据、错误状态和审核失败的风险固定下来。先抽离纯逻辑,再测试服务状态转换,随后覆盖页面交互,最后用发布候选包完成安装---启动---核心流程---卸载烟测。
在这些测试真正执行并留下证据之前,工程只能说"已有测试框架和回归设计",不能说"已通过完整自动化测试"。这种边界意识本身,就是可靠发布流程的一部分。
本文部分内容由 AI 辅助整理,测试现状、风险点与用例均来自句匠真实源码;未虚构测试通过率、设备结果、审核结论或线上数据。