HarmonyOS 测试实践:单元测试、依赖隔离与关键路径 UI 测试
一、为什么鸿蒙项目也要认真写测试
不少人觉得应用层代码“跑起来就行”,测试是后端的事。但鸿蒙应用的业务逻辑、状态流转、数据转换同样复杂,尤其是状态管理与数据层抽离之后,这些代码完全可以脱离 UI 测试。测试带来的收益很直接:改动能被快速验证,回归问题在提交前暴露,重构时有安全网。
二、单元测试:测逻辑,不测界面
单元测试针对纯逻辑:数据解析、格式化、校验、状态流转。前提是这些逻辑不依赖 UI 上下文与真实网络。做法是先抽离,再测试。
// 被测试的逻辑:纯函数,无 UI 依赖
export function formatDuration(seconds: number): string {
if (seconds < 60) return `${seconds}秒`;
const m = Math.floor(seconds / 60);
const s = seconds % 60;
return s === 0 ? `${m}分钟` : `${m}分${s}秒`;
}
import { describe, it, expect } from '@ohos/hypium';
describe('formatDuration', () => {
it('under_one_minute', 0, () => {
expect(formatDuration(45)).assertEqual('45秒');
});
it('exact_minute', 0, () => {
expect(formatDuration(120)).assertEqual('2分钟');
});
it('minute_with_second', 0, () => {
expect(formatDuration(125)).assertEqual('2分5秒');
});
});
原则:一个用例只验证一个行为,边界值必须覆盖(0、1、临界值、异常输入)。用例名称写清楚“在什么条件下期望什么结果”。
三、依赖隔离:网络与存储要能被替换
如果业务逻辑直接 new 一个 http 对象或直接读数据库,测试就无法脱离真实环境。做法是面向接口编程:
export interface TaskRepository {
list(): Promise<Task[]>;
}
export class TaskService {
constructor(private repo: TaskRepository) {}
async pendingCount(): Promise<number> {
const tasks = await this.repo.list();
return tasks.filter(t => !t.done).length;
}
}
测试时传入一个内存实现即可,不需要真机、不需要网络。这也是数据层抽离带来的隐藏收益。
四、UI 测试:只覆盖关键路径
UI 测试成本高、稳定性差,不建议追求覆盖率,只覆盖关键路径:登录、核心下单/提交、关键状态切换。使用 UI 测试框架通过控件 ID 定位,避免依赖文案(文案会变)。
it('submit_button_disabled_when_empty', 0, async () => {
const driver = await Driver.create();
await driver.delayMs(1000);
const btn = await driver.findComponent(ON.id('submit_btn'));
expect(await btn.isEnabled()).assertFalse();
});
给关键控件补 .id() 是低成本高收益的动作,既方便自动化测试,也方便埋点定位。
五、持续集成中的测试
把单元测试挂进流水线,每次提交自动跑;UI 测试因为依赖模拟器,放在每日构建或发布前跑。失败的用例要能定位到具体断言,而不是只输出一个笼统的“测试失败”。
六、什么不值得测
避免为了覆盖率写“假测试”:只断言对象不为空、只调用不验证结果、把实现细节抄进断言。这类测试不提供保护,还会在重构时成批失败。测试应该描述行为契约,而不是复述代码实现。
七、小结
单元测试负责逻辑正确性,依赖注入让测试脱离环境,UI 测试只守关键路径,CI 保证每次改动都被验证。测试不是额外负担,而是让状态管理和模块拆分真正可维护的那道保险。
- 点赞
- 收藏
- 关注作者
评论(0)