🧪 Flutter + OpenHarmony 自动化测试体系:从单元测试到多端 E2E 的全流程保障
作者 :晚霞的不甘
日期 :2025年12月5日
标签:Flutter · OpenHarmony · 自动化测试 · 单元测试 · 集成测试 · E2E 测试 · CI/CD · 鸿蒙生态

引言:质量,是交付的底线,不是可选项
在 OpenHarmony 多设备、高安全、强审核的生态下,一次线上缺陷可能导致全端下架:
- 手机端正常,车机端崩溃 → 用户投诉
- 分布式任务迁移失败 → 体验断裂
- 权限变更未测试 → 审核被拒
手动测试无法覆盖:
- 5+ 设备类型组合
- 100+ 权限开关场景
- 弱网/断电/后台杀进程等异常流
本文将构建一套分层自动化测试体系 ,覆盖 Dart 逻辑、原生插件、跨设备协同、UI 交互 四大维度,助你实现:
- 核心业务单元测试覆盖率 ≥ 80%
- 关键路径 E2E 自动化覆盖 100%
- CI 中 10 分钟内完成全量回归
一、测试金字塔:分层策略与工具选型
plaintext
┌───────────────────────┐
│ 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 | 跨设备任务迁移、分布式数据同步 |
二、单元测试:保障 Dart 逻辑正确性
2.1 测试目标
- 状态管理(Bloc/Provider)
- 工具类(日期格式化、加密)
- 业务规则(如"优惠券是否可用")
2.2 实战:测试一个健康数据计算逻辑
dart
// 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;
}
dart
// 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>()));
});
}
2.3 运行与覆盖率
bash
# 运行测试
flutter test
# 生成覆盖率报告
flutter test --coverage
genhtml coverage/lcov.info -o coverage/html
✅ 目标:核心模块覆盖率 ≥ 80%
三、集成测试:验证 Dart 与原生协同
3.1 模拟 MethodChannel
dart
// 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);
});
}
3.2 测试异常流
dart
when(mockChannel.invokeMethod('readHeartRate'))
.thenThrow(PlatformException(code: 'PERMISSION_DENIED'));
expectLater(
service.getHeartRate(),
throwsA(isA<HealthPermissionException>()),
);
四、UI 测试:Flutter Driver 实战
4.1 编写测试脚本
dart
// 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')));
});
});
}
4.2 在真机运行
bash
# 构建 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若插件不支持空安全
五、多端 E2E 测试:验证超级终端场景
5.1 场景:手机启动监测 → 数据同步至手表
使用 DevEco Studio UI Test(基于 JUnit + ArkTS):
ts
// 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/);
}
}
5.2 多设备模拟器联动
- 在 DevEco 中同时启动 Phone + Watch 模拟器
- 使用
hdc -t <device_id>指定操作目标设备
六、CI/CD 集成:自动化回归
6.1 GitLab CI 示例
yaml
# .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]
6.2 质量门禁
- 单元测试通过率 = 100%
- 覆盖率下降 > 5% → 阻断合并
- E2E 关键路径失败 → 自动创建 Jira 缺陷
七、测试最佳实践 Checklist
✅ 所有新功能必须附带单元测试
✅ 核心用户路径(如登录、支付)100% E2E 覆盖
✅ 每次 PR 触发 CI 全量回归
✅ 多设备组合测试每周执行一次
✅ 异常场景(权限拒绝、网络中断)纳入测试用例
八、常见问题与解决方案
| 问题 | 原因 | 解决方案 |
|---|---|---|
| Flutter Driver 找不到元素 | 未设置 key |
为 Widget 添加 ValueKey('xxx') |
| 真机测试超时 | 设备未授权调试 | 执行 hdc kill 重连 |
| 多设备测试不同步 | 时间未校准 | 使用 NTP 同步设备时间 |
| 覆盖率不准确 | 未排除 generated 文件 | 在 lcov 中添加 -e "*/*.g.dart" |
结语:自动化测试,是快速迭代的基石
没有自动化的质量保障:
- 不敢重构 → 代码腐化
- 发布周期长 → 错失市场
- 用户信任流失 → 品牌受损
🧪 行动建议:
- 今天就为一个工具函数写单元测试
- 明天为登录流程添加 Flutter Driver 脚本
- 下周在 CI 中集成覆盖率报告
因为每一次自信的点击"发布",都源于背后千次自动验证。
附录:测试工具速查
| 工具 | 用途 | 文档 |
|---|---|---|
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,而是证明软件值得被信任。