news 2026/9/7 19:20:19

【口算王|19】HarmonyOS ArkTS 回归测试实战:覆盖启动、空数据、异常输入和重复点击

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【口算王|19】HarmonyOS ArkTS 回归测试实战:覆盖启动、空数据、异常输入和重复点击

【口算王|19】HarmonyOS ArkTS 回归测试实战:覆盖启动、空数据、异常输入和重复点击

证据边界:本文依据 D:/huawei/one16-11 当前可读取源码与测试目录整理;本轮未执行 Hypium、构建、模拟器或真机回归。工程存在默认测试模板不等于业务用例已覆盖,文中矩阵属于建议实现。

回归测试不是把页面依次点一遍,也不是看到工程里存在src/test就认为质量已经有保障。真正有价值的回归测试必须固定前置数据、执行动作、观察结果和持久化副作用,并且能在版本变化后重复执行。对于口算王这类本地题库应用,最容易被忽略的故障恰好不在正常答题路径,而在首次启动、零记录、错误路由参数、连续点击和页面重新进入。

本文基于口算王项目D:\huawei\one16-11的真实源码,复核Index.etsBankDetailPage.etsCategoryPage.etsSearchPage.etsPracticePage.etsExamResultPage.etsLearningStatsPage.etsQuestionUtils.etsStatService.ets。包名com.jiaweikang.one16是本文草稿核验使用的唯一标记。项目目标和兼容 SDK 均为 HarmonyOS 6.0 系列,本文方法适用于 HarmonyOS 5.0 及以上 Stage 模型应用。

先说明测试现状:工程包含 Hypium 测试目录,但entry/src/test/LocalUnit.test.ets仍是默认的字符串断言模板,没有覆盖题目评分、统计汇总、路由参数、页面空态或持久化。本文不会把这些测试写成“已经通过”,而是从现有代码提炼一套可以真正落地的回归矩阵,同时指出当前实现已经具备的保护和仍需补齐的风险。

一、先定义回归测试的可交付结果

一次回归不能只留下“正常”两个字。每条用例至少应记录五个字段:

  • 前置数据,例如首次安装、已有错题或已有挑战历史;
  • 入口与参数,例如从分类页进入,或直接传入非法bankId
  • 操作序列,例如连续点击两次答案或交卷;
  • 可见结果,例如空态、按钮状态和页面栈;
  • 数据结果,例如收藏数、答题记录和挑战历史只变化一次。

这样测试失败时才能定位是渲染、路由、业务计算还是 Preferences 写入出了问题。

二、现有测试目录不等于已有回归覆盖

当前LocalUnit.test.ets的核心断言是:

it('assertContain',0,()=>{leta='abc'

let b = 'b'

expect(a).assertContain(b)expect(a).assertEqual(a)

})

它只能证明 Hypium 模板能够运行,不能证明口算王的任何业务行为。测试报告中应把它标记为“框架冒烟用例”,而不是“核心功能测试”。第一轮建设应优先替换为纯逻辑边界测试,因为这类测试执行快、结果稳定,也最适合作为每次提交的门禁。

三、回归矩阵从四层建立

建议把用例分成四层:

  1. 纯逻辑层:评分、计时、统计和题目选择;
  2. 页面状态层:首次启动、空数据、无结果和错误参数;
  3. 交互层:重复点击、快速切页、弹窗开关和返回;
  4. 持久化层:写入、清空、重启后回读和重复进入。

纯逻辑层可以使用 Hypium 单元测试;页面与交互需要组件测试或真实设备操作;持久化必须跨页面甚至跨进程复核。不要用一类测试替代所有层。

四、启动回归要从干净状态开始

Index.ets@StorageLink('currentTabIndex')恢复当前标签,并根据currentBreakpoint选择底部导航或侧边导航。启动测试至少覆盖:

  • 全新安装后进入首页;
  • 本地数据为空时五个标签均可切换;
  • 上次停留在非首页标签时重新启动;
  • phone、tablet、2in1 对应宽度下导航可见;
  • 状态栏和底部安全区不会遮挡内容。

单纯执行hvigorw assembleHap只能证明编译成功,不能证明首帧、路由和数据初始化正常。发布候选包还应执行安装、启动、核心路径和卸载冒烟。

五、标签重复点击应保持状态稳定

当前标签点击只执行赋值:

.onClick(()=>{this.currentIndex=index

})

重复点击同一个标签不会叠加路由,这是当前实现的优势,因为PageContent由状态条件渲染,而不是每次点击都pushUrl。回归用例仍应连续点击当前标签十次,观察页面是否重复初始化、滚动位置是否异常跳变,以及 StorageLink 是否产生无意义写入。

测试通过的判定不是“没有崩溃”,而是标签索引保持一个确定值,页面没有多份实例,返回键也不会出现多余层级。

六、零数据统计必须返回确定的零

StatService.summarize对空数组已有明确处理:

accuracyPercent:totalAnswered>0?Math.round(totalCorrect/totalAnswered*100)

: 0

recentAvgScore在历史为空时也返回 0。这里应补充最小单元测试:

it('emptyStatsReturnsZero',0,()=>{conststats=StatService.summarize([],[],[],[])

expect(stats.totalAnswered).assertEqual(0)

expect(stats.accuracyPercent).assertEqual(0)expect(StatService.recentAvgScore([])).assertEqual(0)

})

这条用例能防止后续重构时引入NaN、除零或空数组 reduce 异常。

七、统计页没有传统空白页,但有零值态

LearningStatsPage即使没有任何记录,也会展示概览卡片、三个数据卡片和所有题库进度。各项值为 0,每个题库显示“未开始”。这是一种合法的零值态,不必强行替换成整页插画。

回归时应检查:

  • 0%不显示为NaN%
  • 最近五次均分为空时显示 0;
  • 已学习题库数为0/题库总数
  • 每个进度条比例为 0;
  • 窄屏和宽屏布局都能完整显示。

零值态的价值在于用户仍能理解页面结构和下一步目标。

八、搜索页要区分“未搜索”和“无结果”

SearchPagesearched区分两个状态。空关键词且没有分类时会清空结果并保持searched = false;执行一个没有匹配项的关键词后,才进入“未找到相关内容”。

if(this.keyword.trim().length===0&&this.categoryType.length===0) {

this.bankResults = []

this.questionResults=[]this.searched=false

return

}

因此回归用例必须分别覆盖:初次进入、只输入空格、输入不存在关键词、清除分类、从分类页带参数进入。若把前两者都断言为“无结果”,就会误判现有产品设计。

九、异常搜索输入先验证边界而不是猜攻击面

搜索完全基于本地字符串匹配,没有 SQL、HTTP 或 Web 组件,所以不应虚构注入风险。真实需要验证的是:

  • 超长文本是否挤压搜索栏;
  • 中英文混合和特殊符号是否稳定;
  • 前后空格是否被trim
  • 连续提交是否重复构造巨大数组;
  • 分类名称被用户编辑后,分类过滤是否正确清除。

当前代码在输入内容偏离categoryName时会重置分类条件,方向正确。由于普通搜索限制最多 50 道题,长关键词也不会无限追加结果。

十、非法题库 ID 已有可见空态

BankDetailPage读取路由参数后调用getBankById。查不到题库时,bank保持undefined,页面展示“未找到题库”:

if(this.bank===undefined) {Image($r('app.media.img_empty_default'))

Text('未找到题库')

}

应测试缺少参数、空字符串、未知 ID 和合法 ID 四种情况。合法 ID 必须展示内容和底部动作;非法 ID 必须保持空态,且不能执行依赖this.bank!的随机练习或限时挑战。

当前空态分支不会渲染BottomActions,避免了空对象强制解包,这是可以保留的保护。

十一、空题目集合需要单独测试

PracticePage根据 mode、chapterId、questionType 和 startQuestionId 组装题目。普通模式在题目为空且没有questionType时会回退到整个题库;指定题型为空时则保留空集合。

页面只有在currentQ() !== undefined时渲染题目,但底部工具栏仍存在。回归时要确认:

  • 空题集不会崩溃;
  • 进度条比例为 0;
  • 收藏和笔记操作不会写入数据;
  • “下一题/完成”不会产生除零或错误路由;
  • 用户有明确的退出路径。

当前实现具备空值判断,但没有专门的“暂无可练习题目”提示。这是测试应暴露的体验缺口,不能写成已经完善。

十二、评分函数已经防住总题数为零

QuestionUtils.calcScore的首行是:

if(total===0)return0

应覆盖四组数据:0 题、全错、部分正确和全对。还要加入“记录数大于总题数”的异常输入,决定服务是按总题数计算还是先截断记录。当前实现会按全部正确记录除以 total,理论上可能得到超过 100 的分数。

正常 UI 不应产生这种数据,但回归测试的职责正是固定异常边界。更稳妥的服务契约是把结果限制到 0 到 100,并校验记录是否属于当前题集。

十三、时间格式也需要异常输入契约

formatTime把秒数直接拆成分钟和秒:

constm=Math.floor(sec/60)consts=sec%60

return${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}

正常值 0、59、60、3599 都容易断言。负数、小数和极大值目前没有归一化,可能出现负号或小数秒。调用方通常提供递增整数,但公共工具方法仍应通过测试明确输入约束,避免未来被其他页面复用时产生模糊结果。

十四、答题选项已有重复记分保护

selectOption开头有两道门:

if(this.mode==='wrongAnalysis')returnif(this.showAnalysis)return

第一次点击后立即设置showAnalysis = true,后续点击不会再次 push 答题记录,也不会重复添加错题。回归时可连续点击同一选项和不同选项,最终断言:

  • records只增加一条;
  • wrongRecords最多增加一条;
  • 选中态和答案解析保持第一次结果;
  • 自动下一题只调度一次。

这是“重复点击测试”最典型的成功判据:副作用只发生一次。

十五、自动下一题要防止延迟回调串题

开启自动下一题后,代码使用 800 毫秒延迟:

setTimeout(()=>{if(this.showAnalysis) {

this.goNext()

}},800)

如果用户在延迟期间手动点击“下一题”,goNext会先切换题目并把showAnalysis设为 false,因此回调到达后不会再次前进。这一保护值得加入回归用例:答题后立即手动下一题,等待一秒,索引只能增加一次。

还应在页面退出后观察延迟回调是否触发。当前代码没有保存并清理 timeout ID,后续可以把它纳入生命周期治理,但本文不把该改进描述为已实现。

十六、导航按钮目前缺少通用防抖

分类卡片、章节“开始”、随机练习、限时挑战和结果页按钮都直接调用router.pushUrlreplaceUrl,没有isNavigating状态。高频连续点击可能发起多次导航。

建议先写失败用例,再补一个最小门闩:

if(this.isNavigating)returnthis.isNavigating=true

router.pushUrl(target).catch(() => {

this.isNavigating=false})

页面返回或导航失败时必须恢复状态。测试要检查页面栈只增加一层,不能只看最终页面是否打开。

十七、交卷动作需要幂等语义

挑战倒计时到零会调用autoSubmitExam,最后一题按钮也可能调用goNext并进入结果页。两条路径都负责更新进度和替换路由。

当前autoSubmitExam会把timerId设为 -1,但没有独立的submitted标志。边界时刻若用户操作和计时回调非常接近,应验证是否可能执行两次进度更新或两次 replace。可靠判定应是:

  • 一场挑战只生成一个结果页面;
  • 进度只累计一次;
  • 挑战历史只增加一次;
  • 倒计时停止且不会继续回调。

这比给按钮加一个视觉 disabled 更重要,因为幂等必须落在业务动作上。

十八、结果页的异常 JSON 已有降级

ExamResultPage和错题解析都用 try/catch 解析 records。解析失败时数组保持为空,页面仍可展示结果。这条路径应传入空串、损坏 JSON、对象而非数组和超长记录。

不过JSON.parse成功不等于结构正确。若传入{},类型断言不会做运行时校验,后续读取records.length的行为可能偏离预期。建议增加显式数组判断:

constparsed:Object=JSON.parse(raw)this.records=Array.isArray(parsed)

? parsed as AnswerRecord[]

:[]

测试应验证错误数据被降级为空记录,而不是让类型断言掩盖运行时形状。

十九、结果页重复进入会重复追加历史

ExamResultPage.aboutToAppear每次执行都会调用:

this.examHistory=UserDataManager.addExamHistory(this.examHistory,

this.bankId,

this.score,this.total,

this.correct,

this.durationSec)

这意味着页面重建、重复导航或某些生命周期重入可能追加相同挑战历史。回归时应记录进入前后的数组长度,重复进入同一结果参数后仍只能增加一条。

工程上可以给每次挑战生成唯一 sessionId,并在写入时去重;也可以把历史写入放在挑战提交服务中,结果页只负责展示。当前源码尚未实现这一点,属于明确的回归风险。

二十、结果页排名是静态展示,不应作为联网测试

结果页显示${236}/${1258},它不是服务端返回的真实排名。回归测试只能验证文字布局稳定,不能记录“在线排行榜请求成功”或“排名数据同步成功”。

同理,测试报告不能虚构 PV、点赞、收藏或云端账号数据。收藏是本机题目记录,挑战历史也是本地数据。测试对象必须与真实数据源一致。

若后续要接入真实榜单,应先定义请求失败、超时、无网络、匿名标识和隐私说明,再把静态占位替换为明确的加载、成功、空态和失败状态。在此之前,验收表应将这一格标为“静态展示”,避免测试人员把固定数字误判成已经具备联网能力。

二十一、持久化回归必须跨重启

收藏、错题、笔记、进度和挑战历史最终通过本地数据管理器保存。只在当前页面观察 StorageLink 变化,只能证明内存状态更新。

一条完整持久化用例应执行:

  1. 清空目标数据并重启;
  2. 完成一次明确操作;
  3. 记录界面和内存结果;
  4. 退出页面或结束应用;
  5. 重新启动并回到对应页面;
  6. 验证记录仍存在且数量准确;
  7. 执行删除,再次重启验证已消失。

这样才能发现写入失败被吞掉、键名不一致或清理不彻底等问题。

二十二、测试数据要可重复

随机练习使用洗牌,挑战题则按题库 ID 和当前 UTC 小时生成种子。同一小时内挑战题相对稳定,跨小时会变化。自动化测试如果直接断言某道固定题,可能在整点后失败。

可选策略包括:

  • 单元测试直接传入固定 Question 数组;
  • 把时间源封装为可注入依赖;
  • 只断言数量、唯一性和题目属于输入集合;
  • 记录测试开始时的小时边界,避免跨整点执行。

测试稳定不等于业务取消随机性,而是把随机和时间依赖控制在可验证边界内。

二十三、多设备回归不能只改窗口宽度

源码对宽度和currentBreakpoint都有判断。例如题库详情只有在currentBp === 'lg' && pageWidth >= 700时使用双列布局。统计页也会在宽屏切换网格。

回归矩阵至少覆盖 phone 竖屏、phone 横屏或小窗、tablet 宽屏和 2in1 调整窗口。检查重点是:

  • 导航模式是否符合断点;
  • 列表、卡片和底部动作是否可达;
  • 文本是否截断或重叠;
  • 弹窗和软键盘是否遮挡保存操作;
  • 系统底部手势区是否保留安全距离。

只修改pageWidth而不更新断点,不能代表真实设备切换。

二十四、回归证据要能定位版本

每轮发布测试建议保存:

  • 构建版本名和版本号;
  • 包名与签名环境;
  • 设备类型、系统版本和窗口尺寸;
  • 用例编号、前置数据和执行时间;
  • 实际结果、截图或日志;
  • 失败是否可复现及对应源码提交。

日志不应包含用户笔记、账号凭据或私密信息。本文也不会输出或保存密码、Cookie、Token、短信码和证书密码。

二十五、第一批 Hypium 用例应从纯函数开始

最适合先落地的目标是QuestionUtilsStatService

  • calcScore([], 0) === 0
  • 全对、全错和部分正确得分;
  • formatTime(0/59/60/3599)
  • 空统计返回全零;
  • 有进度时正确汇总;
  • 空挑战历史平均分为 0;
  • 最近五次平均只取约定范围。

这些用例不依赖 UIAbility、router 或 Preferences,失败定位最直接。待纯函数门禁稳定后,再增加组件和真实设备用例。

二十六、页面测试应围绕状态转换断言

页面测试不要依赖“截图看起来差不多”。更可靠的是断言状态转换:

场景初始状态动作期望状态
空搜索searched=false提交空格仍为引导态
无匹配searched=false搜索不存在词无结果态
非法题库bank=undefined传未知 ID未找到题库
重复答题records=0连点两次records=1
手动抢自动下一题index=0答题后立即下一题最终 index=1
重复交卷submitted=false连点或超时竞争只写一次

状态断言与可见文案结合,才能同时覆盖业务和体验。

二十七、发布前的最小回归清单

在每个候选包上至少执行:

  • 干净安装并正常启动;
  • 五个主标签切换与系统返回;
  • 搜索初始态、无结果和分类结果;
  • 非法题库 ID 空态;
  • 正常答题、答错、重复点击和自动下一题;
  • 空错题、空收藏、空历史和零统计;
  • 挑战正常交卷、倒计时交卷和结果回看;
  • 收藏、笔记、错题、进度在重启后仍一致;
  • phone、tablet、2in1 关键布局;
  • 卸载后不依赖第三方工具清理。

没有执行的检查必须标记为“未运行”,不能写成通过。

二十八、当前源码的通过项与风险项

从代码静态复核可以确认:

  • 统计和评分对零分母有保护;
  • 搜索区分初始态与无结果态;
  • 非法题库 ID 有可见空态;
  • 选项重复点击不会重复记分;
  • 自动下一题前检查当前解析状态;
  • JSON 解析错误有基础降级。

仍需通过实现或运行测试验证:

  • Hypium 业务用例尚未建立;
  • 空题集缺少专门提示;
  • 通用导航入口没有防抖;
  • 交卷竞争缺少独立幂等标志;
  • 结果页重复出现可能重复写历史;
  • JSON 成功解析后缺少结构校验;
  • 持久化写入需要跨重启证据。

二十九、结语

回归测试的核心不是用例数量,而是把一次用户动作和一次状态变化对应起来。对口算王而言,最值得优先锁定的契约是:空数据必须可解释,坏参数必须可退出,重复答题只能记录一次,交卷只能落库一次,写入后重启仍能回读。

从纯函数 Hypium 用例开始,再补页面状态、导航防抖和跨重启持久化测试,能够以较小成本建立真正可重复的发布门禁。测试报告只记录实际运行证据,静态代码复核、模拟器验证、真机验证和 AppGallery 检查应分别标注,不能相互冒充。

本文部分内容由 AI 辅助整理,所有源码事实、测试边界和结论均以口算王项目实际代码复核为准。

三十、把失败证据也纳入交付

回归报告不能只记录通过项。失败用例应保存用例 ID、前置数据、实际结果、预期结果、首个错误位置和是否阻塞发布。环境、签名或设备不可用导致的未执行,需要单独标为not run,不能算通过或失败。只有修复后重新执行同一操作序列,并得到一致结果,才能关闭问题。

自动化测试适合纯逻辑、存储适配和稳定页面状态;真机冒烟仍需覆盖安装、首次启动、核心答题、退出重启和卸载。两类证据不能相互替代。一个单元测试通过不证明页面布局可用,一次人工点击正常也不证明边界算法长期稳定。

三十一、测试数据必须可恢复

每条用例开始前重置到明确数据快照,结束后核对并恢复。空数据用例不能依赖上一轮是否刚好清理过;重复点击用例要在动作前后读取记录数量和具体身份键;重启恢复用例必须真正重新初始化存储服务,而不是只刷新组件状态。测试若修改共享开发数据,应使用隔离命名空间或专用配置,避免污染真实草稿。

本文本轮没有执行 Hypium、构建、模拟器或真机回归。文章中的矩阵是根据源码风险设计的验收方案,当前只能确认默认模板未覆盖业务逻辑,不能声称任何新增用例已经通过。

AI 辅助声明

本文在人工复核口算王 ArkTS 源码与测试目录后,使用 AI 辅助整理结构、润色表达并生成配图;未执行的 Hypium、构建、模拟器和真机测试均未写成已通过。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 19:19:11

从零搭建AgentChat:构建能调用工具的智能对话系统

1. 项目概述与核心价值1.1 AgentChat 到底是什么先直接说结论:AgentChat 不是一个玩具项目,它本质上是一个“能干活”的对话系统。传统聊天机器人只能一问一答,你说一句它接一句,而 AgentChat 里跑的是一个智能体循环——模型不仅…

作者头像 李华
网站建设 2026/9/7 19:17:55

Linux文本编辑器实战指南:从Vim到sed的选型与高效运维

接手一台新服务器,第一件事往往不是装环境,而是先问自己一个问题:我到底要拿什么改文件?这个看似基础的问题,恰恰是Linux文本编辑器运用中最容易被低估的一环。很多人从图形界面切过来,第一反应是“没有记事…

作者头像 李华
网站建设 2026/9/7 19:17:17

基于Flask的食品配料表安全健康分析APP毕业设计实战

“作为一个在超市里习惯性翻到包装背面、把配料表从头读到尾的人,我一直觉得配料表是食品工业写给消费者的加密文件。”这是我做这个选题的起点,也是整个毕业设计的灵魂。这篇博客要聊的是一个基于Flask的食品配料表安全健康APP,一套能运行、…

作者头像 李华
网站建设 2026/9/7 19:16:19

猫抓扩展使用指南:网页资源嗅探与下载实操

猫抓扩展使用指南:网页资源嗅探与下载实操 【免费下载链接】cat-catch 猫抓 浏览器资源嗅探扩展 / cat-catch Browser Resource Sniffing Extension 项目地址: https://gitcode.com/GitHub_Trending/ca/cat-catch 猫抓(cat-catch)是一…

作者头像 李华
网站建设 2026/9/7 19:16:02

Python+微信小程序火车票预订系统:余票计算与锁座机制解析

要说大学四年做过最有成就感的项目,我肯定选这套基于 Python 的微信小程序铁路火车高铁座位预订售票系统。当时选这个题目,一是因为交通出行这个场景天然适合移动端,二是前后端分离的开发模式非常锻炼人,从需求分析到部署上线跑通…

作者头像 李华