【笔下生辉|19】HarmonyOS ArkTS 回归测试实战:覆盖启动、空数据、异常输入和重复点击
HarmonyOS 5.0 应用做回归测试时最容易被漏掉的不是主路径而是主路径旁边那些“不太像功能”的边界启动页跳到首页是否稳定底部 Tab 被连续点击是否只切状态不制造异常题库详情页拿不到bankId时是否有空态分类卡在宽屏下是否仍然排得下考试结果页收到异常records参数时是否能继续渲染用户从错题解析返回结果页时是否重复写入考试历史。对离线题库应用来说这些问题不会触发网络错误却会直接影响 AppGallery 审核中的启动、导航、布局、空数据和重复操作稳定性。本文基于「笔下生辉」真实源码复核包名标记为com.jiaweikang.one17。涉及的文件包括entry/src/main/ets/pages/Index.ets、BankDetailPage.ets、CategoryPage.ets、ExamResultPage.ets、HakkaBankPage.ets并结合UserDataManager、router、AppStorage和安全区处理来设计回归测试矩阵。本文不会声称源码已经接入自动化测试框架或云测平台只讨论源码可直接支撑的手工与脚本化回归检查点。1. 回归测试先从真实路由和状态开始很多回归清单会写成“启动正常、页面正常、按钮正常”但这种描述不能指导排查。对 ArkTS 页面来说回归测试要先拆出路由、状态、边界保护和可重复操作。当前源码里有几个非常适合做测试断面的点测试断面真实文件为什么要测启动与主 TabIndex.etscurrentTabIndex、错题角标、安全区、底部导航题库详情BankDetailPage.etsbank可能为undefined章节入口依赖参数固定题库入口HakkaBankPage.ets复用BankDetailContent但传入固定bankId分类页CategoryPage.ets按pageWidth切换卡片宽度点击跳搜索考试结果页ExamResultPage.etsJSON 参数解析、得分展示、历史记录防重复这张表的价值在于它把“测试页面”变成了“测试状态变化”。例如Index不只是一个首页它承担主 Tab 切换、错题数量角标和底部安全区避让BankDetailPage不只是题库介绍页它要同时处理正常题库、找不到题库、章节进度和底部操作ExamResultPage不只是结果展示页它还要在aboutToAppear中写入考试历史并防止返回时重复写入。2. 启动链路Splash 到 Index 后先看 AppStorage 和安全区启动回归的第一条链路是SplashPage - Index。前文已经复核过入口EntryAbility会初始化currentTabIndex、favoriteTabIndex、topAvoidAreaHeightPx、navigationIndicatorHeightPx然后加载pages/SplashPage。SplashPage再通过router.replaceUrl({ url: pages/Index })进入主页面。进入Index.ets后页面通过StorageLink读取共享状态StorageLink(currentTabIndex) currentIndex: number 0 StorageLink(wrongRecords) wrongRecords: WrongRecord[] [] StorageLink(currentBreakpoint) currentBp: string sm StorageLink(topAvoidAreaHeightPx) topAvoidAreaHeightPx: number 0 StorageLink(navigationIndicatorHeightPx) navigationIndicatorHeightPx: number 0这意味着启动回归不应该只看“有没有进入首页”还要看三个结果。第一currentIndex初始是否为 0首页是否作为首屏内容出现。第二底部导航高度是否包含bottomSafePadding()不会贴到系统手势区域。第三顶部安全区是否通过topSafePadding()让页面内容避开状态栏。源码里的安全区计算是private bottomSafePadding(): number { return Math.max(Sizes.BOTTOM_NAV_MIN_PADDING, this.getUIContext().px2vp(this.navigationIndicatorHeightPx)) } private topSafePadding(): number { return Math.max(Sizes.PADDING_SMALL, this.getUIContext().px2vp(this.topAvoidAreaHeightPx)) }所以启动测试要覆盖冷启动、返回桌面再进入、横竖屏切换后进入、底部手势导航设备进入。验收点不是截图好不好看而是首页内容、底部 Tab、角标、状态栏和底部导航区域有没有遮挡或跳动。3. Tab 重复点击Index 只改状态不应该制造路由栈Index的底部 Tab 使用BottomNavItem(index)构建。点击时执行.onClick(() { this.currentIndex index })这是一个很好的回归测试点。它没有调用router.pushUrl只改变本页状态所以连续点击首页、题库、挑战、错题本、我的不应该让路由栈增长也不应该让返回键需要退很多次才能退出。回归测试时可以按下面的顺序操作启动后连续点击“题库”5 次连续点击“挑战”5 次在错题数存在时点击“错题本”观察角标仍在正确位置按系统返回确认行为符合主页面预期而不是逐层返回到旧 Tab。错题角标也是重复点击测试的一部分。源码只有在index 3 this.wrongRecords.length 0时显示角标并把超过 99 的数量显示为99。这需要覆盖空错题、1 条错题、99 条错题、100 条以上错题四组数据。测试重点是角标文本不溢出、不遮挡图标、不因为重复切 Tab 而重复叠加。4. 空数据BankDetailPage 找不到题库时要显示空态BankDetailPage的核心是可复用组件BankDetailContent。它支持两种进入方式一种从路由参数读取bankId另一种由HakkaBankPage这类固定入口传入fixedBankId。源码逻辑是aboutToAppear(): void { if (this.fixedBankId.length 0) { this.bank getBankById(this.fixedBankId) return } const params router.getParams() as BankDetailParams | undefined if (params params.bankId) { this.bank getBankById(params.bankId) } }这里必须测两个方向。正常方向从题库列表进入传入有效bankId页面展示封面、统计、章节、底部“随机练习”和“限时挑战”按钮。固定入口方向HakkaBankPage传入fixedBankId: b_hakka不依赖路由参数也能展示客家题库详情。异常方向手动构造无效bankId或不带参数进入BankDetailPage。源码在this.bank undefined时显示空态图片和“未找到题库”文案而不是继续访问this.bank!.id。这个空态非常关键因为下面的HeroCard、章节列表和底部按钮大量依赖this.bank!只有外层if把它们隔离开页面才不会空指针崩溃。回归测试的验收点可以写成输入预期bankId b_hakka展示题库详情和章节列表fixedBankId b_hakka固定入口可正常打开bankId not_exists显示空态不显示底部练习按钮无参数进入显示空态不崩溃这类空数据测试比主路径更重要。主路径通常在开发时天天被打开空态只有在路由参数异常、数据变更、入口改造或深链失败时才会暴露。5. 响应式回归CategoryPage 和 BankDetailPage 都依赖 pageWidthCategoryPage用pageWidth决定分类卡片宽度private itemWidth(): string { return this.pageWidth 720 ? 24% : 48% }页面通过onAreaChange更新pageWidth因此回归测试要覆盖手机窄屏、平板宽屏、小窗缩放和横屏。窄屏下每行大约两张卡片宽屏下每行约四张卡片。测试点包括卡片文字maxLines(1)和textOverflow是否生效题型说明是否被截断到不可理解点击分类卡是否能带着categoryType和categoryName跳到SearchPage。BankDetailPage也有响应式逻辑private useWideLayout(): boolean { return this.currentBp lg this.pageWidth 700 }宽屏时页面拆成左右两列左侧放封面、摘要和资料卡右侧放重点和章节列表窄屏时使用单列 Scroll。回归测试不能只在手机竖屏验收还要在宽度超过 700vp 且断点为lg的场景下检查左右两列是否同时可滚动章节列表是否不被底部按钮遮挡封面图是否保持比例底部按钮是否仍有安全区 padding。这些检查直接对应 AppGallery 的布局审核风险折叠、旋转、小窗、平板和 2in1 下组件不能错位、遮挡、截断或不可点击。6. 异常输入ExamResultPage 的 records 解析失败也要能渲染考试结果页通过路由参数接收bankId、score、total、correct、durationSec和records。其中records是 JSON 字符串源码用try/catch处理try { this.records JSON.parse(params.records) as AnswerRecord[] } catch (_) {}这说明回归测试必须覆盖异常records。如果records不是合法 JSON页面不应该白屏也不应该阻断得分、用时、正确率和错题数量展示。答题网格由makeAnswerGrid()根据total构造如果records.length不足则剩余题目状态为emptyprivate makeAnswerGrid(): GridItem[] { const result: GridItem[] [] for (let i 0; i this.total; i) { let state: string empty if (i this.records.length) { state this.records[i].correct ? correct : wrong } result.push({ index: i 1, state }) } return result }这里至少要测四组参数参数组合预期total10records10 条网格全部显示正确或错误状态total10records3 条前 3 题显示记录后 7 题显示未答total0records[]正确率计算不除以 0页面可渲染recordsbroken-json捕获异常网格按空记录处理源码中正确率使用Math.max(this.total, 1)避免除以 0错题数量使用Math.max(this.total - this.correct, 0)避免负数显示。测试时要确认这些保护不会因为异常输入被绕开。7. 重复写入historySaved 是考试结果页的关键保护ExamResultPage在aboutToAppear中保存考试历史。这个生命周期在页面首次进入时触发也可能在从错题解析页返回时再次触发。如果不加保护用户看一次结果、进一次解析、再返回就可能写入多条相同考试历史。源码用historySaved防重复if (!this.historySaved this.total 0) { this.examHistory UserDataManager.addExamHistory( this.examHistory, this.bankId, this.score, this.total, this.correct, this.durationSec) this.historySaved true }这段代码应该被单独纳入回归测试而不是只看结果页能不能打开。测试步骤可以是完成一次限时挑战进入结果页记录考试历史数量点击“错题解析”进入练习页返回结果页再次查看考试历史数量应保持不变点击“再来一次”进入新考试完成后才新增一条历史。这类重复点击和生命周期回归很常见。ArkUI 页面返回、replaceUrl、pushUrl、aboutToAppear组合在一起时数据写入不能只靠“用户不会这样操作”来保证。historySaved是当前源码中真实存在的防线。8. 路由动作pushUrl 与 replaceUrl 的差异要测出来源码里有两类路由动作。进入详情、练习、设置、搜索多数使用router.pushUrl从考试结果页“再来一次”使用router.replaceUrl。这不是写法差异而是用户体验差异。pushUrl会把新页面压入栈适合从列表进入详情、从结果进入错题解析因为用户通常希望能返回上一个上下文。replaceUrl会替换当前页适合“再来一次”这种重新开始流程避免用户返回到旧结果页再重复触发操作。回归测试需要按按钮逐个确认页面动作预期CategoryPage分类卡pushUrl到SearchPage返回后仍在分类页BankDetailPage章节按钮pushUrl到PracticePage返回后仍在题库详情BankDetailPage随机练习pushUrl到PracticePage返回后仍在题库详情ExamResultPage错题解析pushUrl到PracticePage返回后仍在结果页且不重复写历史ExamResultPage再来一次replaceUrl到PracticePage不把旧结果页留在当前返回路径中这些检查能发现很多“功能能用但返回体验错”的问题。对上架审核来说返回路径也是稳定性的一部分不能只测按钮点击瞬间。9. 回归矩阵把边界写成可执行清单基于当前源码可以把回归矩阵整理成下面这组最小清单。类别操作验收点启动冷启动进入应用Splash 跳 Index首页显示安全区正常主 Tab连续切换 5 个 Tab不崩溃、不堆路由、角标位置稳定空题库无效bankId进入详情显示空态不显示依赖bank!的按钮固定题库打开HakkaBankPagefixedBankId生效展示客家题库分类宽屏宽度 720分类卡约四列文字不溢出分类窄屏手机竖屏分类卡两列说明区域可滚动结果异常records非法 JSON捕获异常页面仍显示得分和空网格结果空数据total0正确率不除以 0不出现负错题重复返回结果页进解析再返回examHistory不重复写入底部安全区手势导航设备底部按钮不进入系统手势区如果要把这套清单接入脚本化测试可以先不追求覆盖所有 UI 像素而是优先检查路由参数、状态数量、关键文本和数据写入次数。自动化不足的部分用固定截图和手工复核补齐。10. 发布前建议回归测试要和审核风险对齐当前「笔下生辉」是离线题库应用回归测试的重点不在网络超时或服务器异常而在本地状态、路由参数、布局适配和重复操作。发布前至少要把下面四类风险跑完。第一类是兼容性风险启动、首页、Tab、详情、练习、结果页、返回路径必须稳定不能闪退、白屏或无法返回。第二类是布局风险Index的底部导航、BankDetailPage的宽窄布局、CategoryPage的卡片列数、ExamResultPage的底部按钮都要在手机、平板、2in1、小窗和横屏下检查。第三类是数据风险本地 Preferences 写入不能因为返回生命周期重复执行清空或异常参数不能让页面进入不可恢复状态。第四类是交互风险重复点击 Tab、连续点章节、结果页重复进入解析、再来一次、返回键都要确认不会制造多余路由或重复记录。11. 小结回归测试不是补表格而是保护真实源码边界「笔下生辉」当前源码已经提供了几个明确的回归保护点Index用AppStorage和安全区处理启动后的主框架BankDetailPage在bank undefined时显示空态CategoryPage通过pageWidth控制分类卡响应式布局ExamResultPage用try/catch处理异常records用Math.max避免极端数据计算异常并用historySaved避免考试历史重复写入。这些都是真实代码可以复核的测试依据。回归测试应该围绕它们设计而不是只写“功能正常”。当测试清单能覆盖启动、空数据、异常输入、重复点击和多设备布局时应用在后续改题库、改导航、改结果页或改设置项时才不容易把稳定性问题带到发布包里。本篇文章的封面使用当前文章独立的media/cover.png同一软件合集继续复用应用级collection-cover.png合集封面保持统一合集内每篇文章封面保持不同便于跨平台发布和回读核验。