法律学习应用的回归测试不能只验证“题目能打开、按钮能点击”。真正容易在版本迭代后复发的问题通常藏在正常路径之外首次安装没有任何学习记录路由参数缺少bankId结果页收到损坏的 JSON用户连续点击章节入口倒计时交卷与手动交卷同时发生或者结果页因为生命周期重入把同一场考试保存两次。知律的源码已经提供了一些可靠保护题库不存在时展示空态答题后通过showAnalysis阻止二次记分统计计算对零分母做了处理HakkaBankPage 使用固定题库 ID 避免依赖外部参数。但项目里的 Hypium 用例仍是默认字符串断言不能证明上述业务链路已经通过自动化回归。本文基于D:\huawei\one19-11的真实源码复核BankDetailPage.ets、CategoryPage.ets、ExamResultPage.ets、HakkaBankPage.ets、Index.ets并沿调用链检查PracticePage.ets、QuestionUtils.ets、StatService.ets与 Preferences 持久化。包名com.jiaweikang.one19是本文草稿回读使用的唯一标记。本文不会把静态代码检查冒充真机测试也不会把未执行的测试写成通过。这套回归方案重点回答四个问题干净启动和空数据时页面是否仍然可理解、可退出路由参数和 JSON 异常时页面是否有确定降级重复点击、自动跳转和交卷竞争是否只产生一次副作用内存状态变化后重启能否证明数据真正写入或删除。一、先把“回归通过”写成可观察契约“功能正常”不是一条可复现的测试结论。知律每条回归用例至少需要记录以下字段字段示例前置状态全新安装、无历史、已有一条错题入口首页、固定校园法律题库页、非法 Want 参数操作连点两次答案、快速点击“模拟考试”可见结果空态文案、按钮状态、页面栈数据副作用历史增加一次、错题只写一条运行环境phone/tablet/2in1、系统版本、包版本失败时这些字段能帮助判断问题位于路由、页面状态、业务计算还是 Preferences。只保留截图而没有前置数据和操作序列往往无法复现边界故障。二、现有 Hypium 目录只是测试框架冒烟entry、librarya 和 libraryb 都有src/test/LocalUnit.test.ets但内容仍是默认模板it(assertContain, 0, () { let a abc let b b expect(a).assertContain(b) expect(a).assertEqual(a) })这条用例只能证明 Hypium 的基础断言能执行不能证明知律的评分、统计、题库查找、异常参数或持久化正确。测试报告中应明确标记为“模板冒烟”不能写成“核心业务单测已覆盖”。第一批自动化应从QuestionUtils和StatService这类纯逻辑开始因为它们不依赖UIAbility、router 或设备环境运行快失败定位也最直接。三、把回归分成四层而不是一张长清单知律适合按职责拆成四层纯逻辑层分数、时间格式、统计汇总、进度比例页面状态层启动、空数据、非法题库、损坏结果参数交互副作用层重复答题、连续导航、交卷幂等持久化与发布层跨重启回读、清理、安装启动卸载。纯函数可以由 Hypium 覆盖页面状态需要组件或真实设备操作持久化必须跨页面、跨进程发布候选包还要经过标准安装和卸载。任何一层都不能替代其他层。四、启动测试必须从干净安装开始EntryAbility初始化 UserDataManager、AppStorage 和断点系统再加载 SplashPageSplashPage 最终使用router.replaceUrl()进入 Index。启动回归至少覆盖全新安装后首次启动Preferences 不存在时使用默认空数组SplashPage 快速点击与定时跳转不会叠出多个 Index五个主标签均能进入退出再启动时没有白屏、闪退或卡死标准卸载后不依赖第三方工具清理。构建成功只能证明 ArkTS 和资源可以编译不能证明首帧和启动路由正常。App Review Guideline 3.1 关心的是发布包能安装、启动、运行核心流程和卸载回归记录必须区分这些状态。五、Index 的标签重复点击副作用较小手机底部导航和宽屏侧边导航都只修改同一个索引.onClick(() { this.currentIndex index })页面内容通过PageContent()条件渲染没有每次点击都调用router.pushUrl()。因此重复点击同一标签不会直接堆叠页面栈这是当前结构的优势。回归时仍应连续点击当前标签十次并检查currentIndex最终只有一个确定值返回键不会多退十层错题徽标不会重复增长页面不会产生多份定时器或重复写入phone 与侧边导航模式行为一致。“没崩溃”只是最低标准状态和页面栈稳定才是通过。六、空数据不是异常应有稳定的零值语义全新安装时收藏、错题、进度和考试历史都是空数组。统计层应返回 0而不是NaN或异常。适合先加入这样的用例it(emptyLearningDataReturnsZero, 0, () { const summary StatService.summarize([], [], [], []) expect(summary.totalAnswered).assertEqual(0) expect(summary.totalCorrect).assertEqual(0) expect(summary.accuracyPercent).assertEqual(0) expect(StatService.recentAvgScore([])).assertEqual(0) })测试还要进入首页、收藏、学习统计和题库详情确认空数组不会让ForEach、进度条或徽标渲染异常。零值页面不一定需要整页插画但必须让用户知道当前没有记录以及下一步可以做什么。七、非法题库参数已经有可见降级BankDetailContent.aboutToAppear()从路由读取bankIdconst params router.getParams() as BankDetailParams | undefined if (params params.bankId) { this.bank getBankById(params.bankId) }查不到题库时页面进入明确分支if (this.bank undefined) { Text(未找到题库) }应固定四组参数缺失参数、空字符串、未知 ID、合法 ID。前三组必须显示空态且保留返回路径不能渲染依赖this.bank!的底部动作合法 ID 才能显示章节和练习入口。这条用例很有价值因为它能同时保护路由协议和页面空值处理。八、HakkaBankPage 提供了固定参数入口校园法律题库页不读取外部路由参数而是直接组合BankDetailContent({ fixedBankId: b_hakka })BankDetailContent会优先使用fixedBankId然后提前返回。这形成一条与普通题库详情不同的入口普通详情依赖 router 参数HakkaBankPage 依赖编译期固定 ID。回归时应分别覆盖两条路径。固定入口能降低错误参数风险但也需要检查b_hakka是否仍存在于 MockBanks如果题库重命名或删除固定页会稳定进入“未找到题库”不会自动寻找替代数据。九、题库进度对零题数做了保护题库详情的进度计算是private bankProgressRatio(): number { if (!this.bank || this.bank.totalCount 0) return 0 return Math.min(this.bankFinished() / this.bank.totalCount, 1) }这避免了零题库出现除零也把超额历史限制为 1。章节进度同样在chapter.total 0后才计算。建议增加以下边界已完成总题数期望比例00001005100.51010112101最后一组很重要它模拟题库缩减后历史计数大于新总题数的情况。十、CategoryPage 要测试宽度临界点分类页根据页面宽度返回字符串宽度private itemWidth(): string { return this.pageWidth 720 ? 24% : 48% }这意味着 719 和 720 是明确断点。回归不能只测一个手机宽度和一个平板宽度应加入719vp双列720vp四列窗口在断点附近来回调整分类名称变长题量从一位数变为三位数2in1 调整窗口时无重叠、截断或跳动。分类卡片使用FlexWrap.Wrap和SpaceBetween测试重点是行宽、间距与文本省略是否稳定而不是只看卡片数量。十一、分类卡片连续点击可能重复导航分类卡片当前直接执行router.pushUrl({ url: pages/SearchPage, params: { categoryType: cat.type, categoryName: cat.name } })没有isNavigating或按钮禁用状态。高频双击可能发起两次 push。类似风险也存在于题库详情的“开始”“随机练习”“模拟考试”入口。可靠的回归判据是页面栈只增加一层SearchPage 只初始化一次返回一次就能回到分类页导航失败后按钮可再次使用。如果测试能复现叠栈可在页面或统一导航服务增加门闩if (this.isNavigating) return this.isNavigating true router.pushUrl(target).catch(() { this.isNavigating false })门闩必须在返回或失败时恢复不能把入口永久锁死。十二、答题重复点击已有第一道幂等保护PracticePage.selectOption()在开头判断if (this.mode wrongAnalysis) return if (this.showAnalysis) return第一次选择后会立即设置showAnalysis true再写入答题记录和错题。因此第二次点击同一选项或其他选项时方法会直接返回。重复点击用例应断言records只增加一条wrongRecords最多增加一条解析和选中态保持第一次结果自动下一题只安排一次连续点击不会改变首次答案。测试副作用数量比截图更可靠。按钮颜色正确但数组写了两次仍然是失败。十三、自动下一题与手动下一题存在竞争窗口开启自动下一题后源码延迟 800毫秒调用setTimeout(() { if (this.showAnalysis) { this.goNext() } }, 800)用户在延迟期间手动点击下一题时goNext()会切题并重置showAnalysis延迟回调随后不会再次前进。这是一个可验证的保护。测试步骤可以固定为开启自动下一题选择答案立即点击下一题等待 1 秒题号只能增加一次。还要测试答题后立即退出页面。当前代码没有保存这个 timeout 的 ID也没有在生命周期结束时显式清理不能把“退出后绝不回调”写成已确认事实。十四、交卷需要独立的业务幂等标志考试可能通过两条路径结束倒计时到零自动交卷用户在最后一题主动完成。两条路径都会计算分数、更新进度并replaceUrl()到结果页。边界时刻如果计时回调与用户操作靠得很近仅依靠按钮状态不一定足够。建议用一次提交状态把业务动作包住private submitted: boolean false private submitExamOnce(): void { if (this.submitted) return this.submitted true // 计算、落库、导航 }回归应验证一场考试只更新一次题库进度增加一条考试历史打开一个结果页停止一个计时器。当前源码是否在真机高频边界下发生重复提交需要运行证据不能仅凭静态阅读宣布通过。十五、结果页对损坏 JSON 只有基础保护ExamResultPage会解析路由中的 recordstry { this.records JSON.parse(params.records) as AnswerRecord[] } catch (_) {}损坏 JSON 会被降级为空数组页面仍可显示分数与答题网格。这是基础保护但类型断言不会校验运行时形状。JSON.parse({})能成功却不是数组。更稳的归一化方式是const parsed: Object JSON.parse(raw) this.records Array.isArray(parsed) ? parsed as AnswerRecord[] : []回归输入至少包括空串、损坏 JSON、{}、null、空数组、合法数组和超长数组。预期不是“全部照常”而是非法形状被安全降级不造成页面崩溃。十六、结果页参数本身也需要约束结果页直接接收score、total、correct和durationSec并计算Math.round(this.correct / Math.max(this.total, 1) * 100)零总题数不会除零但负数、正确数大于总题数、分数超过 100、负时长都没有归一化。正常答题路径不会主动生成这些值可异常路由或后续重构仍可能传入。建议先通过测试定义契约score 限制在 0 到 100total、correct、durationSec 不小于 0correct 不大于 totalrecords 超出 total 时只展示 total 个格子缺少 bankId 时禁用“再考一次”或提供返回。输入校验适合放在路由参数归一化方法中而不是让每个 Text 组件各自兜底。十七、aboutToAppear 会无条件追加考试历史结果页在aboutToAppear()末尾调用this.examHistory UserDataManager.addExamHistory( this.examHistory, this.bankId, this.score, this.total, this.correct, this.durationSec )它没有 sessionId也没有“本次已保存”标志。页面重建、重复导航或生命周期重新进入时可能把同一考试追加多次。这是知律当前回归矩阵中的高价值风险点。测试应记录进入前后的历史长度再用同一组参数重复进入。工程修复可以选择在考试提交服务中落库结果页只展示为每场考试生成 sessionId 并按 ID 去重在页面生命周期内维护一次性写入标志。没有实际修复和运行验证前只能记录风险不能写成“历史已防重复”。十八、静态排名不能当作在线能力测试结果页当前显示Text(${236}/${1258})这是一段固定字符串不来自服务端。因此回归只能验证它的布局不应记录“在线排名请求成功”“排行榜同步正常”或真实用户规模。同样CSDN 的 PV、点赞和收藏也只能在公开页面真实回读后记录。应用内收藏是本机题目记录不是平台运营指标。测试报告必须区分静态展示、本地数据和线上数据源。十九、“错题解析”和“再考一次”也要做重复点击测试结果页底部两个 GreenButton 分别调用pushUrl()和replaceUrl()没有统一防抖。快速连续点击可能造成重复页面初始化进而放大考试历史重复写入风险。测试应观察动作页面栈预期数据预期双击错题解析只进入一层 PracticePage不新增考试历史双击再考一次只保留一个新考试页不重复初始化计时器交替点击两个按钮只接受第一个有效动作页面状态可预测如果页面栈行为不稳定应把导航门闩放在共享动作层而不是分别给按钮做动画禁用。二十、持久化必须跨重启才算通过收藏、笔记、错题、进度和考试历史通过 UserDataManager 写入 Preferences。当前页面上的StorageLink变化只证明内存状态更新不能证明落盘成功。完整用例应执行清空目标记录结束应用并重新启动确认前置为空完成一次收藏、笔记、答错或考试记录当前数量杀进程并重新启动返回对应页面数量和内容仍一致执行删除再次重启确认记录不恢复。这套流程还能发现上一篇权限隐私审计中提到的风险persist()吞掉异常后界面可能先显示成功。二十一、多设备回归要同时观察断点和实际宽度Index 使用currentBreakpoint决定底部导航或侧边导航BankDetailPage 又要求return this.currentBp lg this.pageWidth 700因此只拖动窗口宽度、但断点状态没有同步不能代表真实多设备适配。至少覆盖phone 竖屏phone 横屏或小窗tablet 竖屏与横屏2in1 连续调整窗口719/720 分类断点699/700 题库详情宽屏断点。检查文本、省略、底部按钮、安全区、滚动可达性和系统返回。布局切换时不能丢失当前题库或学习状态。二十二、回归证据要绑定发布候选包每轮记录应包含bundleName、versionName、versionCodeHAP/APP 文件摘要或构建时间签名环境设备型号、系统版本和窗口尺寸用例编号、前置数据、实际结果截图、非敏感日志或视频对应源码提交或工作区版本passed、failed、not run三态结论。静态阅读得到的是“代码存在保护”或“发现风险”不是“真机已通过”。日志也不应包含用户笔记、账号凭据、Cookie、Token、短信码或证书密码。二十三、第一批真正值得写的 Hypium 用例优先把默认assertContain替换为纯函数契约it(scoreAndTimeBoundaries, 0, () { expect(QuestionUtils.calcScore([], 0)).assertEqual(0) expect(QuestionUtils.formatTime(0)).assertEqual(00:00) expect(QuestionUtils.formatTime(59)).assertEqual(00:59) expect(QuestionUtils.formatTime(60)).assertEqual(01:00) })随后补充全对、全错、部分正确空统计返回全零最近五次均分题库进度上限为 1非数组结果记录归一化为空负数和超范围参数的约定行为。纯函数门禁稳定后再增加路由页面、重复点击和跨重启测试。先覆盖最容易自动化且最关键的契约比一次搭建庞大但不稳定的 UI 脚本更有效。二十四、发布前最小回归清单[ ] 全新安装后 SplashPage 和 Index 正常[ ] 五个主标签重复点击不叠栈[ ] 空收藏、空错题、空历史和零统计可解释[ ] 非法、缺失和合法 bankId 分支正确[ ] HakkaBankPage 固定题库入口有效[ ] 719/720、699/700 宽度临界点无错位[ ] 分类和题库入口双击只导航一次[ ] 答案连续点击只记录一次[ ] 手动下一题与自动下一题只前进一步[ ] 倒计时与手动交卷竞争只提交一次[ ] 结果页坏 JSON 安全降级[ ] 结果页重复进入不重复写历史[ ] 本地数据经过重启仍一致[ ] 删除后重启不恢复[ ] phone、tablet、2in1 核心路径可达[ ] 发布候选包完成安装、启动、核心流程和卸载。没有运行的项目必须标记not run不能从源码“看起来没问题”推导成通过。二十五、常见失败定位表症状最可能的边界优先检查首次启动白屏初始化或 Splash 路由EntryAbility、SplashPage 日志非法题库闪退空值分支未覆盖bankId 归一化和底部动作分类双击返回两次重复 push导航门闩与页面栈一题记了两次错题选择幂等失效showAnalysis 设置时机自动下一题跳过一题延迟回调竞争timeout 与手动 goNext考试历史重复结果页重复写入aboutToAppear 与 sessionId结果页坏参数崩溃只做类型断言JSON 形状和数字范围校验清空后重启恢复写入失败被吞掉Preferences flush 结果平板卡片重叠双断点不同步currentBreakpoint 与 pageWidth排名测试“通过”把静态值当网络结果确认真正数据源二十六、总结围绕副作用数量建立门禁知律当前已经有一批值得保留的边界设计不存在的题库会显示“未找到题库”固定校园法律入口不依赖外部参数进度比例对零题数和超额历史做了限制重复选择答案通过showAnalysis被拦截损坏 JSON 有基础 try/catch 降级。同样不能忽略尚未闭合的风险Hypium 仍是默认模板分类、章节和结果页导航缺少通用防抖自动跳转与页面退出的 timeout 生命周期未验证交卷缺少独立 submitted 契约结果页对 JSON 只解析、不校验形状aboutToAppear()可能重复追加考试历史Preferences 的成功与失败需要跨重启证据。回归测试最有价值的断言不是“页面打开了”而是“一次动作只产生一次副作用”一次答题写一条记录一次交卷保存一场历史一次点击增加一层页面一次删除经过重启仍然有效。把这些契约固定下来再覆盖启动、空数据、异常输入和多设备断点才能形成可重复的 HarmonyOS 发布门禁。本文部分内容由 AI 辅助整理。源码事实与风险均依据知律项目实际文件复核Hypium 示例、幂等状态、参数归一化和导航门闩属于基于当前实现边界的测试与增强建议未冒充已运行的真机结果。