引言
测试是软件工程中不可或缺的环节。在 HarmonyOS 应用开发中,良好的测试策略能够有效提升代码质量、减少线上故障、加速迭代节奏。本文将系统性地介绍 HarmonyOS 应用的单元测试与 UI 自动化测试实战,涵盖测试框架选型、用例编写、Mock 技巧、UI 交互验证以及持续集成接入,帮助开发者建立完整的测试体系。
一、HarmonyOS 测试体系概览
1.1 测试分层模型
HarmonyOS 应用测试遵循经典的测试金字塔模型:
- 单元测试(Unit Test):测试单个函数、类或模块的逻辑正确性,执行速度快,占比最高
- 集成测试(Integration Test):测试模块间协作、数据流转、API 调用等场景
- UI 自动化测试(UI Test):模拟用户操作,验证界面交互、页面跳转、状态更新等端到端流程
1.2 HarmonyOS 测试框架
HarmonyOS 官方提供了完整的测试框架支持:
- ArkTS 单元测试框架:基于 Jest 风格的测试 API,支持同步与异步断言
- UI 测试框架(UiTest):提供组件查找、事件注入、属性断言等能力
- 测试运行器(Test Runner):集成在 DevEco Studio 中,支持一键运行与调试
二、单元测试实战
2.1 测试环境搭建
在 DevEco Studio 创建的工程中,测试代码默认位于 entry/src/ohosTest/ets/test 目录。测试文件命名遵循 *.test.ets 规范。
典型的测试用例结构:
typescript
import { describe, it, expect } from '@ohos/hypium'
export default function abilityTest() {
describe('UserService', () => {
it('should return user info when login success', async () => {
const service = new UserService()
const result = await service.login('test', 'password')
expect(result.success).assertTrue()
expect(result.userName).assertEqual('test')
})
})
}
2.2 业务逻辑单元测试
针对业务逻辑层(如数据处理、状态计算、工具函数),编写纯函数风格的单元测试:
typescript
// utils/StringUtil.ets
export class StringUtil {
static truncate(text: string, maxLength: number): string {
if (text.length <= maxLength) return text
return text.substring(0, maxLength) + '...'
}
static isEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
}
}
// test/StringUtil.test.ets
import { describe, it, expect } from '@ohos/hypium'
import { StringUtil } from '../../../ets/utils/StringUtil'
export default function stringUtilTest() {
describe('StringUtil', () => {
it('should truncate long text', () => {
const result = StringUtil.truncate('Hello World', 5)
expect(result).assertEqual('Hello...')
})
it('should not truncate short text', () => {
const result = StringUtil.truncate('Hi', 5)
expect(result).assertEqual('Hi')
})
it('should validate email format', () => {
expect(StringUtil.isEmail('user@example.com')).assertTrue()
expect(StringUtil.isEmail('invalid-email')).assertFalse()
})
})
}
2.3 异步逻辑测试
对于网络请求、数据库操作等异步场景,使用 async/await 配合 done 回调:
typescript
import { describe, it, expect } from '@ohos/hypium'
import { http } from '@kit.NetworkKit'
export default function httpTest() {
describe('NetworkService', () => {
it('should fetch user list from API', async (done) => {
const request = http.createHttp()
try {
const response = await request.request('https://api.example.com/users')
const data = JSON.parse(response.result as string)
expect(data.code).assertEqual(200)
expect(data.users.length).assertLarger(0)
done()
} catch (err) {
expect().assertFail()
done()
}
})
})
}
2.4 Mock 与依赖注入
当测试代码依赖外部服务(如网络、数据库)时,通过 Mock 技术隔离外部依赖:
typescript
// service/UserRepository.ets
export interface IUserRepository {
getUserById(id: string): Promise<User>
}
export class UserRepository implements IUserRepository {
async getUserById(id: string): Promise<User> {
// 真实实现调用网络或数据库
return await http.get(`/users/${id}`)
}
}
// test/UserViewModel.test.ets
class MockUserRepository implements IUserRepository {
async getUserById(id: string): Promise<User> {
return { id, name: 'Mock User', email: 'mock@test.com' }
}
}
export default function viewModelTest() {
describe('UserViewModel', () => {
it('should load user info', async () => {
const mockRepo = new MockUserRepository()
const viewModel = new UserViewModel(mockRepo)
await viewModel.loadUser('123')
expect(viewModel.userName).assertEqual('Mock User')
})
})
}
2.5 状态管理测试
对于使用 @State、@Observed 的状态驱动逻辑,可以测试状态变更是否触发预期行为:
typescript
@Observed
class CounterState {
count: number = 0
increment() {
this.count++
}
decrement() {
this.count--
}
}
export default function stateTest() {
describe('CounterState', () => {
it('should increment count', () => {
const state = new CounterState()
state.increment()
expect(state.count).assertEqual(1)
})
it('should decrement count', () => {
const state = new CounterState()
state.count = 5
state.decrement()
expect(state.count).assertEqual(4)
})
})
}
三、UI 自动化测试实战
3.1 UI 测试框架基础
HarmonyOS 的 UI 测试框架(UiTest)提供了组件查找、事件模拟、属性断言等能力。核心 API:
Driver.create():创建测试驱动器driver.findComponent(on):查找组件component.click():模拟点击component.inputText(text):输入文本component.getText():获取文本内容
3.2 组件查找与断言
通过 id、text、type 等属性定位组件并验证状态:
typescript
import { describe, it, expect } from '@ohos/hypium'
import { Driver, ON } from '@ohos.UiTest'
export default function loginPageTest() {
describe('LoginPage', () => {
it('should display login button', async () => {
const driver = Driver.create()
const loginBtn = await driver.findComponent(ON.text('登录'))
expect(await loginBtn.isEnabled()).assertTrue()
})
it('should show error when submit empty form', async () => {
const driver = Driver.create()
const submitBtn = await driver.findComponent(ON.id('submitButton'))
await submitBtn.click()
const errorMsg = await driver.findComponent(ON.text('请输入用户名'))
expect(await errorMsg.isVisible()).assertTrue()
})
})
}
3.3 输入与交互测试
模拟用户输入、点击、滑动等操作:
typescript
export default function formTest() {
describe('RegisterForm', () => {
it('should submit form with valid data', async () => {
const driver = Driver.create()
// 输入用户名
const usernameInput = await driver.findComponent(ON.id('usernameInput'))
await usernameInput.inputText('testuser')
// 输入密码
const passwordInput = await driver.findComponent(ON.id('passwordInput'))
await passwordInput.inputText('password123')
// 点击提交
const submitBtn = await driver.findComponent(ON.id('submitButton'))
await submitBtn.click()
// 验证成功提示
await driver.delayMs(1000) // 等待网络请求
const successMsg = await driver.findComponent(ON.text('注册成功'))
expect(await successMsg.isVisible()).assertTrue()
})
})
}
3.4 列表与滚动测试
对于 List、Grid 等可滚动组件,测试滚动行为与数据加载:
typescript
export default function listTest() {
describe('UserList', () => {
it('should load more data when scroll to bottom', async () => {
const driver = Driver.create()
const list = await driver.findComponent(ON.type('List'))
// 获取初始条目数
const initialItems = await driver.findComponents(ON.type('ListItem'))
const initialCount = initialItems.length
// 滚动到底部
await list.scrollToBottom()
await driver.delayMs(1000)
// 验证条目增加
const updatedItems = await driver.findComponents(ON.type('ListItem'))
expect(updatedItems.length).assertLarger(initialCount)
})
})
}
3.5 页面跳转测试
验证页面导航与参数传递:
typescript
export default function navigationTest() {
describe('PageNavigation', () => {
it('should navigate to detail page with correct params', async () => {
const driver = Driver.create()
// 点击列表第一项
const firstItem = await driver.findComponent(ON.id('listItem_0'))
await firstItem.click()
// 验证跳转到详情页
await driver.delayMs(500)
const detailTitle = await driver.findComponent(ON.id('detailTitle'))
const titleText = await detailTitle.getText()
expect(titleText).assertContain('详情')
})
})
}
3.6 弹窗与确认框测试
测试 Dialog、AlertDialog 等弹窗组件:
typescript
export default function dialogTest() {
describe('DeleteConfirmDialog', () => {
it('should close dialog when click cancel', async () => {
const driver = Driver.create()
// 触发删除操作
const deleteBtn = await driver.findComponent(ON.id('deleteButton'))
await deleteBtn.click()
// 验证弹窗显示
const dialog = await driver.findComponent(ON.text('确认删除'))
expect(await dialog.isVisible()).assertTrue()
// 点击取消
const cancelBtn = await driver.findComponent(ON.text('取消'))
await cancelBtn.click()
// 验证弹窗消失
await driver.delayMs(300)
expect(await dialog.isVisible()).assertFalse()
})
})
}
四、测试最佳实践
4.1 测试用例设计原则
- 单一职责:每个测试用例只验证一个行为或场景
- 可重复性:测试结果不依赖执行顺序或外部状态
- 可读性:使用清晰的命名和注释,让测试意图一目了然
- 独立性:测试用例之间互不依赖,可以并行执行
4.2 测试覆盖率目标
- 核心业务逻辑:覆盖率 ≥ 80%
- 工具类与公共模块:覆盖率 ≥ 90%
- UI 关键路径:登录、支付、订单等核心流程必须有 UI 自动化测试
4.3 持续集成接入
将测试集成到 CI/CD 流程中,在代码提交或合并时自动运行:
bash
# 命令行运行测试
hdc shell aa test -b com.example.app -m entry_test -s unittest OpenHarmonyTestRunner
# 在 CI 脚本中集成
npm run test
npm run test:ui
4.4 测试数据管理
- 使用测试专用数据源:避免污染生产数据
- 测试后清理数据 :在
afterEach或afterAll中重置状态 - 使用数据驱动测试:通过参数化测试覆盖多种输入场景
typescript
const testCases = [
{ input: 'user@example.com', expected: true },
{ input: 'invalid-email', expected: false },
{ input: '', expected: false }
]
testCases.forEach(({ input, expected }) => {
it(`should validate email: ${input}`, () => {
expect(StringUtil.isEmail(input)).assertEqual(expected)
})
})
五、常见问题与解决方案
5.1 异步测试超时
问题:异步测试用例超时失败。
解决方案:
- 使用
done()回调明确标记测试完成 - 增加超时时间配置
- 检查异步逻辑是否有未捕获的异常
5.2 UI 测试不稳定
问题:UI 测试偶现失败,无法稳定复现。
解决方案:
- 添加合理的
delayMs等待动画或网络完成 - 使用
waitForComponent等待组件出现 - 避免依赖绝对坐标,使用
id或text定位
5.3 Mock 数据与真实环境差异
问题:Mock 测试通过,但真实环境出现问题。
解决方案:
- Mock 数据尽量贴近真实场景
- 补充集成测试,使用真实接口验证
- 定期更新 Mock 数据结构
六、总结
本文系统介绍了 HarmonyOS 应用的单元测试与 UI 自动化测试实战,涵盖测试框架、用例编写、Mock 技巧、UI 交互验证等核心内容。通过建立完整的测试体系,开发者可以显著提升代码质量、减少线上故障、加速迭代节奏。
测试不是开发的负担,而是质量的保障。在实际项目中,建议从核心业务逻辑的单元测试入手,逐步补充 UI 自动化测试覆盖关键路径,并将测试集成到 CI/CD 流程中,让测试成为开发流程的有机组成部分。
下一步,我们将探讨 HarmonyOS 应用的安全加固与数据保护实战,敬请期待。