
鸿蒙5 ArkTS单元测试框架使用指南
ArkTS作为鸿蒙5的主要开发语言,提供了完善的单元测试框架支持。本文将详细介绍ArkTS测试框架的使用方法,包括测试环境搭建、基础测试编写、高级测试技巧以及持续集成实践,并通过丰富的代码示例展示如何为鸿蒙应用编写高质量的单元测试。
测试环境配置
- 项目结构配置
在鸿蒙项目中,测试代码应放在src/test目录下,典型结构如下:
src/
main/
ets/
// 主代码
test/
ets/
Example.test.ets // 测试文件
pages/
IndexPage.test.ets
ohosTest/
testRunner.ets // 测试运行配置
2. 配置文件设置
在oh-package.json5中添加测试依赖:
{
“devDependencies”: {
“@ohos/hypium”: “^1.0.0”,
“@ohos/arkunit”: “^1.0.0”
}
}
基础测试编写
- 函数测试示例
测试一个简单的工具函数:
// src/main/ets/utils/MathUtil.ets
export class MathUtil {
static add(a: number, b: number): number {
return a + b;
}
static factorial(n: number): number {
if (n < 0) throw new Error(“负数没有阶乘”);
return n <= 1 ? 1 : n * this.factorial(n - 1);
}
}
对应的测试文件:
// src/test/ets/MathUtil.test.ets
import { describe, it, expect } from ‘@ohos/arkunit’;
import { MathUtil } from ‘…/…/main/ets/utils/MathUtil’;
describe(‘MathUtil测试’, () => {
it(‘add方法应该正确计算两数之和’, () => {
expect(MathUtil.add(2, 3)).assertEqual(5);
expect(MathUtil.add(-1, 1)).assertEqual(0);
expect(MathUtil.add(0.1, 0.2)).toBeCloseTo(0.3, 5);
});
it(‘factorial方法应该正确计算阶乘’, () => {
expect(MathUtil.factorial(0)).assertEqual(1);
expect(MathUtil.factorial(5)).assertEqual(120);
});
it(‘factorial方法应该对负数抛出异常’, () => {
expect(() => MathUtil.factorial(-1)).toThrowError(“负数没有阶乘”);
});
});
2. 组件测试示例
测试一个简单的ArkUI组件:
// src/main/ets/components/Greeting.ets
@Component
export struct Greeting {
@Prop name: string = ‘’
build() {
Text(Hello, ${this.name}!
)
.fontSize(20)
.fontColor(‘#333333’)
}
}
组件测试文件:
// src/test/ets/components/Greeting.test.ets
import { describe, it, expect, TestContext } from ‘@ohos/arkunit’;
import { Greeting } from ‘…/…/…/main/ets/components/Greeting’;
describe(‘Greeting组件测试’, () => {
it(‘应该正确渲染默认文本’, (ctx: TestContext) => {
const greeting = new Greeting();
greeting.build();
const text = ctx.findComponent(Text);
expect(text).toBeDefined();
expect(text.text).assertEqual('Hello, !');
});
it(‘应该正确渲染传入的name属性’, (ctx: TestContext) => {
const greeting = new Greeting();
greeting.name = ‘ArkTS’;
greeting.build();
const text = ctx.findComponent(Text);
expect(text.text).assertEqual('Hello, ArkTS!');
expect(text.fontSize).assertEqual(20);
expect(text.fontColor).assertEqual('#333333');
});
});
高级测试技巧
- 模拟(mock)测试
// src/main/ets/services/UserService.ets
export class UserService {
async getUser(id: string): Promise<{id: string, name: string}> {
const response = await http.get(/users/${id}
);
return response.data;
}
}
使用mock的测试:
// src/test/ets/services/UserService.test.ets
import { describe, it, expect, mock } from ‘@ohos/arkunit’;
import { UserService } from ‘…/…/…/main/ets/services/UserService’;
import http from ‘@ohos.net.http’;
describe(‘UserService测试’, () => {
it(‘应该正确处理用户数据’, async () => {
// 模拟http.get方法
const mockGet = mock(http, ‘get’).mockReturnValue({
data: {id: ‘123’, name: ‘测试用户’}
});
const service = new UserService();
const user = await service.getUser('123');
expect(user).assertEqual({id: '123', name: '测试用户'});
expect(mockGet).toHaveBeenCalledWith('/users/123');
// 清理mock
mockGet.restore();
});
it(‘应该处理网络错误’, async () => {
const mockGet = mock(http, ‘get’).mockRejectedValue(new Error(‘网络错误’));
const service = new UserService();
await expect(service.getUser('123')).rejects.toThrowError('网络错误');
mockGet.restore();
});
});
2. 异步测试
// src/main/ets/utils/TimerUtil.ets
export class TimerUtil {
static async delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
异步测试示例:
// src/test/ets/utils/TimerUtil.test.ets
import { describe, it, expect } from ‘@ohos/arkunit’;
import { TimerUtil } from ‘…/…/…/main/ets/utils/TimerUtil’;
describe(‘TimerUtil测试’, () => {
it(‘delay方法应该等待指定时间’, async () => {
const start = new Date().getTime();
await TimerUtil.delay(100);
const duration = new Date().getTime() - start;
expect(duration).toBeGreaterOrEqual(100);
expect(duration).toBeLessThan(150);
}, 200); // 设置超时时间为200ms
});
3. 组件交互测试
// src/main/ets/components/Counter.ets
@Component
export struct Counter {
@State count: number = 0
build() {
Column() {
Text(Count: ${this.count}
)
Button(‘Increment’)
.onClick(() => this.count++)
}
}
}
交互测试示例:
// src/test/ets/components/Counter.test.ets
import { describe, it, expect, TestContext } from ‘@ohos/arkunit’;
import { Counter } from ‘…/…/…/main/ets/components/Counter’;
describe(‘Counter组件测试’, () => {
it(‘应该正确初始化计数器’, (ctx: TestContext) => {
const counter = new Counter();
counter.build();
const text = ctx.findComponent(Text);
expect(text.text).assertEqual('Count: 0');
});
it(‘点击按钮应该增加计数’, (ctx: TestContext) => {
const counter = new Counter();
counter.build();
const button = ctx.findComponent(Button);
button.simulateClick();
const text = ctx.findComponent(Text);
expect(text.text).assertEqual('Count: 1');
});
it(‘多次点击应该正确累加’, (ctx: TestContext) => {
const counter = new Counter();
counter.build();
const button = ctx.findComponent(Button);
button.simulateClick();
button.simulateClick();
button.simulateClick();
const text = ctx.findComponent(Text);
expect(text.text).assertEqual('Count: 3');
});
});
测试运行与报告
- 运行测试
在package.json中添加测试脚本:
{
“scripts”: {
“test”: “ark test”,
“test:watch”: “ark test --watch”,
“test:coverage”: “ark test --coverage”
}
}
运行测试:
npm test # 运行所有测试
npm run test:watch # 监视模式运行
npm run test:coverage # 带覆盖率运行
2. 测试覆盖率配置
在ohosTest/目录下创建testRunner.ets:
import { TestRunner, TestConfig } from ‘@ohos/arkunit’;
const config: TestConfig = {
testDir: ‘./src/test/ets’,
coverage: {
enabled: true,
reporters: [‘text’, ‘html’],
exclude: [‘/test/’, ‘**/*.test.ets’]
},
timeout: 5000
};
export default new TestRunner(config).run();
持续集成实践
- GitHub Actions配置
.github/workflows/test.yml示例:
name: ArkTS Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: '16'
- name: Install dependencies
run: npm install
- name: Run tests
run: npm test
- name: Upload coverage
uses: actions/upload-artifact@v3
with:
name: coverage-report
path: coverage/
- 自定义测试报告
// src/test/ets/customReporter.ets
import { TestReporter, TestResult } from ‘@ohos/arkunit’;
export class CustomReporter implements TestReporter {
onStart(): void {
console.log(‘测试开始运行…’);
}
onTestFinish(result: TestResult): void {
const status = result.passed ? ‘✓’ : ‘✗’;
console.log(${status} ${result.description}
);
}
onFinish(results: TestResult[]): void {
const passed = results.filter(r => r.passed).length;
console.log(\n测试完成: ${passed}/${results.length} 通过
);
}
}
// 在testRunner.ets中使用
const config: TestConfig = {
// …其他配置
reporters: [new CustomReporter()]
};
测试最佳实践
- 测试组织策略
// 好的测试组织示例
describe(‘UserService’, () => {
describe(‘getUser()’, () => {
it(‘应该返回用户数据当用户存在时’, () => { /…/ });
it(‘应该抛出错误当用户不存在时’, () => { /…/ });
});
describe(‘updateUser()’, () => {
it(‘应该成功更新用户信息’, () => { /…/ });
it(‘应该验证用户输入’, () => { /…/ });
});
});
2. 测试数据管理
// 使用工厂函数创建测试数据
function createTestUser(overrides?: Partial<User>): User {
return {
id: ‘123’,
name: ‘测试用户’,
email: ‘test@example.com’,
…overrides
};
}
describe(‘UserService’, () => {
it(‘应该处理用户更新’, () => {
const user = createTestUser({ name: ‘新名字’ });
// …测试逻辑
});
});
3. 组件测试技巧
describe(‘LoginForm组件’, () => {
it(‘应该验证输入字段’, (ctx: TestContext) => {
const form = new LoginForm();
form.build();
// 获取输入组件
const usernameInput = ctx.findComponent(TextInput);
const passwordInput = ctx.findComponent(TextInput, { id: 'password' });
// 模拟输入
usernameInput.simulateChange('testuser');
passwordInput.simulateChange('123456');
// 验证状态
expect(form.username).assertEqual('testuser');
expect(form.password).assertEqual('123456');
});
});
常见问题解决
- 测试异步代码超时
it(‘应该完成异步操作’, async () => {
const result = await someAsyncFunction();
expect(result).toBe(true);
}, 5000); // 设置更长的超时时间 - 模拟系统模块
import router from ‘@ohos.router’;
describe(‘导航测试’, () => {
it(‘应该导航到详情页’, () => {
const mockPush = mock(router, ‘push’);
// 触发导航的代码
navigateToDetail('123');
expect(mockPush).toHaveBeenCalledWith({
url: 'pages/DetailPage',
params: { id: '123' }
});
mockPush.restore();
});
});
3. 测试覆盖率提升技巧
// 原始代码
function calculate(a: number, b: number): number {
if (a > b) {
return a - b;
} else {
return a + b;
}
}
// 测试用例
describe(‘calculate函数’, () => {
it(‘当a>b时应该返回a-b’, () => {
expect(calculate(5, 3)).assertEqual(2);
});
it(‘当a<=b时应该返回a+b’, () => {
expect(calculate(3, 5)).assertEqual(8);
expect(calculate(4, 4)).assertEqual(8);
});
});
总结
鸿蒙5的ArkTS测试框架提供了全面的单元测试支持,本文涵盖了:
测试基础:环境配置、简单函数和组件测试
高级技巧:模拟测试、异步测试、组件交互测试
工程实践:测试运行、覆盖率报告、持续集成
最佳实践:测试组织、数据管理、常见问题解决
通过合理使用ArkTS测试框架,开发者可以:
提高代码质量和可靠性
减少回归缺陷
改进代码设计(可测试性)
加速开发迭代速度
建议开发团队:
将测试作为开发流程的必要环节
追求合理的测试覆盖率(通常70-90%)
重视测试的可读性和可维护性
将测试集成到CI/CD流程中
随着鸿蒙生态的发展,ArkTS测试框架也将持续演进,为开发者提供更强大的测试能力。希望本文能帮助你快速掌握鸿蒙5的单元测试技术,构建更可靠的鸿蒙应用。
