
鸿蒙测试框架实战 — 从单元测试到 UI 测试的全栈覆盖文章简介测试是保障代码质量的关键手段。HarmonyOS 提供了一套完整的测试框架 hypium支持单元测试LocalUnit、集成测试ohosTest和 UI 测试等不同层次的测试。MoneyTrack 在各个模块中都配置了测试目录通过 List.test.ets 和 Ability.test.ets 组织测试用例。本文从测试分层架构、框架 API 详解到实际测试编写系统介绍鸿蒙应用的测试实践。测试分层架构在鸿蒙应用中测试按覆盖范围从底向上分为三个层次每一层都有明确的职责划分不依赖真机需要真机/模拟器需要真机/模拟器测试分层架构Unit 测试 / LocalUnitIntegration 测试 / ohosTestUI 测试 / UiTest纯 TypeScript/ArkTS 逻辑工具函数测试ViewModel 计算逻辑数据模型验证Ability 生命周期系统 API 调用模块间交互页面元素定位用户操作模拟端到端流程验证核心知识点1. hypium 测试框架hypium 是 HarmonyOS 的官方测试框架采用 BDD行为驱动开发风格提供简洁的 describe/it/expect APIimport{describe,it,expect}fromohos/hypium;describe(Calculator Tests,(){it(should add two numbers correctly,(){constresultadd(1,2);expect(result).assertEqual(3);});it(should handle edge cases,(){expect(0.10.2).assertClose(0.3,0.001);expect(()divide(1,0)).assertThrowError(Division by zero);});});hypium 核心 API 一览API功能示例describe(title, fn)定义测试套件describe(HomeVM, () {})it(title, fn)定义测试用例it(should init data, () {})expect(actual)创建断言对象expect(result).assertEqual(expected)断言相等expect(a).assertEqual(3).assertTrue()断言为真expect(flag).assertTrue().assertContain(item)断言包含expect(list).assertContain(a).assertClose(expected, delta)断言近似相等expect(0.10.2).assertClose(0.3, 0.001).assertThrowError(msg?)断言抛出异常expect(fn).assertThrowError()’beforeAll(fn)套件前执行一次beforeAll(() initMock())afterAll(fn)套件后执行一次afterAll(() cleanup())beforeEach(fn)每条用例前执行beforeEach(() resetState())afterEach(fn)每条用例后执行afterEach(() clearData())2. LocalUnit 本地测试LocalUnit 测试不依赖真机在本地运行适合测试纯逻辑代码。MoneyTrack 项目中的测试目录结构如下src/test/ ├── LocalUnit.test.ets # 本地测试入口 └── unittest/ ├── home/ # Home 模块测试 └── common/ # 公共工具测试测试工具函数示例// src/test/unittest/common/DateUtils.test.etsimport{describe,it,expect}fromohos/hypium;import{formatDate,parseAmount}from../../../../commons/commonlib/src/main/ets/utils/DateUtils;describe(DateUtils,(){it(formatDate should return YYYY-MM-DD format,(){constdatenewDate(2024,0,15);// 2024-01-15expect(formatDate(date)).assertEqual(2024-01-15);});it(formatDate should pad single digit month/day,(){constdatenewDate(2024,8,5);// 2024-09-05expect(formatDate(date)).assertEqual(2024-09-05);});it(parseAmount should handle integer string,(){expect(parseAmount(100)).assertEqual(100);});it(parseAmount should handle decimal string,(){expect(parseAmount(99.99)).assertClose(99.99,0.001);});it(parseAmount should return 0 for invalid input,(){expect(parseAmount()).assertEqual(0);expect(parseAmount(abc)).assertEqual(0);});});测试 ViewModel 逻辑示例// src/test/unittest/home/HomeVM.test.etsimport{describe,it,expect,beforeEach}fromohos/hypium;import{HomeVM}from../../../../features/home/src/main/ets/viewmodel/HomeVM;describe(HomeVM,(){letvm:HomeVM;beforeEach((){vmnewHomeVM();});it(initial state should have empty bill list,(){expect(vm.billList).assertEqual([]);expect(vm.totalIncome).assertEqual(0);expect(vm.totalExpense).assertEqual(0);});it(should calculate totals correctly,(){vm.addBill({amount:100,type:expense});vm.addBill({amount:200,type:income});vm.addBill({amount:50,type:expense});expect(vm.totalExpense).assertEqual(150);expect(vm.totalIncome).assertEqual(200);});it(should filter bills by date range,(){vm.addBill({amount:100,date:2024-01-15,type:expense});vm.addBill({amount:200,date:2024-02-01,type:income});constfilteredvm.getBillsByRange(2024-01-01,2024-01-31);expect(filtered.length).assertEqual(1);expect(filtered[0].amount).assertEqual(100);});});3. ohosTest 集成测试ohosTest 需要在真机或模拟器上运行可以访问系统 API 和 UI 组件。每个模块的src/ohosTest/目录下存放集成测试代码。测试 Ability 生命周期// features/home/src/ohosTest/ets/test/Ability.test.etsimport{describe,it,expect}fromohos/hypium;import{UIAbilityContext}fromkit.AbilityKit;describe(EntryAbility Lifecycle Test,(){letabilityContext:UIAbilityContext;// 模拟 Ability 启动beforeAll(async(){abilityContextawaitcreateTestAbility(EntryAbility);});it(should create ability successfully,(){expect(abilityContext).assertTrue();expect(abilityContext.abilityInfo.name).assertEqual(EntryAbility);});it(should load main page on start,async(){constpageUrlabilityContext.getLastPageUrl();expect(pageUrl).assertContain(pages/Index);});it(should handle onForeground correctly,async(){awaitabilityContext.onForeground();expect(abilityContext.isForeground).assertTrue();});it(should handle onBackground correctly,async(){awaitabilityContext.onBackground();expect(abilityContext.isForeground).assertFalse();});it(should restore state after onNewWant,async(){constwant{parameters:{target:settings}};awaitabilityContext.onNewWant(want);constpageUrlabilityContext.getLastPageUrl();expect(pageUrl).assertContain(pages/Settings);});afterAll((){destroyTestAbility(abilityContext);});});测试用例组织List.test.ets// features/home/src/ohosTest/ets/test/List.test.etsimport{describe}fromohos/hypium;import{abilityTest}from./Ability.test;import{homeVMTest}from./HomeVM.test;exportfunctionentryTest():void{describe(EntryAbility Suite,abilityTest);describe(HomeVM Suite,homeVMTest);}4. LocalUnit 与 ohosTest 对比对比维度LocalUnit 本地测试ohosTest 集成测试运行环境本地 IDE不依赖真机真机或模拟器测试目录src/test/src/ohosTest/系统 API 访问不可访问可完整访问执行速度极快毫秒级较慢秒级适用场景工具函数、数据模型、ViewModel 逻辑Ability 生命周期、UI 交互、系统服务调试方式直接运行断点调试需连接设备调试CI/CD 集成可在流水线中直接运行需要设备池或云真机代码覆盖率支持覆盖率收集支持覆盖率收集5. 测试覆盖率hypium 测试框架支持收集测试覆盖率数据帮助团队衡量测试的充分性。覆盖率指标包括行覆盖率Line Coverage衡量代码中哪些行被执行过分支覆盖率Branch Coverage衡量条件判断的真假分支是否都被覆盖函数覆盖率Function Coverage衡量每个函数是否被调用在build-profile.json5中开启覆盖率收集{ buildOption: { coverage: { enabled: true, include: [**/main/ets/**/*.ets], exclude: [**/node_modules/**] } } }MoneyTrack 项目要求核心业务模块的代码行覆盖率不低于 80%工具函数模块不低于 90%。最佳实践测试金字塔原则LocalUnit 测试应占 70% 以上ohosTest 占 20%UI 测试占 10%。本地测试速度快、维护成本低应优先覆盖。测试隔离每条测试用例应相互独立使用beforeEach重置状态避免测试间数据污染。依赖注入在 ViewModel 和数据层中使用依赖注入方便在测试时替换为 Mock 实现避免对外部系统的依赖。边界值覆盖对工具函数测试时除了正常输入外一定要覆盖空值、负值、大数等边界情况。测试命名规范测试用例命名应清晰描述被测行为和预期结果推荐格式should [预期行为] when [条件]如should return zero when amount is negative。持续集成将 LocalUnit 测试集成到 CI/CD 流水线中每次提交代码自动运行确保新代码不会破坏已有功能。推荐参考文档HarmonyOS hypium 测试框架文档LocalUnit 本地测试开发指南ohosTest 集成测试配置说明测试覆盖率工具使用指南