封面

灯光模拟最难回归的不是某个按钮能不能点,而是一次点击会同时改变灯光状态、倒计时、题号、反馈文字、考试结果和本地历史。首页看起来正常,并不代表超时分支、重复点击保护、随机抽题和重启恢复都正常。

这篇不再按 UI 区域介绍功能,而是把 The_kemusan 的真实入口、灯光命令、理论题、历史存储、资源和工程配置整理成可执行矩阵。每一项都标明应该在哪个层级取证,避免把“读过源码”“构建成功”“模拟器走通”和“真机可用”混成一句结论。

写用例前,先定义四种证据

流程图

证据层级操作方式能说明什么不能替代什么
静态审计对照 ETS、JSON/JSON5、资源文件分支、常量、引用关系存在编译与运行
构建执行工程的 debug/release 构建ArkTS、资源、模块配置可被构建系统接受页面交互正确
模拟器安装并完整操作主链路大部分状态迁移和本地存储行为真实屏幕、性能、系统差异
真机冷启动、前后台、重启、长时间操作设备级图标、计时、触控、持久化表现应用市场审核结果

回归报告每个结论都应带层级。例如“handleTimeout() 有失败分支”是静态事实;“等待 5 秒后页面显示未合格并写入历史”必须来自模拟器或真机操作。若只写“已验证”,后续无法判断证据到底来自哪里。

从八个首页入口建立冒烟清单

QuestionBank.etsMODULES 是入口真值,不需要临时凭记忆列模块:

export const MODULES: ModuleEntry[] = [
  { id: 'subject1', title: '科一灯光题', badge: '理论', /* ... */ },
  { id: 'subject2', title: '科二灯光基础', badge: '基础', /* ... */ },
  { id: 'subject3Exam', title: '科三灯光模拟', badge: '模拟', /* ... */ },
  { id: 'subject3Flow', title: '科三实操灯光', badge: '实操', /* ... */ },
  { id: 'subject4', title: '科四灯光题', badge: '安全', /* ... */ },
  { id: 'questionBank', title: '题库', badge: '题库', /* ... */ },
  { id: 'history', title: '错题本/历史', badge: '复盘', /* ... */ },
  { id: 'settings', title: '设置', badge: '本机', /* ... */ }
];

第一轮冒烟不追求把每个模块测深,而是确认八个入口都能从首页打开、标题正确、返回首页可用、前一个模块的计时/闪烁不会泄漏到下一个模块。

编号入口首屏应出现返回后重点
S-01科一灯光题理论题面与选项重新进入时题组能初始化
S-02科二灯光基础三个分类标签分类切换后题目属于对应类别
S-03科三灯光模拟状态行、指令面板、六个按钮离开时计时停止
S-04科三实操灯光实操状态行、九个动作离开时交替闪烁停止
S-05科四灯光题安全文明类题面记录 subject 为 subject4
S-06题库学科标签和搜索框关键词清空后结果复位
S-07错题本/历史筛选标签或空态过滤条件不破坏原始记录
S-08设置清空本地练习数据清空后首页统计同步归零

科三灯光模拟要覆盖固定首尾、随机中段与五秒时限

startSubjectThreeExam() 先关闭灯光并重置计数,再由 selectLightCommands() 组出固定首题、最多六个随机中间题和固定关闭题:

private selectLightCommands(): LightCommand[] {
  const candidates: LightCommand[] = [];
  for (let index = 1; index < LIGHT_COMMANDS.length; index++) {
    candidates.push(LIGHT_COMMANDS[index]);
  }
  // Fisher-Yates 打乱 candidates
  const result: LightCommand[] = [];
  result.push(LIGHT_COMMANDS[0]);
  const maxCount = Math.min(RANDOM_SCENE_QUESTION_COUNT, candidates.length);
  for (let index = 0; index < maxCount; index++) {
    result.push(candidates[index]);
  }
  result.push(CLOSE_COMMAND);
  return result;
}

基于这段真实规则,科三矩阵至少包含:

编号前置状态操作预期状态/反馈记录期望
L-01未开始点任意灯光按钮进入演示,提示“不计入考试”不新增考试记录
L-02灯光任意点开始考试灯光回到关闭;题号从 1 开始;剩余 5 秒暂不写记录
L-03首题“开启前照灯”5 秒内点近光计数加一,随后进入下一题完成整场后统一记录
L-04中间题点错误动作立即结束,显示正确动作passed=false,保存误选与正确项
L-05远光/双闪等题不操作至 0 秒结束并提示超时保存错误题 id,误选为空
L-06当前题要求近光且灯光已为近光保持不动至 0 秒handleTimeout() 按正确处理正确数增加
L-07当前题为远近交替点交替高低光以 160ms 节拍切换 6 次,最终回近光不产生重复记录
L-08通过全部中段最后一题关闭全部显示考试合格分数、总题数、耗时完整
L-09考试进行中连续快速点两个按钮actionLocked 阻止第二次判题只生成一个结果
L-10考试进行中点首页定时器和交替闪烁停止不应误写“合格”记录

特别容易漏掉的是 L-06:源码允许“题目要求近光且当前本来就是近光”在超时时视作保持正确。这是业务规则,不应把所有超时统一断言为失败。

科三实操使用固定顺序,不能套用随机题预期

实操模式的 selectPracticalCommands() 只是按数组顺序复制 18 条命令,没有打乱:

private selectPracticalCommands(): PracticalCommand[] {
  const result: PracticalCommand[] = [];
  for (let index = 0; index < PRACTICAL_COMMANDS.length; index++) {
    result.push(PRACTICAL_COMMANDS[index]);
  }
  return result;
}

因此实操应单独建矩阵:

编号场景正确动作核对点
P-01起步左转向第一条固定为 p_start_left
P-02变道结束关闭转向ACTION_CLOSE_SIGNAL 能清除转向状态
P-03夜间超车提醒远近交替交替动画完成后回近光
P-04驶回原车道右转向状态行右转向激活
P-05雨雪天气近光加示廓近光状态也应被视为激活
P-06雾天雾灯加双闪使用 ACTION_FOG
P-07考试结束关闭全部完成全部 18 条后合格
P-08任意一步误点错误动作立即不合格,不跳过继续
P-09任意一步超时不操作 5 秒实操超时一律失败

实操文案中出现“保持 3 秒以上”,但当前判题代码只比较按钮 action,并没有计量转向灯保持时长。回归报告应写成“文案存在 3 秒要求,当前实现未见对应计时判定”,不能用文案替代功能实现。

理论题要回归随机上限、首次选择锁定和记录内容

理论练习从符合学科/类别的题目中打乱,最多取 20 道。答题函数用 selectedAnswer 拒绝同题第二次选择:

private answerTheoryQuestion(answer: string): void {
  if (this.selectedAnswer.length > 0 || this.activeQuestions.length === 0) {
    return;
  }
  const question = this.getCurrentQuestion();
  const correctAnswer = getQuestionAnswer(question);
  const correct = answer === correctAnswer;
  this.selectedAnswer = answer;
  this.answerCorrect = correct;
  this.answerMessage = correct
    ? '回答正确'
    : `回答错误,正确选项:${correctAnswer}. ${this.getOptionText(question, correctAnswer)}`;
  this.addSimpleRecord(/* subject、mode、正确项、误选项 */);
}

建议执行下面几组:

编号操作预期
T-01进入科一/科四题组非空且不超过 20 道
T-02科二切换“图标认知/上车检查/夜间场景”题目 category 与当前标签一致
T-03选择正确项反馈“回答正确”,新增合格记录
T-04选择错误项显示正确项,记录 wrongQuestionId、正确文本和误选文本
T-05同一题连续点两个选项第二次点击无效,只新增一条记录
T-06点“下一题”清空选中与反馈状态,进入后续题
T-07点“重新开始”重新抽题并把索引归零

随机测试不能断言每次第一题都不同;随机本身允许重复结果。应断言题目来自正确集合、数量上限正确、每项结构完整。若要验证洗牌分布,需要单独的统计方法,而不是用两次肉眼对比得结论。

题库搜索要覆盖空关键词、学科隔离和 40 条上限

refreshQuestionBankResults() 会先 trim().toLowerCase(),空关键词直接清空结果;非空时只遍历当前学科,并在 40 条时停止。匹配范围包含标题、解析、类别和选项文本。

Q-01  输入全空格            → 结果应为空
Q-02  输入题目中的中文片段  → 返回当前学科匹配项
Q-03  切换学科              → 旧学科结果不能残留
Q-04  输入常见高频词        → 结果最多 40 条
Q-05  输入选项中的文字      → 对应题目也可命中
Q-06  清空输入              → LazyDataSource 重新加载空数组

页面使用 QuestionBankLazyDataSource 并在 setQuestions() 后通知 onDataReloaded()。若数据数组已经变化但列表没刷新,排查重点是 data source 监听和 LazyForEach,而不是继续修改搜索匹配规则。

结构图

历史与 Preferences 必须通过“重启后仍存在”来证明

PracticeStore 的存储名是 kemusan_exam_store,键是 exam_history,最多保留 100 条。新增时先读取并排序,再把新记录放到最前面并 flush()

async addRecord(record: PracticeRecord): Promise<PracticeRecord[]> {
  const records = await this.listRecords();
  records.unshift(record);
  const nextRecords = records.slice(0, MAX_HISTORY_COUNT);
  await this.putString(HISTORY_KEY, JSON.stringify(nextRecords));
  return nextRecords;
}

async clearRecords(): Promise<void> {
  await this.putString(HISTORY_KEY, '[]');
}

持久化矩阵应覆盖:

编号操作预期
D-01完成一道理论题历史第一条时间最新、subject/mode 正确
D-02科三错误结束记录正确动作、误选动作、耗时和题目 id
D-03完成多条后切换筛选“全部/科一/科二/科三/科四/错题”数量对应原始记录
D-04杀掉应用再启动记录仍可从 Preferences 读取
D-05清空历史列表变空,首页练习/合格/错题统计归零
D-06清空后重启空状态保持,不被旧内存数据恢复
D-07构造超过 100 条只保留最新 100 条
D-08读取旧版缺省字段normalizeRecord() 提供 subject/mode 等回退

还有一个风险要主动观察:PracticeStore.init/getString/putString 捕获异常后回退或直接返回,UI 可能继续工作但数据没有真正落盘。因此 D-04 是不可省略的;只看到当前会话列表新增,不能证明 Preferences 写入成功。

生命周期与重置项专门防止“离开页面还在跑”

goHome()openModule()aboutToDisappear() 都会停止倒计时和交替闪烁的两个 interval,而 resetSubjectThreeExam() 负责把相关状态字段恢复初值。这里不能扩展为“所有异步任务都已停止”:答对后进入下一题的两处 setTimeout 尚未保存句柄。建议把这些路径作为独立回归组:

aboutToDisappear(): void {
  this.stopTimer();
  this.stopAltFlash();
}

private goHome(): void {
  this.stopTimer();
  this.stopAltFlash();
  this.currentPage = PAGE_HOME;
  this.pageTitle = '驾考灯光综合助手';
}
编号操作应重点观察
R-01考试计时中返回首页秒数不再继续变化,也不触发超时记录
R-02交替闪烁中返回首页高低光切换停止
R-03科三返回首页后进入题库前一模块的 timer/flash 已停止
R-04重置灯光lightState=closed,未考试时提示“灯光已复位”
R-05重置实操题号、总数、反馈、命令数组全部回初始值
R-06应用切到后台再回来记录当前源码行为,并确认没有幽灵倒计时
R-07答对后在 550ms/1000ms 延迟窗口内返回首页或重新开始目标行为是旧回调不得推进新会话;当前实现未取消该任务,需作为风险项验证

源码只有页面组件的 aboutToDisappear(),没有专门的前后台恢复策略。R-06 应以设备实测为准,并根据结果决定是否需要在 Ability 生命周期中补暂停/恢复,不要先假设框架会替业务计时器做选择。

资源与工程配置使用静态矩阵,不和业务用例混写

编号核对文件静态期望还需什么证据
C-01base/dark color.json同名键完整;当前 15 项同值设备切换模式后的实际外观
C-02main_pages.json只登记 pages/Index构建与冷启动
C-03EntryAbility.etsloadContent('pages/Index') 与 profile 一致模拟器启动日志
C-04AppScope layered_image.json前景和背景文件存在桌面图标实机截图
C-05entry layered_image.json启动窗图标资源存在冷启动录屏
C-06module.json5pages、Ability、backup metadata 引用有落点模块构建
C-07backup_config.jsonallowToBackupRestore=true不能证明历史数据已经恢复
C-08根 build-profileSDK、产品、模块关系存在debug/release 构建结果

签名路径、证书、profile 和任何口令字段只在受控环境核对,不进入截图或公开报告。静态检查通过以后,工程仍应按实际交付要求运行 debug 构建;准备发布时再补 release 构建和签名包验证。

当前自动化测试只是脚手架,业务矩阵仍需落地

工程的 entry/src/testentry/src/ohosTest 文件存在,但内容仍是模板式 assertContain 示例,没有覆盖 QuestionBank、灯光状态机或 PracticeStore。因此“有 test 目录”不能写成“业务回归已有自动化”。

最适合先自动化的是纯数据规则:

优先级 A:selectLightCommands 的固定首尾、数量和候选范围
优先级 A:getStats 与历史筛选
优先级 A:normalizeRecord 的旧字段回退
优先级 B:题库学科/类别筛选与 20 条上限
优先级 B:搜索匹配与 40 条上限
保留手工:倒计时、交替闪烁、触控、冷启动图标、前后台、重启持久化

目前这些逻辑大多是 Index 私有方法,直接写单元测试会受页面组件限制。后续若要补自动化,先把“抽题、过滤、统计、记录规范化”提取为无 UI 副作用的函数或服务,再写 Hypium 用例;不要为了测试去暴露所有页面私有状态。

按 P0、P1、P2 安排每次发版回归

优先级每次必须执行适用时机
P0八入口冒烟、科三正确/错误/超时、理论首次选择、历史写入、返回停止计时每次业务或 UI 改动
P1实操 18 步、题库搜索、筛选、清空、应用重启恢复、深浅模式准备测试包
P2100 条上限、旧记录兼容、长时间闪烁、前后台、字体放大、不同真机发布候选包

执行顺序建议固定为:先静态检查改动文件,再构建,然后在干净数据状态跑 P0,接着构造有数据状态跑 P1/P2。失败时不要立即清空环境,应先保存题目 id、动作、当前状态、时间和历史记录截图,保证问题能够复现。

问题单可以使用下面这份最小字段:

用例编号:L-04
环境:系统版本 / 设备或模拟器 / 构建模式
前置数据:历史条数、当前灯光、是否首次安装
操作:从首页进入科三模拟 → 开始 → 在指定题点错误动作
预期:立即不合格,显示正确动作,新增一条失败记录
实际:
日志/截图/录屏:
源码定位:handleLightAction → finishLightExam → addSimpleRecord
证据层级:模拟器 / 真机

结语:回归矩阵要跟状态机一起维护

这套清单不是把功能名称排成表格,而是把每条用户路径连接到真实常量、状态字段、处理函数和持久化结果。灯光模拟看似只有几个按钮,实际至少包含随机题组、固定序列、五秒倒计时、交替闪烁、重复点击锁、理论题选择、搜索与 100 条本地历史。

后续新增一个动作时,至少同步更新动作常量、状态映射、判题分支和对应的 L/P 用例;新增一个存储字段时,则同步补写入、旧数据回退、重启恢复和清空用例。让源码变化能追到矩阵,让矩阵结果带着明确证据层级,才算真正可回归。

Logo

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

更多推荐