OpenHarmony 单元测试 HUnit 自动化测试(API Version23+)
摘要
HUnit 是 OpenHarmony 官方提供的单元测试框架,用于对工具类、业务逻辑、数据模型进行自动化代码测试,校验函数入参、返回值、异常分支是否符合预期,是课程设计、毕设加分项与企业项目质量管控必备模块。API Version23 重构测试用例生命周期、异步测试断言、沙盒上下文隔离、测试日志输出逻辑,修复低版本异步测试断言失效、工具类上下文缺失、多用例污染数据、测试执行崩溃等兼容缺陷。旧项目升级 API23 后常出现:异步工具函数测试无结果、RDB/Preferences 测试初始化失败、多个测试用例数据互相干扰、测试日志空白等问题。本文基于 DevEco Studio API23+,完整讲解测试工程创建、同步 / 异步用例编写、工具类测试、数据库模拟测试、测试报告查看全流程,封装通用测试基类,配套文件工具、通知工具、数据库工具实战测试用例,输出测试隔离、上下文初始化、异步断言规范,汇总版本升级故障解决方案,为鸿蒙自动化测试开发提供标准化模板。
关键词
OpenHarmony;ArkTS;API Version23;HUnit;单元测试;自动化测试;异步测试;工具类测试
一、引言
1.1 单元测试开发背景
项目中大量工具函数(文件读写、网络请求、数据库 CRUD、权限校验)存在大量分支逻辑,手动逐个场景验证效率极低,且修改代码后无法快速验证是否引入 BUG。HUnit 单元测试可批量自动执行校验,通过断言判断返回值、状态、异常是否符合设计预期。 测试分层:
- 单元测试:仅测试独立工具类 / 纯函数(无 UI、不依赖页面),本文核心内容;
- UI 测试:测试页面点击、弹窗、列表等交互(依赖 XTest)。
OpenHarmony API Version23 HUnit 核心升级变更:
- 标准化测试套件生命周期:beforeAll → beforeEach → 测试用例 → afterEach → afterAll;
- 完善异步测试 async/await 断言支持,修复 Promise 场景捕获失效;
- 提供独立测试沙盒上下文,避免测试污染业务真实数据库 / 存储;
- 新增精准断言 API:相等、布尔、空值、数组、异常捕获;
- 隔离测试进程与应用主进程,测试操作不会覆盖用户真实数据;
- 优化控制台日志分级输出,区分成功 / 失败 / 异常用例日志。
大量旧项目升级后数据库工具测试报错、异步接口断言无响应,根源是未在生命周期钩子初始化独立测试上下文,未使用异步专用测试模板,因此掌握 HUnit 单元测试是规范工程必备能力。
1.2 开发环境与测试场景
开发工具:DevEco Studio 5.0+ 适配系统:OpenHarmony API23、HarmonyOS NEXT 依赖模块:@ohos.test.HUnit 工程结构:独立 hittest 测试模块,关联 entry 业务模块 测试场景:文件工具读写校验、Preferences 存取、RDB 增删改查、工具类入参异常拦截、异步网络请求模拟校验
二、API23+ HUnit 核心生命周期与断言 API
2.1 测试套件完整生命周期执行顺序
plaintext
beforeAll() // 整个测试套件执行前运行1次(全局初始化)
├─ beforeEach() // 单个用例执行前(重置数据、初始化上下文)
│ ├─ test() // 单条测试用例
├─ afterEach() // 单个用例执行后(清理测试数据)
afterAll() // 全部用例执行完毕(销毁资源)
2.2 核心断言 Assert 方法
ets
import Assert from '@ohos.test.HUnit.Assert'
Assert.equal(actual, expect, "错误提示文案") // 判断相等
Assert.true(condition, "条件应为true")
Assert.false(condition)
Assert.notNull(value)
Assert.isNull(value)
Assert.throws(func, "预期抛出异常")
2.3 同步 / 异步测试模板区分
- 同步用例:
test("名称", ()=>{}) - 异步用例:
test("名称", async ()=>{ await 逻辑; 断言 })
2.4 API23 强制约束
- 数据库、Preferences 工具必须在 beforeEach 初始化独立测试上下文;
- 每个用例 afterEach 清空测试沙盒数据,防止用例之间数据污染;
- 异步操作必须加 await,否则断言会提前执行导致用例误判成功;
- 禁止测试用例直接操作 entry 真实业务沙盒,仅使用测试隔离环境;
- 耗时 IO 操作不要放入 beforeAll,统一放到 beforeEach 单用例重置。
三、基础最简单元测试示例
3.1 同步纯函数测试(字符串工具)
ets
import { describe, test, beforeAll, beforeEach, afterEach, afterAll } from '@ohos.test.HUnit'
import Assert from '@ohos.test.HUnit.Assert'
// 待测试纯工具函数
function add(a:number,b:number):number {
return a + b
}
describe("数学计算工具测试套件", ()=>{
test("两数相加正常返回结果", ()=>{
const res = add(2,3)
Assert.equal(res,5,"2+3预期等于5")
})
})
3.2 异步文件读写测试基础模板
ets
describe("文件工具异步测试", ()=>{
let ctx: common.UIAbilityContext
beforeEach(async ()=>{
// 获取测试独立上下文
ctx = getContext(this)
FileUtil.setContext(ctx)
})
test("写入并读取文本文件", async ()=>{
const path = ctx.filesDir + "/test_log.txt"
const text = "单元测试文本"
FileUtil.writeText(path, text)
const readStr = FileUtil.readText(path)
Assert.equal(readStr, text,"写入读取内容不一致")
})
})
四、三大完整实战测试用例(适配前文全部工具类)
4.1 实战一:通用测试基类封装(hittest/src/main/ets/BaseTest.ets)
ets
import { beforeAll, beforeEach, afterEach, afterAll } from '@ohos.test.HUnit'
import common from '@ohos.app.ability.common'
import FileUtil from '../../../entry/src/main/ets/utils/file_util'
import PrefUtil from '../../../entry/src/main/ets/utils/preference_utils'
import RdbUtil from '../../../entry/src/main/ets/utils/rdb_util'
export default class BaseTest {
protected ctx: common.UIAbilityContext | null = null
// 套件全局初始化
initSuite() {
beforeAll(()=>{
console.info("===== 测试套件全局初始化 =====")
})
}
// 每个用例前置:绑定上下文、初始化全部存储工具
initCase() {
beforeEach(async ()=>{
this.ctx = getContext(this)
FileUtil.setContext(this.ctx)
PrefUtil.setContext(this.ctx)
await PrefUtil.init(this.ctx)
await RdbUtil.init(this.ctx)
})
}
// 每个用例后置:清空测试数据、释放数据库
clearCase() {
afterEach(async ()=>{
if(this.ctx) {
// 清空测试Preferences
await PrefUtil.clearAll()
// 关闭数据库释放锁
await RdbUtil.closeDB()
// 清空测试文件目录
const dir = this.ctx.filesDir
FileUtil.clearDir(dir)
}
})
}
// 套件全部执行完毕销毁
destroySuite() {
afterAll(()=>{
console.info("===== 全部测试用例执行完毕 =====")
})
}
}
4.2 实战二:Preferences 持久化工具完整测试套件
文件路径:hittest/src/main/ets/PrefTest.ets
ets
import { describe, test } from '@ohos.test.HUnit'
import Assert from '@ohos.test.HUnit.Assert'
import PrefUtil from '../../../entry/src/main/ets/utils/preference_utils'
import BaseTest from './BaseTest'
// 初始化测试生命周期
const base = new BaseTest()
base.initSuite()
base.initCase()
base.clearCase()
base.destroySuite()
describe("Preferences本地存储工具测试", ()=>{
test("写入、读取布尔值校验", async ()=>{
await PrefUtil.putBool("test_bool", true)
const val = await PrefUtil.getBool("test_bool")
Assert.true(val,"布尔值读取应为true")
})
test("写入、读取字符串校验", async ()=>{
const testStr = "unit_test_string"
await PrefUtil.putString("test_str", testStr)
const res = await PrefUtil.getString("test_str")
Assert.equal(res, testStr,"字符串读写不匹配")
})
test("删除key后读取返回默认值", async ()=>{
await PrefUtil.putNumber("test_num", 999)
await PrefUtil.deleteKey("test_num")
const num = await PrefUtil.getNumber("test_num", 0)
Assert.equal(num, 0,"删除key后应返回默认0")
})
})
4.3 实战三:RDB 数据库笔记 CRUD 测试套件
文件路径:hittest/src/main/ets/RdbNoteTest.ets
ets
import { describe, test } from '@ohos.test.HUnit'
import Assert from '@ohos.test.HUnit.Assert'
import RdbUtil from '../../../entry/src/main/ets/utils/rdb_util'
import BaseTest from './BaseTest'
const base = new BaseTest()
base.initSuite()
base.initCase()
base.clearCase()
base.destroySuite()
describe("RDB笔记数据库CRUD单元测试", ()=>{
test("新增笔记,查询条数为1", async ()=>{
// 新增一条笔记
await RdbUtil.addNote("单元测试标题","单元测试内容")
const list = await RdbUtil.queryNoteList(0,10)
Assert.equal(list.length, 1,"新增后列表应仅有1条数据")
})
test("删除笔记后列表为空", async ()=>{
const id = await RdbUtil.addNote("待删除笔记","内容")
await RdbUtil.deleteNote(id)
const list = await RdbUtil.queryNoteList(0,10)
Assert.equal(list.length, 0,"删除后数据应清空")
})
test("修改笔记标题校验", async ()=>{
const id = await RdbUtil.addNote("旧标题","内容")
await RdbUtil.updateNote(id, "新标题","内容")
const list = await RdbUtil.queryNoteList(0,10)
Assert.equal(list[0].title, "新标题","标题修改未生效")
})
})
五、API23+ 单元测试工程配置与执行规范
5.1 hittest 模块 module.json5 基础配置
json
{
"module": {
"name": "hittest",
"type": "har",
"description": "单元测试模块",
"deviceTypes": ["phone"],
"deliveryWithInstall": false,
"dependencies": [
{
"name": "entry",
"version": "1.0.0",
"scope": "shared"
}
]
}
}
5.2 执行测试操作步骤
- DevEco 左侧测试窗口打开 HUnit;
- 选择测试设备 / 模拟器;
- 选择单个测试套件 / 全部用例点击运行;
- 底部控制台查看每条用例成功 / 失败日志;
- 失败用例日志会打印预期值、实际值、错误代码行。
5.3 测试分层规范
- 纯逻辑工具(计算、字符串处理):同步快速测试,无需上下文;
- 存储类工具(fs、Preferences、RDB):异步测试,必须重置沙盒;
- 网络工具:可模拟固定返回数据,避免真实接口依赖;
- 页面 UI、XComponent、Web 组件不适合单元测试,改用 XTest UI 自动化。
5.4 数据隔离规范
- 每个用例 afterEach 清空数据库、文件、存储,用例互不干扰;
- 测试独立沙盒与业务 APP 存储隔离,不会删除用户真实笔记、配置;
- 测试使用专用 test_前缀 key / 表名,区分业务数据。
六、API23 升级高频兼容问题与解决方案
问题 1:RDB/Preferences 测试提示上下文为空 解决:在 beforeEach 生命周期钩子中获取测试专用上下文并传入工具初始化。
问题 2:异步读写文件断言直接判定成功,实际逻辑未执行完 解决:异步测试函数头部添加 async,IO 操作前加 await 等待执行完毕再断言。
问题 3:第一条用例正常,第二条用例读取到上一条残留数据 解决:afterEach 清空数据库、删除测试文件、清空 Preferences。
问题 4:运行测试后打开业务 APP,本地配置全部清空 解决:使用独立 hittest 测试沙盒,不要共用 entry 业务上下文。
问题 5:升级 API23 后旧版 runTest 执行函数编译报错 解决:全部替换为标准 describe+test 生命周期写法,废弃旧版单文件测试入口。
问题 6:测试控制台无日志输出,无法定位失败原因 解决:使用 Assert 第三个参数填写清晰错误描述,开启 DevEco 测试日志打印开关。
七、总结
HUnit 单元测试是 OpenHarmony 标准化自动化验证工具,API Version23 完善测试生命周期、异步断言、独立隔离沙盒三大核心能力,解决旧版异步校验失效、测试数据互相污染、上下文缺失崩溃等问题。项目封装统一测试基类,自动完成初始化、数据清理、资源销毁,配套 Preferences、RDB 数据库两大高频工具完整测试套件,覆盖增删改查、读写、删除全分支校验。 单元测试可大幅降低后期改代码产生 BUG 的风险,课程设计、毕设增加测试模块能显著提升项目完整度与答辩分数,全部代码兼容 API23 与 HarmonyOS NEXT,可直接集成至现有四层架构工程。
更多推荐



所有评论(0)