HarmonyOS掌上记账APP开发实践第8篇:构建可测试的鸿蒙应用 — MoneyTrack 的自动化测试体系
构建可测试的鸿蒙应用 — MoneyTrack 的自动化测试体系

概述
在软件开发过程中,测试是保障应用质量、防止回归缺陷和支撑快速迭代的关键环节。然而,移动应用开发中的测试面临诸多挑战:模拟器与真机环境的差异、UI 交互的复杂性、异步操作的时序问题,以及多设备适配带来的测试量激增。HarmonyOS 提供了基于 hypium 测试框架的完整测试体系,支持本地单元测试(Local Unit Test)和仪器化测试(Instrumentation Test)两种模式,分别适用于不同粒度和不同运行环境的测试需求。
MoneyTrack 项目作为一个具备 21 个 HAR 模块的中型应用,在多个模块中建立了规范化的测试目录结构,覆盖了从底层工具函数到上层 Ability 生命周期的测试场景。本文将从测试目录结构、hypium 框架 API 使用、本地测试与仪器化测试的选择策略、Mock 技术应用以及持续集成等角度,全面分析 MoneyTrack 的测试体系设计与最佳实践。
核心知识点
1. 测试目录结构与分类
每个 HAR 模块包含两套测试目录,分别对应两种测试类型:
- src/test/:本地单元测试(Local Unit Test),在开发机上运行,不依赖真机或模拟器,执行速度快,适合测试纯逻辑代码。
- src/ohosTest/:仪器化测试(Instrumentation Test),包括 UI 测试和集成测试,需在真机或模拟器上运行,适合测试依赖系统 API 或组件生命周期的场景。
以 commonlib 模块为例,其测试目录结构如下:
commons/commonlib/
├── src/test/
│ ├── List.test.ets # 测试入口,注册所有测试套件
│ └── LocalUnit.test.ets # 本地单元测试用例
└── src/ohosTest/
└── ets/test/
├── List.test.ets # 仪器化测试入口
└── Ability.test.ets # Ability 测试用例
MoneyTrack 的 21 个模块中,绝大多数模块都遵循这一目录规范,形成了统一的测试组织方式。
测试体系架构图
以下 mermaid 图展示了 MoneyTrack 的测试分层架构:
2. hypium 测试框架
hypium 是鸿蒙官方测试框架,提供 BDD(Behavior-Driven Development)风格的测试 API,包括 describe、it、expect 以及完整的生命周期钩子函数。
生命周期钩子
MoneyTrack 中的每个测试文件都完整使用了 hypium 的生命周期钩子,以实现测试环境的精确控制:
import {
describe,
beforeAll,
beforeEach,
afterEach,
afterAll,
it,
expect,
} from '@ohos/hypium';
export default function abilityTest() {
describe('ActsAbilityTest', () => {
// 在所有测试用例执行前执行一次
beforeAll(() => {
// 适合放置全局初始化逻辑:创建数据库、初始化全局状态等
});
// 在每个测试用例执行前执行
beforeEach(() => {
// 适合放置每个用例的预设操作:重置测试数据、清理状态等
});
// 在每个测试用例执行后执行
afterEach(() => {
// 适合放置每个用例的清理操作:释放临时资源、重置 Mock 等
});
// 在所有测试用例执行后执行一次
afterAll(() => {
// 适合放置全局清理逻辑:关闭数据库连接、释放全局资源等
});
it('assertContain', 0, () => {
let a = 'abc';
let b = 'b';
expect(a).assertContain(b);
expect(a).assertEqual(a);
});
});
}
各生命周期钩子的执行顺序为:beforeAll → beforeEach → it → afterEach(重复前三步)→ afterAll。
断言语法的使用
hypium 的 expect 模块提供了丰富的断言方法,适用于不同的验证场景:
// 字符串包含断言
expect(a).assertContain(b);
// 严格相等断言
expect(a).assertEqual(a);
// 真值断言
expect(isReady).assertTrue();
// 未定义断言
expect(result).assertUndefined();
// 实例类型断言
expect(obj).assertInstanceOf('Array');
3. 本地测试与仪器化测试的选择建议
在实际项目中,合理选择测试类型至关重要:
| 测试类型 | 运行环境 | 执行速度 | 适用场景 |
|---|---|---|---|
| 本地单元测试 | 开发机(JVM) | 快(毫秒级) | 纯逻辑函数、数据模型、工具类、无 UI 依赖的服务 |
| 仪器化测试 | 真机/模拟器 | 慢(秒级) | Ability 生命周期、UI 交互、系统 API 调用、跨模块集成 |
选择建议:
-
优先编写本地单元测试:对于不依赖系统 API 的纯逻辑代码(如数据格式化、金额计算、日期处理等),优先使用本地单元测试。它们执行速度快,可以在每次代码提交时完整运行。
-
将仪器化测试聚焦于关键场景:仪器化测试由于需要在真机上运行,执行速度较慢,应聚焦于核心业务流程(如页面跳转、数据持久化、Ability 生命周期)和需要系统 API 支持的功能(如推送通知、传感器数据)。
-
合理拆分:一个模块两套测试:MoneyTrack 的做法是每个模块同时保留
src/test/和src/ohosTest/,将纯逻辑测试放在本地测试中,将 UI 和集成测试放在仪器化测试中,既保证了覆盖率,又控制了测试执行时间。
4. 测试模块的 module.json5 配置
仪器化测试模块需要在 src/ohosTest/module.json5 中定义独立的模块配置,type 通常为 "feature":
{
"module": {
"name": "commonlib_test",
"type": "feature",
"deviceTypes": [
"default",
"tablet"
],
"deliveryWithInstall": true,
"installationFree": false
}
}
在 MoneyTrack 中,几乎所有功能模块(home、mine、statistics、assets 等)和公共组件模块(bill_card、asset_manage、membership 等)都包含了 ohosTest/module.json5 配置文件,这表明项目对仪器化测试的重视。
5. Mock 与 Stub 在鸿蒙测试中的应用
在鸿蒙应用测试中,Mock 和 Stub 是处理外部依赖的重要技术:
- Mock:模拟外部对象的行为,验证其是否按预期被调用。适用于需要验证交互场景的测试。
- Stub:提供预设的返回值,替代真实的外部依赖。适用于需要隔离被测单元的测试。
在 MoneyTrack 的测试场景中,常见的 Mock 应用包括:
-
Mock 网络请求:在测试数据层代码时,使用 Mock 替代真实的 HTTP 请求,避免测试对外部网络的依赖,同时可以模拟各种异常场景(超时、网络错误、错误状态码)。
-
Mock 数据库操作:在测试业务逻辑时,使用 Mock 替代真实的数据库读写操作,确保测试的隔离性和可重复性。
-
Stub 系统能力:对于依赖系统 API(如
@ohos.data.preferences、@ohos.ability.wantConstant)的代码,使用 Stub 提供预设的返回值,使测试不依赖真机环境。
Mock 测试的伪代码示例:
// Mock 网络请求的典型模式
import { describe, it, expect } from '@ohos/hypium';
describe('dataServiceTest', () => {
it('should handle network error gracefully', 0, () => {
// 使用 Stub 替代真实网络请求,返回模拟的错误响应
const mockResponse = { code: 500, message: 'Internal Server Error' };
// stubNetworkRequest(mockResponse);
// 执行被测方法
// const result = dataService.fetchData();
// 验证错误处理逻辑
// expect(result).assertEqual(defaultFallbackValue);
});
});
6. 测试覆盖率与持续集成建议
测试覆盖率
为了确保测试的有效性,团队应关注以下覆盖率指标:
- 行覆盖率(Line Coverage):至少达到 70% 以上,确保核心逻辑路径被执行到。
- 分支覆盖率(Branch Coverage):关注条件分支的覆盖情况,尤其是
if/else和switch/case语句。 - 方法覆盖率(Method Coverage):至少覆盖 80% 的公开方法。
建议在项目持续集成(CI)流程中集成覆盖率工具,对每次提交的覆盖率变化进行监控,并设置覆盖率门禁——当新增代码的覆盖率低于阈值时,构建失败。
持续集成流程
将测试集成到 CI 流水线中的推荐做法:
-
提交触发本地测试:每次代码提交时,在 CI 环境中运行所有本地单元测试。本地测试执行速度快,可以在几分钟内完成,适合作为提交门禁。
-
定时运行仪器化测试:仪器化测试由于需要真机环境,可以在夜间或定时任务中运行,生成详细的测试报告。
-
增量覆盖率检查:对比当前分支与目标分支的测试覆盖率变化,确保新代码有对应的测试用例覆盖。
-
失败快速反馈:当测试失败时,通过即时通讯工具通知相关负责人,缩短问题发现和修复的周期。
最佳实践与注意事项
-
保持测试的独立性:每个测试用例应该是独立的、可重复执行的,不依赖其他测试用例的执行顺序或状态。利用
beforeEach和afterEach在每个测试前后重置环境,避免测试之间的状态污染。 -
统一测试入口注册方式:在每个模块的
src/test/List.test.ets和src/ohosTest/ets/test/List.test.ets中注册所有测试套件,保持入口文件的简洁性,只在List.test.ets中做导入和注册,具体的测试逻辑写在独立的*.test.ets文件中。 -
测试用例的命名规范:使用清晰的三段式命名——“被测方法_场景_预期结果”,例如
fetchData_networkError_returnsDefaultValue,便于快速定位失败的测试用例。 -
合理划分测试粒度:避免编写过于粗粒度(一次测试覆盖太多逻辑)或过于细粒度(一个函数拆成十几个测试)的测试用例。推荐一个核心函数对应 3-5 个测试用例,覆盖正常路径、边界条件和异常场景。
-
善用 hypium 的生命周期钩子:
beforeAll适合全局初始化(如创建数据库连接池),beforeEach适合用例级准备(如插入测试数据),afterEach适合用例级清理(如删除测试数据),afterAll适合全局清理(如关闭连接)。合理利用这些钩子可以显著减少重复代码。
总结
MoneyTrack 的测试体系通过 hypium 测试框架建立了一套规范化的分层测试架构。本地单元测试(src/test/)覆盖纯逻辑和工具类函数,提供快速反馈;仪器化测试(src/ohosTest/``)覆盖 Ability 生命周期和 UI 交互,保障核心流程的正确性。每个模块的双测试目录结构、统一的 List.test.ets注册机制和完善的module.json5` 配置,共同构成了一个可维护、可扩展的测试基础设施。结合 Mock/Stub 技术处理外部依赖、覆盖率指标监控测试质量、以及持续集成流程自动化测试执行,MoneyTrack 的测试体系为应用质量提供了坚实的保障。在后续开发中,随着测试用例的不断丰富,这一体系将持续支撑项目的稳健演进。
参考文档
- Test Kit 测试框架指南
- hypium 测试框架 API 参考
- 本地单元测试与仪器化测试最佳实践
更多推荐

所有评论(0)