HarmonyOS 6.1 测试驱动实战:从单元测试到分布式场景自动化
系列质量保障篇·第37篇。AI篇后,有资深开发者提问:“Demo跑通了,AI也接上了,但怎么保证每次修改代码后,之前的功能不被破坏?尤其是分布式流转和AI推理,怎么自动化测试?” 这触及了工程化开发的核心——测试。很多个人开发者忽视测试,导致项目后期难以维护。今天我们将电商Demo纳入测试驱动开发(TDD)轨道,使用Hypium框架编写单元测试、UI测试、分布式场景测试,并集成DevEco Testing实现自动化。全程基于API23,含官方文档未涉及的“分布式Mock技巧”和“AI模型确定性测试”方案。
一、前言:为什么测试是“高级开发者的分水岭”?
在单人开发Demo阶段,手动点几下看看效果似乎够用。但在团队协作、商业交付场景下,手动测试的弊端暴露无遗:
-
回归测试噩梦:改了一个Bug,却引入了三个新Bug。每次发版前,测试人员需要把所有功能重测一遍,耗时耗力。
-
分布式场景难复现:“手机流转到手表后,数据偶尔丢失”——这种偶发问题很难通过手动操作抓到。
-
AI结果的不确定性:AI模型的输出有随机性,如何确保每次更新模型后,抠图效果没有变差?
解决方案:测试驱动开发(TDD)。核心思想是“先写测试,后写代码”。测试不仅是验证工具,更是活文档和设计工具。它能强制你写出低耦合、高内聚的代码。
HarmonyOS提供了强大的Hypium测试框架,支持从底层逻辑到上层UI,再到跨设备分布式场景的全链路测试。
二、核心概念辨析(测试金字塔)
|
测试类型 |
层级 |
速度 |
成本 |
覆盖范围 |
适用场景 |
|---|---|---|---|---|---|
|
单元测试 |
底层 |
极快 |
低 |
单个函数/类 |
验证业务逻辑、算法正确性 |
|
UI测试 |
中层 |
快 |
中 |
单个页面/组件 |
验证界面交互、组件渲染 |
|
分布式场景测试 |
顶层 |
慢 |
高 |
多设备协作流程 |
验证流转、同步、数据一致性 |
|
手动测试 |
顶层 |
极慢 |
极高 |
整体用户体验 |
探索性测试、主观体验评估 |
最佳实践:遵循“测试金字塔”模型,大量单元测试,适量UI测试,少量分布式场景测试。
三、代码实现:从“裸奔”到“测试全覆盖”
3.1 环境准备与目录结构
在DevEco Studio中,测试代码位于entry_test目录下。
entry/
├── src/
│ ├── main/ # 主代码
│ └── test/ # 测试代码
│ ├── js/
│ │ ├── unit/ # 单元测试
│ │ ├── ui/ # UI测试
│ │ └── dist/ # 分布式场景测试
│ └── resources/ # 测试资源
3.2 单元测试:验证核心逻辑
以之前的CartCalculator为例,编写单元测试。
创建entry_test/src/test/js/unit/CartCalculator.test.ets:
import { describe, it, expect, beforeAll, afterAll } from '@ohos/hypium'
import { CartCalculator } from '../../../../src/main/ets/model/CartCalculator'
export default function cartCalculatorTest() {
describe('CartCalculator单元测试', () => {
let calculator: CartCalculator
beforeAll(() => {
calculator = new CartCalculator()
})
afterAll(() => {
// 清理工作
})
// 测试正常计算总价
it('should_calculate_total_price_correctly', 0, () => {
const items = [
{ price: 100, count: 2 },
{ price: 50, count: 1 }
]
const total = calculator.calculateTotal(items)
expect(total).assertEqual(250)
})
// 测试空购物车
it('should_return_zero_for_empty_cart', 0, () => {
const total = calculator.calculateTotal([])
expect(total).assertEqual(0)
})
// 测试边界值:数量为0
it('should_handle_zero_quantity', 0, () => {
const items = [{ price: 100, count: 0 }]
const total = calculator.calculateTotal(items)
expect(total).assertEqual(0)
})
// 测试优惠逻辑:满300减50
it('should_apply_discount_when_total_over_300', 0, () => {
const items = [{ price: 200, count: 2 }] // 400元
const total = calculator.calculateTotal(items)
expect(total).assertEqual(350) // 400 - 50
})
})
}
3.3 UI测试:验证界面交互
UI测试模拟用户操作,验证界面是否正确响应。
创建entry_test/src/test/js/ui/ProductPage.test.ets:
import { describe, it, expect, UiDriver, By } from '@ohos/hypium'
import { Driver } from '@kit.TestKit'
export default function productPageTest() {
describe('商品页面UI测试', () => {
let driver: UiDriver
beforeAll(async () => {
driver = await UiDriver.create()
// 启动应用
await driver.startAbility({
bundleName: 'com.example.shop',
abilityName: 'EntryAbility'
})
// 等待页面加载
await driver.delayMs(2000)
})
afterAll(async () => {
await driver.stopAbility()
})
// 测试点击加购按钮
it('should_add_to_cart_when_click_add_button', 0, async () => {
// 1. 查找“加入购物车”按钮
const addButton = await driver.findComponent(By.text('加入购物车'))
expect(addButton).assertNotNull()
// 2. 点击按钮
await addButton.click()
await driver.delayMs(500)
// 3. 验证购物车数量是否更新
const cartBadge = await driver.findComponent(By.id('cart_badge'))
const text = await cartBadge.getText()
expect(text).assertEqual('1')
// 4. 验证Toast提示
const toast = await driver.findComponent(By.textContains('已加入购物车'))
expect(toast).assertNotNull()
})
// 测试滑动浏览商品
it('should_scroll_product_list_smoothly', 0, async () => {
const list = await driver.findComponent(By.id('product_list'))
expect(list).assertNotNull()
// 向上滑动
await list.swipeUp()
await driver.delayMs(300)
// 向下滑动
await list.swipeDown()
await driver.delayMs(300)
// 验证滑动后没有崩溃
const title = await driver.findComponent(By.text('HarmonyOS手机'))
expect(title).assertNotNull()
})
})
}
3.4 分布式场景测试:Mock与多设备协同
这是鸿蒙测试的难点。真实测试需要多台物理设备,成本高且不稳定。解决方案是Mock(模拟)分布式环境。
创建entry_test/src/test/js/dist/DistributedFlow.test.ets:
import { describe, it, expect, mock, afterEach } from '@ohos/hypium'
import { distributedDataObject } from '@kit.ArkData'
import { DistributedManager } from '../../../../src/main/ets/manager/DistributedManager'
// Mock分布式数据对象
mock(distributedDataObject, 'createDistributedDataObject', (context: Context, config: any) => {
console.log('Mock: createDistributedDataObject called')
return {
on: (event: string, callback: Function) => {
console.log(`Mock: Registered listener for ${event}`)
// 模拟数据同步事件
if (event === 'dataChanged') {
setTimeout(() => {
callback({ 'latestOrder': { orderId: 'mock_order_123' } })
}, 100)
}
},
off: (event: string) => {
console.log(`Mock: Unregistered listener for ${event}`)
},
sync: async (devices: string[]) => {
console.log(`Mock: Syncing data to devices: ${devices}`)
return true
}
}
})
export default function distributedFlowTest() {
describe('分布式流转场景测试', () => {
let manager: DistributedManager
beforeEach(() => {
manager = new DistributedManager()
})
afterEach(() => {
mock.reset()
})
// 测试手机到手表的流转
it('should_sync_order_data_from_phone_to_watch', 0, async () => {
// 1. 初始化分布式管理器
await manager.init(globalThis.context)
// 2. 模拟手机端创建订单
const order = { orderId: 'test_001', amount: 5999 }
await manager.createOrder(order)
// 3. 验证数据同步是否触发
// 由于我们使用了Mock,这里验证的是逻辑流程,而非真实设备
const syncedData = await manager.getSyncedData()
expect(syncedData).assertNotNull()
expect(syncedData.orderId).assertEqual('mock_order_123')
console.log('分布式流转逻辑验证成功')
})
// 测试网络断开后的重连机制
it('should_resync_when_network_recovers', 0, async () => {
await manager.init(globalThis.context)
// 模拟网络断开
manager.simulateNetworkDisconnect()
expect(manager.isSyncing()).assertFalse()
// 模拟网络恢复
await manager.simulateNetworkRecover()
expect(manager.isSyncing()).assertTrue()
// 验证数据是否重新同步
const syncedData = await manager.getSyncedData()
expect(syncedData).assertNotNull()
})
})
}
3.5 AI模型确定性测试
AI模型的输出有随机性,但测试需要确定性。解决方案是固定随机种子和结果比对。
创建entry_test/src/test/js/unit/AIModel.test.ets:
import { describe, it, expect, mock } from '@ohos/hypium'
import { ImageSegmentation } from '../../../../src/main/ets/ai/ImageSegmentation'
import { image } from '@kit.ImageKit'
export default function aiModelTest() {
describe('AI模型确定性测试', () => {
let segmentation: ImageSegmentation
beforeAll(async () => {
segmentation = new ImageSegmentation()
await segmentation.init(globalThis.context)
})
// 测试相同输入产生相同输出(确定性)
it('should_produce_deterministic_output_for_same_input', 0, async () => {
// 1. 加载固定的测试图片
const testImage = await loadFixedTestImage()
// 2. 第一次推理
const result1 = await segmentation.segmentProduct(testImage)
const hash1 = await calculateImageHash(result1)
// 3. 第二次推理(相同输入)
const result2 = await segmentation.segmentProduct(testImage)
const hash2 = await calculateImageHash(result2)
// 4. 验证两次结果一致
expect(hash1).assertEqual(hash2)
console.log('AI模型输出具有确定性')
})
// 测试模型降级逻辑
it('should_fallback_to_cpu_when_npu_unavailable', 0, async () => {
// Mock NPU不可用
mock(segmentation, 'initNPU', async () => {
throw new Error('NPU not available')
})
// 重新初始化,应触发降级
await segmentation.init(globalThis.context)
// 验证是否使用了CPU后端
const backend = segmentation.getCurrentBackend()
expect(backend).assertEqual('CPU')
console.log('AI模型成功降级为CPU推理')
})
})
}
// 辅助函数:加载固定测试图片
async function loadFixedTestImage(): Promise<image.PixelMap> {
// ... 加载一张固定的、已知的图片 ...
return {} as image.PixelMap
}
// 辅助函数:计算图片哈希值(用于比较相似度)
async function calculateImageHash(pixelMap: image.PixelMap): Promise<string> {
// ... 实现简单的图片哈希算法 ...
return ''
}
四、踩坑记录(官方文档没写的测试细节)
-
异步测试陷阱:Hypium的
it函数默认是同步的。如果你的测试用例包含异步操作(如await),必须在it的第二个参数中传入0,并将测试函数声明为async。否则测试会提前结束,断言不会执行。// 正确 it('async_test', 0, async () => { ... }) // 错误 it('async_test', () => { ... }) -
UI测试的时序问题:UI渲染和动画是异步的。在点击按钮后立即断言,可能因为UI还没更新而失败。必须使用
driver.delayMs(500)或等待特定组件出现。 -
Mock的局限性:Mock能模拟分布式环境,但无法模拟真实的网络延迟和设备性能差异。分布式测试必须包含真机测试,Mock只能作为辅助。
-
测试资源的清理:每个测试用例(
it)结束后,必须清理测试数据(如删除临时文件、重置全局状态)。否则,前一个测试的状态可能影响后一个测试,导致“间歇性失败”。 -
AI测试的随机性:即使固定了随机种子,不同硬件(CPU指令集差异)也可能导致浮点数计算结果有微小差异。在比较AI结果时,应使用误差容忍(如
expect(diff).assertLess(0.001)),而非精确相等。
更多推荐



所有评论(0)