ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

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

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

一个 HarmonyOS 工程里出现src/test、ohosTest和 Hypium 依赖,不代表核心功能已经受到测试保护。真正有效的回归用例必须能在需求变更后回答:冷启动是否恢复本地数据,空列表是否给出可理解反馈,未选择答案能否提交,连续点击会不会重复记分,参数运行期间是否还能被修改,收藏按钮是否误触整行导航。

本文基于化学实验室 1.0.5的真实 ArkTS 源码展开。工程已经依赖@ohos/hypium 1.0.25与@ohos/hamock 1.0.0,但entry/src/test/LocalUnit.test.ets和entry/src/ohosTest/ets/test/Ability.test.ets仍是模板生成的abc包含b断言,没有引用任何实验、收藏、答题、持久化或路由代码。

源码审计还发现几个很适合转成回归用例的真实风险:ExamPracticePage在未选择答案时用正确答案作为兜底并立即记为正确;提交后立即切到下一题,连续点击可能继续用兜底答案记分;DailyQuizPage的异步提交按钮在hasSubmitted后变成“重新作答”,快速二次点击可能先重置页面再等待前一次保存完成;LabPage的收藏按钮位于可点击列表行内部,收藏与进入实验可能发生事件竞争;EntryAbility同时启动DataStore.init()与loadContent(),冷启动恢复存在时序边界。

回归测试不是把页面走一遍,而是把每个可失败的状态转换成可重复输入、明确预期和可追踪证据。

CHEM-LAB-ONE12-REGRESSION-RISK-GATES-20260730

本文将完成以下内容:

  • 盘点工程现有测试文件与真实覆盖缺口。
  • 为启动、空数据、损坏数据、异常输入和重复点击建立风险表。
  • 把答题、收藏和实验运行规则抽成可单测纯逻辑。
  • 使用 Hypium 编写未选答案、重复提交和数据去重用例。
  • 设计 Preferences 正常、空值、损坏 JSON 和写入失败测试。
  • 为五个主页面建立导航、空态和状态恢复场景。
  • 验证实验参数边界、运行中禁用、暂停、重置与完成去重。
  • 给出手机、平板、2in1、深浅色与字体放大的回归矩阵。
  • 建立构建、安装、启动、核心流和卸载的发布冒烟门禁。

一、现有测试只是脚手架

当前单元测试核心内容是:

it('assertContain', 0, () => { let a = 'abc' let b = 'b' expect(a).assertContain(b) expect(a).assertEqual(a) })

ohosTest 中也存在同样断言。它能证明测试框架被加载,却不能证明应用行为正确。只要abc仍包含b,即使首页打不开、答题全部自动判对、收藏重复写入,测试仍会通过。

第一步不是继续增加模板断言,而是删除与业务无关的示例,按风险建立测试目录:

entry/src/test/ exam/ experiment/ favorites/ persistence/ routing/ entry/src/ohosTest/ets/test/ startup/ main-tabs/ experiment-flow/ learning-flow/ settings-flow/

纯函数和业务状态放单元测试,依赖 UIAbility、窗口、路由与真实组件的流程放 ohosTest 或设备回归。

二、先用风险表决定测什么

测试数量不是目标,覆盖高损失路径才是目标。根据源码可建立以下优先级:

风险触发条件用户后果优先级
冷启动数据未恢复页面早于 DataStore 初始化收藏与统计短暂为零高
未选答案被自动判对专项页直接点提交统计失真高
连点重复记分提交后立即进入下一题多题被错误写入高
收藏与行导航竞争点击星标事件冒泡意外进入实验高
空列表无反馈数据源为空或筛选无结果页面看似损坏中
JSON 损坏后静默归零Preferences 内容异常用户数据消失且无提示中
实验运行中改参数状态控制失效引擎与界面不一致高
多次开始产生多个定时器连续点击开始速度异常、重复记录高
路由异常被吞掉路由名错误点击无反馈中

每个高风险项至少有一个自动用例和一个人工或设备场景。测试名直接描述行为,例如submitWithoutSelection_shouldReject,而不是testButton1。

三、先修复可被稳定复现的未选答案缺口

ExamPracticePage当前代码:

private fallbackOptionId(question: ExamQuestion): string { if (this.selectedOptionId) { return this.selectedOptionId } return question.answerId }

用户不选择任何选项直接点击“提交答案”,方法返回正确答案,随后isCorrect为 true。这不是异常输入保护,而是把缺失输入转换成满分答案。

修复方向是显式拒绝:

interface SubmitDecision { accepted: boolean selectedOptionId: string message: string } export function decideSubmission(selectedOptionId: string): SubmitDecision { if (!selectedOptionId) { return { accepted: false, selectedOptionId: '', message: '请先选择一个答案。' } } return { accepted: true, selectedOptionId, message: '' } }

页面只有在accepted为 true 时才计算正确性、写入记录和切换题目。

四、用 Hypium 固定异常输入规则

纯函数可以直接写单测:

import { describe, it, expect } from '@ohos/hypium' import { decideSubmission } from '../../main/ets/domain/ExamSubmission' export default function examSubmissionTest(): void { describe('ExamSubmission', () => { it('submitWithoutSelection_shouldReject', 0, () => { const result = decideSubmission('') expect(result.accepted).assertFalse() expect(result.selectedOptionId).assertEqual('') expect(result.message).assertEqual('请先选择一个答案。') }) it('submitWithSelection_shouldAccept', 0, () => { const result = decideSubmission('B') expect(result.accepted).assertTrue() expect(result.selectedOptionId).assertEqual('B') }) }) }

这组测试会阻止未来重新引入“无输入等于正确答案”的逻辑。页面文案改变时,可以只更新消息断言;接受与拒绝规则保持稳定。

五、专项练习的重复点击风险来自立即切题

当前submitAnswer()设置hasSubmitted = true后调用:

this.savePracticeRecords(nextRecords) this.nextQuestion()

nextQuestion()又执行resetQuestion(),立即把hasSubmitted设回 false。这样按钮没有等待保存完成,也没有保留提交中的状态。快速第二次点击会作用于下一题;若未选择,当前兜底规则又会记成正确答案。

建议引入提交状态机:

enum SubmitState { Idle, Saving, Saved, Failed } @State submitState: SubmitState = SubmitState.Idle private async submitAnswer(): Promise<void> { if (this.submitState === SubmitState.Saving) return const decision = decideSubmission(this.selectedOptionId) if (!decision.accepted) { this.feedback = decision.message return } this.submitState = SubmitState.Saving const result = await this.repository.saveAnswer(/* record */) this.submitState = result ? SubmitState.Saved : SubmitState.Failed }

保存完成后由用户点击“下一题”,或在成功反馈停留一个明确状态后切换。保存期间按钮 disabled,重复点击不会创建第二条记录。

六、重复提交单测要断言副作用次数

只断言按钮 disabled 不够,还要断言仓库写入次数:

class FakeExamRepository { saveCount: number = 0 async saveAnswer(): Promise<boolean> { this.saveCount++ return true } } it('doubleSubmit_shouldPersistOnce', 0, async () => { const repo = new FakeExamRepository() const controller = new ExamController(repo) controller.select('B') await Promise.all([ controller.submit(), controller.submit() ]) expect(repo.saveCount).assertEqual(1) expect(controller.answeredCount).assertEqual(1) })

如果实现先检查 busy、再同步置为 busy,这个测试会稳定通过;若 busy 在第一个await后才设置,两个提交都可能进入仓库,测试会暴露竞态。

七、每日一题的二次点击是另一种状态竞争

DailyQuizPage在点击提交后立即:

this.hasSubmitted = true await this.saveTodayAnswer()

按钮文案随即变成“重新作答”。快速第二次点击可能进入resetCurrent(),把界面恢复未提交,而第一次保存仍在进行。最终出现“页面显示未答、重启后又显示已答”的错觉。

测试步骤:

  1. 选择错误选项。
  2. 连续两次点击主按钮,间隔小于存储完成时间。
  3. 断言页面在保存期间不进入重置状态。
  4. 断言每日记录只有一条。
  5. 断言错题记录只有一条。
  6. 重启页面,断言反馈与持久化结果一致。

修复方式同样是Idle -> Saving -> Submitted,重新作答只能在Submitted状态由一次独立点击触发。

八、实验开始按钮已经有一层定时器保护

ExperimentSimPage.startTimer()首先调用:

private startTimer(): void { this.stopTimer() this.timerId = setInterval(() => { // step / draw / persist }, 16) }

这能避免同一个页面同时保留旧定时器和新定时器,是值得保留的幂等设计。回归测试仍要验证:

  • 连点开始后只有一个计时循环。
  • 暂停后step()不再增长。
  • 继续后从当前进度恢复,不重置参数。
  • 完成时实验次数只增加一次。
  • 返回页面后定时器被释放。

可把定时器封装成可注入的Clock,单测不用真实等待 16ms,也能控制 tick 次数。

九、实验参数边界来自 ParamDef

每个实验参数有min、max、defaultValue和step,Slider 使用这些边界,并在运行中:

.enabled(!this.isRunning)

需要覆盖以下输入:

输入预期结果
小于 min不进入引擎,或被显式钳制到 min
等于 min正常初始化
等于 max正常初始化
大于 max不进入引擎,或被显式钳制到 max
NaN/Infinity拒绝并恢复默认值
非 step 倍数按规则舍入
运行中修改控件禁用,参数不变

Slider UI 通常不会产生 NaN,但业务方法仍应防御来自恢复数据、路由参数或未来文本输入的非法数值。

十、为参数归一化建立纯函数

interface NumericRange { min: number max: number step: number defaultValue: number } export function normalizeParameter(value: number, range: NumericRange): number { if (!Number.isFinite(value)) return range.defaultValue const clamped = Math.max(range.min, Math.min(range.max, value)) const steps = Math.round((clamped - range.min) / range.step) return Number((range.min + steps * range.step).toFixed(4)) }

对应测试:

it('invalidValue_shouldUseDefault', 0, () => { const range = { min: 0.1, max: 2, step: 0.1, defaultValue: 0.5 } expect(normalizeParameter(Number.NaN, range)).assertEqual(0.5) }) it('overMax_shouldClamp', 0, () => { const range = { min: 0.1, max: 2, step: 0.1, defaultValue: 0.5 } expect(normalizeParameter(9, range)).assertEqual(2) })

十一、收藏按钮与整行导航要做事件测试

LabPage的星标 Text 有onClick(toggleFavorite),外层列表 Row 也有onClick(router.pushUrl)。在嵌套可点击区域里,必须用真机或 UI 自动化验证点击星标是否同时触发行导航。

场景用例:

前置:实验未收藏,停留在实验室列表 操作:只点击星标中心 预期:星标变实心,仍停留在列表 失败:同时进入实验详情,或出现两次收藏切换

若事件传播无法稳定隔离,改为把收藏按钮和行导航放在不重叠的点击区域,或让整行导航避开右侧操作区。视觉上位于同一行,不代表必须共享点击处理。

十二、收藏异步操作需要 busy 或目标状态

toggleFavorite()先读取当前数组,再异步调用 DataStore,最后赋值新数组。连续点击两次时,两次调用可能都基于同一旧状态,产生与用户最后意图不同的结果。

推荐两种方案:

  1. 对单个收藏 ID 设置pendingFavoriteIds,保存完成前禁用该星标。
  2. 使用“目标状态”写入,而不是无条件 toggle:setFavorite(id, true/false)。

回归断言应检查最终收藏状态和持久化数组都符合最后一次有效操作,并且同一 ID 不重复。

十三、冷启动要测试 DataStore 与首屏加载的竞态

EntryAbility.onCreate()启动:

DataStore.init(this.context).then(() => { hilog.info(DOMAIN, 'One9App', 'DataStore initialized') })

onWindowStageCreate()随后直接loadContent('pages/Index')。页面可能在 Preferences 初始化完成前出现。LabPage.aboutToAppear()若此时读取收藏,会拿到默认空数组;只有重新切页触发onPageShow()才可能恢复。

冷启动用例需要预置收藏与统计,杀进程后启动,验证首屏和首次进入实验室就显示正确数据,而不是切换一次后才刷新。架构上可以增加全局storageReady状态或启动加载页,让依赖数据的页面在初始化完成后读取。

十四、启动用例至少覆盖四种路径

场景前置条件预期结果
首次安装冷启动无 Preferences首页正常,无错误弹窗
有数据冷启动收藏、统计已存在首次进入即恢复
Preferences 损坏JSON 字段非法页面降级为空态,不崩溃
初始化失败仓库抛错核心静态内容可用,有可诊断日志

DataStore当前大量 catch 后返回默认值,这能减少崩溃,但也可能隐藏数据损坏。测试要同时检查页面结果和日志,不能只断言“应用没退出”。

十五、空数据不是空白页面

LabPage直接对filteredExperiments()执行ForEach,没有空分支。如果数据源为空或类别筛选结果为空,列表区域只剩背景,用户无法判断是加载失败、没有数据还是筛选无结果。

推荐状态:

if (this.filteredExperiments().length === 0) { EmptyState({ title: '当前分类暂无实验', actionText: '查看全部', onAction: () => { this.selectedCategory = 0 } }) } else { this.ExperimentList() }

空态用例要断言标题、恢复操作和点击后的列表结果。收藏、笔记、错题和实验记录页也要分别测试首次为空、清除后为空和加载失败三种状态。

十六、损坏 JSON 要区分恢复与告警

DataStore.loadRecords()解析失败后返回空数组:

try { return JSON.parse(json) as T[] } catch { return [] }

单测至少覆盖:[]、正常对象数组、空字符串、截断 JSON、错误类型对象。页面可以降级为空,但服务层应记录键名与解析失败,避免用户数据静默消失后毫无证据。

it('brokenJson_shouldReturnEmptyAndReport', 0, () => { const reporter = new FakeErrorReporter() const result = parseRecords('[{"id":', reporter) expect(result.length).assertEqual(0) expect(reporter.count).assertEqual(1) })

十七、路由失败不能只吞异常

HomePage.openPage()当前:

private openPage(url: string): void { try { router.pushUrl({ url: url }) } catch (_) { } }

路由名错误时,用户点击无任何反馈。回归测试很难区分“没点到”和“路由失败”。建议封装NavigationService,返回成功或失败并记录目标路由;页面失败时显示简短 Toast。

路由用例需要覆盖首页八个快捷入口、实验行、每日一题错题本、设置页三个信息页面,以及系统返回。每个入口断言目标页唯一标题,而不是只检查点击事件被触发。

十八、Tab 回归要检查状态保留

Index.ets用@State currentIndex管理四个主 Tab。回归步骤:

  1. 首页滚动到中部。
  2. 切到实验室并选择“高中化学”。
  3. 收藏一个实验。
  4. 切到学习页,再切回实验室。
  5. 断言分类和收藏状态符合产品约定。
  6. 再切回首页,检查滚动位置是否需要保留。

如果产品要求切换后重置,要在测试中明确;如果要求保留,也要固定。没有预期的“看起来都能切换”不是回归标准。

十九、页面尺寸变化也属于回归

HomePage和LearningPage根据 600、840 宽度改变列数;上一轮源码审计已经发现首页在 medium 宽度下 8 个项目、3 列需要 3 行,但 Grid 高度仍为 172vp。回归要覆盖断点临界值:

599vp / 600vp / 601vp 839vp / 840vp / 841vp

每个宽度检查项目数量、列数、行数、最后一项可见性和页面滚动。平板分屏与 2in1 自由窗口要在运行时缩放,确认布局持续重算。

二十、构建测试数据工厂

不要让测试直接依赖全量生产数据。为实验、答题和记录建立工厂:

export function makeQuestion(overrides?: Partial<ExamQuestion>): ExamQuestion { return { id: 'q_test_1', category: '实验题', title: '测试题', options: [ { id: 'A', text: '选项 A' }, { id: 'B', text: '选项 B' } ], answerId: 'B', analysis: '测试解析', tip: '测试提示', ...overrides } }

用工厂构造空选项、重复 ID、超长标题、单个选项和非法答案 ID。生产题库变化时,规则测试不必跟着大面积修改。

二十一、测试仓库要能模拟失败和延迟

class FakePreferenceRepository { data: Map<string, string> = new Map() delayMs: number = 0 shouldFail: boolean = false async putString(key: string, value: string): Promise<void> { if (this.shouldFail) throw new Error('write failed') if (this.delayMs > 0) await new Promise<void>((resolve) => { setTimeout(resolve, this.delayMs) }) this.data.set(key, value) } }

延迟仓库用于稳定复现重复点击,失败仓库用于验证页面不会显示虚假成功。真实 Preferences 集成测试再验证序列化和冷启动恢复,二者职责不同。

二十二、回归分层结构

推荐四层:

Unit Layer 答题决策、参数归一化、收藏去重、统计计算 Component Layer 空态、错误态、按钮禁用、反馈文本、焦点 Scenario Layer 冷启动、Tab、路由、实验流程、重复点击、窗口变化 Release Layer 安装、启动、核心流、前后台、卸载

单元层运行快,负责大多数边界组合;设备层运行慢,只验证生命周期和真实交互。不要把所有逻辑都留给 UI 自动化,也不要用纯函数测试代替窗口与路由验证。

二十三、五个主页面回归表

页面前置操作预期结果失败信号
首页首次安装启动、滚动、点快捷入口内容完整、路由正确白屏、点击无反馈
实验室有/无收藏筛选、收藏、进入实验状态正确且不误导航星标触发行跳转
学习工具数据正常打开题库、阅读、换算所有入口可达路由名失效
我的空/有统计打开记录、设置、政策计数与存储一致首屏显示旧值
Tabs各页有状态连续切换四个 Tab无崩溃、状态符合约定状态随机丢失

二十四、实验流程回归表

场景操作预期结果
默认参数进入后点击开始单一定时器运行
暂停继续开始、暂停、继续进度连续,不重复初始化
运行中改参数运行时拖 Slider控件禁用,参数不变
重置运行中点击重置定时器停止,进度归零
连续开始快速点击主按钮不产生多个计时器
完成等待引擎结束记录和实验次数各增加一次
返回运行中返回定时器释放,无后台增长
非法路由参数expId 不存在使用明确默认或错误页,不崩溃

二十五、答题流程回归表

场景操作预期结果
未选择直接提交提示先选择,不记分、不切题
正确答案选择正确项提交正确计数增加一次
错误答案选择错误项提交记录一次并可进入错题本
连续点击双击提交只保存一次
存储失败提交时模拟写失败明确失败,可重试,不虚增统计
重新作答保存完成后点击UI 与持久化规则一致
损坏历史注入非法 JSON页面可用并记录解析问题
全部答完继续下一题不越界、不除零、状态明确

二十六、设备与系统场景矩阵

维度覆盖值验收重点
设备phone、tablet、2in1主流程可达
窗口竖屏、横屏、分屏、自由窗口无裁切与空白
主题当前锁定浅色、未来深色系统栏与页面可读
字体默认与较大字体按钮、题目、卡片不遮挡
生命周期冷启、热启、前后台、杀进程数据恢复一致
数据空、正常、边界、损坏、写失败状态有反馈
交互单击、双击、快速切页、返回副作用不重复

二十七、构建与测试执行顺序

先确认工程可构建:

$env:DEVECO_SDK_HOME='D:\huawei\DevEco Studio\sdk' $env:PATH='D:\huawei\DevEco Studio\jbr\bin;' + $env:PATH & 'D:\huawei\DevEco Studio\tools\hvigor\bin\hvigorw.bat' --no-daemon assembleHap

然后在 DevEco Studio 运行entry/src/test的本地单元测试,再运行entry/src/ohosTest的设备测试。CLI 任务名应以当前工程的 hvigor 任务列表为准,不要把另一个模板的命令直接复制进流水线。

最后安装 release candidate,执行一次安装、启动、首页、实验、答题、收藏、设置清除、前后台、退出和卸载。构建通过、单测通过和设备冒烟通过是三个不同结果,报告中必须分别记录。

二十八、回归报告要保存环境与证据

每次回归至少记录:

应用版本 / versionCode HAP 或 APP 文件哈希 HarmonyOS 版本 设备型号与窗口模式 测试数据版本 单元测试通过/失败数量 设备场景通过/失败数量 失败步骤、日志与截图 修复提交与复测结果

华为 DevEco Testing 的回归服务支持步骤截图、日志和性能指标,适合把核心场景构建成独立测试包。无论使用本地 Hypium 还是平台服务,失败后都应保留第一处真实错误,不要只截最终空白页。

二十九、发布门禁

  • 模板abc测试已替换为业务断言。
  • 未选择答案不会被自动判对。
  • 提交、收藏和开始实验的连续点击不会重复副作用。
  • DataStore 初始化完成前页面有明确加载或刷新机制。
  • 空列表、损坏数据和写入失败都有用户可见状态。
  • 所有主入口和返回路径可达。
  • 599/600/601 与 839/840/841 宽度均完成布局检查。
  • phone、tablet、2in1 的核心流程已验证。
  • release candidate 可安装、启动、运行核心流并卸载。
  • 回归报告包含版本、环境、日志、截图和复测结果。

三十、源码事实与建议实现的边界

为了避免把测试设计误写成已经交付的能力,下面把本次源码审计能直接确认的事实、仍需实现的建议,以及验证方式分开列出。本文出现的示例代码属于测试设计稿,除非表中明确写为“当前源码”,否则不能据此推断工程已经完成修改、构建或设备验证。

关注点当前源码可确认的事实建议实现与验证
测试框架根目录已依赖@ohos/hypium 1.0.25和@ohos/hamock 1.0.0在现有框架内补业务测试,不以依赖存在代替覆盖证明
现有用例LocalUnit.test.ets与Ability.test.ets只有模板字符串断言替换为答案判定、参数边界、数据恢复和路由场景,并保存测试报告
专项练习未选答案fallbackOptionId()在没有选择时返回question.answerId,submitAnswer()会据此判定删除正确答案兜底,未选择时阻止提交;用单测断言记录数和得分都不变化
专项练习连续点击hasSubmitted能阻止同一题内再次进入,但提交后立即调用nextQuestion()增加操作标识或isSubmitting,在设备上连续点击并核对只产生一次持久化副作用
每日一题当前逻辑会拒绝空选择,提交后按钮进入“重新作答”状态对异步保存期间的二次点击建立场景测试;如能复现竞态,再加入提交锁
收藏交互收藏控件位于可点击列表行内部,两处都有点击处理用组件测试确认事件传播;必要时隔离点击区域,并用 busy 或目标状态避免异步覆盖
冷启动DataStore.init()在onCreate()启动,loadContent('pages/Index')在窗口阶段启动,两者没有同一个 ready 门禁增加可观察的初始化状态,覆盖正常、空数据、损坏数据和初始化失败四条路径
实验计时ExperimentSimPage已有定时器、离页清理和运行中禁用控制把参数归一化抽为纯函数,再补暂停、重置、离页和完成去重的设备回归
空数据与损坏数据数据读取存在空数组或默认值兜底继续验证页面是否给出用户可见的空态、恢复提示和重试入口,而不只验证“不崩溃”
构建与发布结果本文没有执行或记录本轮源码修改后的构建、安装、设备测试与卸载结果完成实现后依次运行构建、Hypium、ohosTest与 release candidate 冒烟,并把真实日志写入报告

这种边界划分有两个直接收益:读者能复核哪些结论来自现有 ArkTS,团队也能把建议逐项转成任务,而不会把文章中的伪代码当成已经合入的生产代码。对回归测试而言,“尚未验证”本身就是一条需要保留的工程事实。

三十一、结语

化学实验室已经具备 Hypium 与 Hamock 依赖,但当前测试仍停留在工程模板阶段。把测试升级为业务保护网的关键,不是增加更多assertContain,而是从真实源码中找出能造成统计失真、数据错乱、页面空白和重复副作用的状态。

本次审计最值得先落地的三项是:删除专项练习的正确答案兜底;为答题、收藏和异步保存增加提交状态;为冷启动存储恢复建立可观察的 ready 边界。它们都能先抽成纯逻辑,用 Hypium 快速覆盖,再由 UI 与设备测试验证真实生命周期。

当每个用例都写清前置数据、操作、预期结果和失败信号,并且构建、单元、设备、发布冒烟分别有证据时,回归测试才真正能够支撑频繁迭代,而不是只在测试目录里占一个位置。

参考资料

  1. Huawei Developer,其他测试服务与回归测试:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/other-test
  2. Huawei Developer,HarmonyOS 开发知识地图:https://developer.huawei.com/consumer/cn/app/knowledge-map/
  3. Huawei Developer,DevEco Testing 资源:https://developer.huawei.com/consumer/cn/deveco-testing/resources/
  4. 项目测试:entry/src/test/LocalUnit.test.ets
  5. 项目测试:entry/src/ohosTest/ets/test/Ability.test.ets
  6. 项目源码:entry/src/main/ets/entryability/EntryAbility.ets
  7. 项目源码:entry/src/main/ets/pages/LabPage.ets
  8. 项目源码:entry/src/main/ets/views/experiment/ExperimentSimPage.ets
  9. 项目源码:entry/src/main/ets/views/learning/DailyQuizPage.ets
  10. 项目源码:entry/src/main/ets/views/learning/ExamPracticePage.ets

AI 辅助声明:本文由 AI 辅助完成源码梳理、结构校对与配图设计,技术结论均以文中列出的本地 ArkTS 源码和测试文件为依据,并由作者复核后发布。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进