单元测试与 UI 测试arkXtest 框架与工程实践一、引言短视频工程的复杂度横跨六个设备形态、四个入口 HAP 与若干特性模块任何一次顺手重构都可能悄悄破坏断点判定、数据模型默认值或窗口状态同步这类基础能力。若没有自动化测试兜底回归只能靠人肉点测——而多设备场景下人肉回归的覆盖面天然不足。multi-short-video 工程目前尚未建立 test 目录本文基于工程内真实的可测单元WidthBreakpointType、MSVDataModel、CommonConstants、Logger等讲解如何用 arkXtest 框架补齐单元测试与 UI 测试覆盖从工具类到页面交互的质量闭环。HarmonyOS 的测试体系分三层arkXtest 单元测试验证函数/类的行为、UI 测试驱动真机/模拟器验证界面交互、性能与稳定性测试。本文聚焦前两者。测试对多设备工程还有一层特殊意义六个形态、四个入口意味着回归面被放大数倍而人工回归往往只覆盖主路径。自动化用例充当最低保障网——它不替代人工测试但保证每次改动后断点判定、数据默认值、页签切换这些基础行为不会悄悄退化。本文所有示例都基于工程现有代码可直接复制到新建设的测试目录中运行。二、arkXtest 框架基础与测试目录结构arkXtest 是 ArkTS 官方的单元测试框架语法与 Jest 类似describe组织测试套件、it声明单条用例、expect做断言。工程建议在模块下新建ohosTest目录DevEco Studio 新建模块时默认生成结构如下features/multishortvideobase以公共模块为例 └── ohosTest/ └── ets/ └── test/ ├── List.test.ets # 工具类与模型测试 └── Ability.test.ets # 模块级冒烟测试TestRunner与测试套件注册在module.json5或测试配置中声明IDE 与命令行均可触发。一个最小用例// ohosTest/ets/test/List.test.ets import { describe, it, expect } from ohos/hypium; export default function listTest() { describe(CommonConstantsTest, () { it(FULL_PERCENT_should_be_100_percent, 0, () { expect(CommonConstants.FULL_PERCENT).assertEqual(100%); }); }); }describe名称使用 PascalCase 表达被测对象it的第二个参数是用例编号同一 describe 内递增断言统一走expect(...).assertXxx(...)链式 API。arkXtest 常用断言如下断言说明使用场景assertEqual值相等返回字符串、数字、枚举assertTrue/assertFalse布尔成立条件分支、存在性判断assertNull/assertNotNull是否为空可选字段、可选参数assertLarger/assertLess数值比较列表长度、计数assertContain包含关系字符串、数组成员assertThrowError期望抛错非法参数防御数值与字符串断言尽量选择精确匹配避免用布尔断言糊过去对可能为 undefined 的字段用assertNull/assertNotNull明确表达预期。三、针对工程真实代码编写测试用例工程中最值得测、也最好测的单元是无 UI 依赖的纯逻辑。以断点工具WidthBreakpointTypecommon/multishortvideobase/src/main/ets/utils/WidthBreakpointType.ets为例它接收四个断点取值并按当前宽度返回对应项是评论区、个人页、TabBar 大量布局分支的决策核心但当前没有一条测试用例——这正是单测的首选目标it(getValue_should_pick_value_by_breakpoint, 0, () { const bp new WidthBreakpointTypestring(xs, sm, md, lg); expect(bp.getValue(WidthBreakpoint.WIDTH_XS)).assertEqual(xs); expect(bp.getValue(WidthBreakpoint.WIDTH_SM)).assertEqual(sm); expect(bp.getValue(WidthBreakpoint.WIDTH_MD)).assertEqual(md); expect(bp.getValue(WidthBreakpoint.WIDTH_LG)).assertEqual(lg); }); it(getValue_should_fallback_to_last_when_out_of_range, 1, () { const bp new WidthBreakpointTypenumber(10, 20, 30, 40); expect(bp.getValue(WidthBreakpoint.WIDTH_XL)).assertEqual(40); });数据模型同样适合单测。MSVDataModelcommon/multishortvideobase/src/main/ets/model/MSVDataModel.ets的构造函数带默认值逻辑可以锁定未传参时的兜底行为it(iconSize_should_default_to_36, 0, () { const model new MSVDataModel(home, $r(app.string.home_title)); expect(model.iconOnly).assertFalse(); expect(model.iconSize).assertEqual(36); });除了公共模块特性模块的纯逻辑同样值得覆盖。以评论数据模型CommentDataModelfeatures/multishortvideocomment/.../model/CommentDataModel.ets与视频数据模型AvDataSourceModelfeatures/multishortvideoadaptivevideo/.../model/AvDataSourceModel.ets为例它们承载了评论内容、回复列表、视频比例与播放地址等字段直接决定 UI 渲染内容。测试可断言构造参数能正确落位如currentSource与 rawfile 路径一致、可选字段如replys为空时的默认行为、以及AvDataSourceViewModel返回的列表与资源定义的数量一致。这类用例把数据模型 → ViewModel → UI链路的前两环锁住UI 层出问题时能迅速排除数据层嫌疑。再如Logger工具类common/.../utils/Logger.ets虽然它的输出目标是 hilog但可以通过注入方式验证其不抛异常把 hilog 调用封装在可替换的接口后面测试中注入桩实现断言各级别方法在正常/异常参数下都能安全调用。这类测试的价值不在断言本身而在于防止后续重构破坏公共 API 的签名与默认行为——MSVDataModel构造参数顺序、WidthBreakpointType的取值顺序一旦变动测试会第一时间报警。class LoggerStub { public lastArgs: Object[] []; public info(...args: Object[]): void { this.lastArgs args; } } it(logger_should_not_throw_when_empty_args, 0, () { const stub new LoggerStub(); const logger new Logger(test); expect(() logger.info(hello, world)).not.assertThrowError(); });依赖注入的边界设计还有一层工程考量被测对象应接近真实而非过度打桩。ViewModel 的 mock 数据直接来自 string.json 资源与本地文件测试断言的是真实资源经过 ViewModel 加工后的结果比纯手工构造的假数据更有回归价值只有当外部能力hilog、窗口、数据库会带来不确定性时才用桩替换。这条原则避免了测试测了个寂寞——用例验证的必须是生产路径上真实发生的逻辑而不是一套只在测试环境存在的平行实现。四、UI 测试与组件定位UI 测试基于ohos.UiTest即 UiTest 框架驱动真实页面先Driver.create()建立会话再用By查找器按 id、text、类型定位组件最后触发点击/滑动并断言结果。工程组件普遍设置了id如MSVTabs内部MSVTabContent的stackId为 UI 测试提供了稳定锚点import { Driver, ON, By } from ohos.UiTest; export default function uiTest() { describe(IndexUITest, () { it(tabBar_should_switch_to_mine_tab, 0, async () { const driver Driver.create(); // 按文本定位我的页签并点击 const mineTab await driver.findComponent(By.text(我的)); await mineTab.click(); // 断言个人页关键文案出现 const intro await driver.findComponent(By.text(点击添加介绍让大家认识你...)); expect(intro).assertTrue(); }); }); }UI 测试要点优先按 id 定位文案随语言切换By.text会因语言环境失效组件id与语言无关是更稳的锚点等待与超时异步渲染、动画转场需要await driver.waitForComponent(...)或合理超时避免时序抖动多设备矩阵工程六个形态差异大UI 用例需标注运行设备deviceType至少覆盖直板机与平板两种断点才能覆盖分栏/全窗口两类布局分支隔离外部依赖测试环境禁用真实网络与系统级交互评论、作品等数据全部走 ViewModel 内置的 mock 数据源见下节保证用例可重复。五、Mock 数据与依赖注入工程的 ViewModel 层天然适合依赖注入 MockCommentViewModel().getCommentList()、MainTabsViewModel().getMainTabsData()、AvDataSourceViewModel().getAvDataSource()都返回内存中的 mock 数据数据取自 string.json 资源与本地 rawfile 视频没有网络与数据库依赖。这让单元测试几乎不需要额外 mock 框架it(getCommentList_should_return_non_empty, 0, () { const vm new CommentViewModel(); const list vm.getCommentList(); expect(list.length).assertLarger(0); expect(list[0].name).assertTrue(); });当被测对象需要注入外部能力时如Logger的 hilog、WindowUtil的窗口对象采用构造器注入或可替换的模块级引用测试中替换为桩实现验证调用关系与参数而不是真实能力。这与本文第五节日志中把 hilog 封装在可替换接口后的思路一致接口的稳定边界同时服务了可测性。六、测试脚本执行与覆盖率测试在 DevEco Studio 中可直接右键用例运行命令行场景由 hvigor 驱动常用命令# 在模块目录下执行 ohosTest 测试 hvigorw --mode module -p modulemultishortvideobasedefault -p isTesttrue assembleHap hvigorw test --buildMode debug覆盖率方面DevEco Studio 的测试面板提供行覆盖率与分支覆盖率统计。工程实践建议按优先级推进第一阶段覆盖纯逻辑层断点工具、数据模型、ViewModel 的 mock 数据方法目标行覆盖率 80% 以上第二阶段补充关键路径的 UI 用例首页页签切换、评论半模态打开、个人页九宫格切换不追求数量追求每个断点分支都有用例经过。覆盖率低的分支如多设备差异分支恰好就是多设备回归的高风险区值得优先补测。CI 集成是测试发挥价值的最后一公里。工程建议在推送流水线中挂两条测试任务unit test跑全部 ohosTest 单测失败即阻断合入与ui test真机矩阵按设备形态抽样至少覆盖直板机与平板。覆盖率报告作为制品归档每个迭代对比增量代码的覆盖缺口形成新增逻辑必带用例的团队约定。测试耗时控制上单测应控制在分钟级以内——若某个用例超过秒级通常意味着它依赖了不该依赖的外部能力需要继续拆分 Mock 边界。最后回到工程整体视角测试应当按金字塔结构布局底层是大量廉价的单元测试断点工具、数据模型、ViewModel中层是少量 UI 组件测试页签切换、半模态、九宫格顶层是极少数端到端冒烟多设备矩阵。金字塔越往上维护成本与执行时间越高用例数量应当越少。工程目前的起步建议是每个模块至少为纯逻辑单元建一个*.test.ets预计每个模块 5~15 条用例公共模块multishortvideobase优先级最高——它的WidthBreakpointType、MSVDataModel、Logger被所有特性模块依赖回归风险也最高。落地节奏上不要求一次铺满而是新增代码必带用例、存量代码按覆盖率补测两个迭代内把纯逻辑层覆盖到 80% 以上即可形成基本安全网。测试不是负担而是重构与多设备适配的底气没有用例兜底顺手改个断点都可能变成线上事故。七、总结与最佳实践单元测试与 UI 测试的落地路径归纳为五条最佳实践先测纯逻辑WidthBreakpointType、MSVDataModel、CommonConstants这类无 UI 依赖的单元是单测性价比最高的对象优先补齐并锁定默认值与边界行为。测试目录即规范按模块建立ohosTest/ets/test/*.test.etsdescribe用被测类命名it编号递增断言统一expect。UI 测试锚定 id、等待转场用组件id而非文案定位文案受国际化影响异步场景显式等待多设备用例至少覆盖两种断点。Mock 走 ViewModel 边界工程 ViewModel 内置 mock 数据源测试直接复用外部能力hilog、窗口用注入或可替换引用打桩。覆盖率牵引补测以行覆盖率 80%纯逻辑层为牵引优先补齐多设备差异分支让每一次重构都有自动化回归兜底。
