公司动态

灯光模拟HarmonyOS应用实战-20-把灯光模拟应用整理成可回归的测试清单

📅 2026/9/1 16:19:46
灯光模拟HarmonyOS应用实战-20-把灯光模拟应用整理成可回归的测试清单
灯光模拟最难回归的不是某个按钮能不能点而是一次点击会同时改变灯光状态、倒计时、题号、反馈文字、考试结果和本地历史。首页看起来正常并不代表超时分支、重复点击保护、随机抽题和重启恢复都正常。这篇不再按 UI 区域介绍功能而是把The_kemusan的真实入口、灯光命令、理论题、历史存储、资源和工程配置整理成可执行矩阵。每一项都标明应该在哪个层级取证避免把“读过源码”“构建成功”“模拟器走通”和“真机可用”混成一句结论。写用例前先定义四种证据证据层级操作方式能说明什么不能替代什么静态审计对照 ETS、JSON/JSON5、资源文件分支、常量、引用关系存在编译与运行构建执行工程的 debug/release 构建ArkTS、资源、模块配置可被构建系统接受页面交互正确模拟器安装并完整操作主链路大部分状态迁移和本地存储行为真实屏幕、性能、系统差异真机冷启动、前后台、重启、长时间操作设备级图标、计时、触控、持久化表现应用市场审核结果回归报告每个结论都应带层级。例如“handleTimeout()有失败分支”是静态事实“等待 5 秒后页面显示未合格并写入历史”必须来自模拟器或真机操作。若只写“已验证”后续无法判断证据到底来自哪里。从八个首页入口建立冒烟清单QuestionBank.ets的MODULES是入口真值不需要临时凭记忆列模块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 为subject4S-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中间题点错误动作立即结束显示正确动作passedfalse保存误选与正确项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_leftP-02变道结束关闭转向ACTION_CLOSE_SIGNAL能清除转向状态P-03夜间超车提醒远近交替交替动画完成后回近光P-04驶回原车道右转向状态行右转向激活P-05雨雪天气近光加示廓近光状态也应被视为激活P-06雾天雾灯加双闪使用ACTION_FOGP-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): PromisePracticeRecord[] { 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(): Promisevoid { await this.putString(HISTORY_KEY, []); }持久化矩阵应覆盖编号操作预期D-01完成一道理论题历史第一条时间最新、subject/mode 正确D-02科三错误结束记录正确动作、误选动作、耗时和题目 idD-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重置灯光lightStateclosed未考试时提示“灯光已复位”R-05重置实操题号、总数、反馈、命令数组全部回初始值R-06应用切到后台再回来记录当前源码行为并确认没有幽灵倒计时R-07答对后在 550ms/1000ms 延迟窗口内返回首页或重新开始目标行为是旧回调不得推进新会话当前实现未取消该任务需作为风险项验证源码只有页面组件的aboutToDisappear()没有专门的前后台恢复策略。R-06 应以设备实测为准并根据结果决定是否需要在 Ability 生命周期中补暂停/恢复不要先假设框架会替业务计时器做选择。资源与工程配置使用静态矩阵不和业务用例混写编号核对文件静态期望还需什么证据C-01base/darkcolor.json同名键完整当前 15 项同值设备切换模式后的实际外观C-02main_pages.json只登记pages/Index构建与冷启动C-03EntryAbility.etsloadContent(pages/Index)与 profile 一致模拟器启动日志C-04AppScopelayered_image.json前景和背景文件存在桌面图标实机截图C-05entrylayered_image.json启动窗图标资源存在冷启动录屏C-06module.json5pages、Ability、backup metadata 引用有落点模块构建C-07backup_config.jsonallowToBackupRestoretrue不能证明历史数据已经恢复C-08根 build-profileSDK、产品、模块关系存在debug/release 构建结果签名路径、证书、profile 和任何口令字段只在受控环境核对不进入截图或公开报告。静态检查通过以后工程仍应按实际交付要求运行 debug 构建准备发布时再补 release 构建和签名包验证。当前自动化测试只是脚手架业务矩阵仍需落地工程的entry/src/test与entry/src/ohosTest文件存在但内容仍是模板式assertContain示例没有覆盖QuestionBank、灯光状态机或PracticeStore。因此“有 test 目录”不能写成“业务回归已有自动化”。最适合先自动化的是纯数据规则优先级 AselectLightCommands 的固定首尾、数量和候选范围 优先级 AgetStats 与历史筛选 优先级 AnormalizeRecord 的旧字段回退 优先级 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 用例新增一个存储字段时则同步补写入、旧数据回退、重启恢复和清空用例。让源码变化能追到矩阵让矩阵结果带着明确证据层级才算真正可回归。