
作者:晚霞的不甘 日期:2025年12月5日 标签:Flutter · OpenHarmony · 自动化测试 · 单元测试 · 集成测试 · E2E 测试 · CI/CD · 鸿蒙生态

在 OpenHarmony 多设备、高安全、强审核的生态下,一次线上缺陷可能导致全端下架:
手动测试无法覆盖:
本文将构建一套分层自动化测试体系,覆盖 Dart 逻辑、原生插件、跨设备协同、UI 交互 四大维度,助你实现:
┌───────────────────────┐
│ E2E 测试 (5%) │ ← DevEco UI Test / Flutter Driver
├───────────────────────┤
│ 集成测试 (15%) │ ← Mock 原生通道 + 真实业务流
├───────────────────────┤
│ 单元测试 (80%) │ ← flutter test + mockito
└───────────────────────┘测试类型 | 工具 | 运行速度 | 适用场景 |
|---|---|---|---|
单元测试 | flutter test | < 1s | 纯 Dart 逻辑(如状态管理、工具函数) |
集成测试 | flutter test + Mock | 2–5s | 调用 MethodChannel 的业务逻辑 |
UI 测试 | Flutter Driver | 10–30s | 页面跳转、表单提交、列表滚动 |
多端 E2E | DevEco UI Test | 30–60s | 跨设备任务迁移、分布式数据同步 |
// lib/utils/health_calculator.dart
double calculateAvgHeartRate(List<int> rates) {
if (rates.isEmpty) throw ArgumentError('Rates cannot be empty');
return rates.reduce((a, b) => a + b) / rates.length;
}// test/utils/health_calculator_test.dart
import 'package:test/test.dart';
import 'package:my_app/utils/health_calculator.dart';
void main() {
test('calculateAvgHeartRate returns correct average', () {
expect(calculateAvgHeartRate([60, 70, 80]), equals(70.0));
});
test('calculateAvgHeartRate throws on empty list', () {
expect(() => calculateAvgHeartRate([]), throwsA(isA<ArgumentError>()));
});
}# 运行测试
flutter test
# 生成覆盖率报告
flutter test --coverage
genhtml coverage/lcov.info -o coverage/html✅ 目标:核心模块覆盖率 ≥ 80%
// test/integration/health_service_test.dart
import 'package:flutter/services.dart';
import 'package:mockito/mockito.dart';
class MockMethodChannel extends Mock implements MethodChannel {}
void main() {
late MockMethodChannel mockChannel;
late HealthService service;
setUp(() {
mockChannel = MockMethodChannel();
// 注入 Mock 通道
when(mockChannel.invokeMethod('readHeartRate'))
.thenAnswer((_) async => 72);
service = HealthService(channel: mockChannel);
});
test('getHeartRate returns mocked value', () async {
final rate = await service.getHeartRate();
expect(rate, equals(72));
verify(mockChannel.invokeMethod('readHeartRate')).called(1);
});
}when(mockChannel.invokeMethod('readHeartRate'))
.thenThrow(PlatformException(code: 'PERMISSION_DENIED'));
expectLater(
service.getHeartRate(),
throwsA(isA<HealthPermissionException>()),
);// test_driver/app_test.dart
import 'package:flutter_driver/flutter_driver.dart';
import 'package:test/test.dart';
void main() {
group('Health App E2E', () {
final heartRateFinder = find.byValueKey('heart_rate_display');
final startButton = find.byValueKey('start_monitoring');
FlutterDriver driver;
setUpAll(() async {
driver = await FlutterDriver.connect();
});
tearDownAll(() async {
if (driver != null) await driver.close();
});
test('shows heart rate after start', () async {
await driver.tap(startButton);
await driver.waitFor(heartRateFinder);
final text = await driver.getText(heartRateFinder);
expect(text, matches(RegExp(r'\d+ bpm')));
});
});
}# 构建 profile 包
flutter build ohos --profile
# 安装并运行测试
hdc install build/ohos/profile/outputs/default/entry-default-signed.hap
flutter drive --target=test_driver/app_test.dart⏱️ 提示:使用
--no-sound-null-safety若插件不支持空安全
使用 DevEco Studio UI Test(基于 JUnit + ArkTS):
// ohosTest/HealthSyncTest.ets
import { describe, it, expect } from '@ohos/test';
@Describe('Health Data Sync')
class HealthSyncTest {
@It('should sync heart rate to wearable')
async testSyncToWearable() {
// 1. 在手机端启动监测
await this.launchAppOnDevice('phone');
await this.clickButton('start_monitoring');
// 2. 等待手表端接收数据
await this.launchAppOnDevice('watch');
const rate = await this.getText('latest_heart_rate');
// 3. 验证数据一致
expect(rate).toMatch(/\d+ bpm/);
}
}hdc -t <device_id> 指定操作目标设备# .gitlab-ci.yml
stages:
- test
- e2e
unit_test:
stage: test
script:
- flutter test --coverage
- genhtml coverage/lcov.info -o coverage/
artifacts:
paths: [coverage/]
ui_test_phone:
stage: e2e
script:
- flutter build ohos --profile
- hdc install ...
- flutter drive --target=test_driver/app_test.dart
tags: [ohos_device]
ui_test_watch:
stage: e2e
script:
- ./run_dev_evo_test.sh HealthSyncTest
tags: [ohos_wearable]✅ 所有新功能必须附带单元测试 ✅ 核心用户路径(如登录、支付)100% E2E 覆盖 ✅ 每次 PR 触发 CI 全量回归 ✅ 多设备组合测试每周执行一次 ✅ 异常场景(权限拒绝、网络中断)纳入测试用例
问题 | 原因 | 解决方案 |
|---|---|---|
Flutter Driver 找不到元素 | 未设置 key | 为 Widget 添加 ValueKey('xxx') |
真机测试超时 | 设备未授权调试 | 执行 hdc kill 重连 |
多设备测试不同步 | 时间未校准 | 使用 NTP 同步设备时间 |
覆盖率不准确 | 未排除 generated 文件 | 在 lcov 中添加 -e "*/*.g.dart" |
没有自动化的质量保障:
🧪 行动建议:
因为每一次自信的点击“发布”,都源于背后千次自动验证。
附录:测试工具速查
工具 | 用途 | 文档 |
|---|---|---|
flutter test | 单元/集成测试 | https://docs.flutter.dev/testing |
Flutter Driver | UI 自动化 | https://docs.flutter.dev/testing/integration-tests |
DevEco UI Test | 多端 E2E | https://developer.huawei.com/consumer/cn/doc/development/Testing-Guides |
OhTestRunner | OpenHarmony 测试框架 | https://gitee.com/openharmony/xts_acts |
测试不是找 Bug,而是证明软件值得被信任。