关于 flutter-add-integration-test
flutter-add-integration-test 是指导开发者为 Flutter 项目添加和运行集成测试的技能,涵盖测试配置与编写、设备和 Web 目标、性能时间线及 CI 工作流。
主要功能
- 01配置集成测试依赖
- 02编写组件交互与断言
- 03在设备和 Web 上运行测试
- 04捕获性能时间线
- 05配置 CI 集成测试
适用场景
- 01测试跨屏幕应用流程
- 02验证带键组件的交互
- 03在 Chrome 中运行集成测试
- 04分析测试操作性能
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ dhruvanbhalara/ skills --skill flutter-add-integration-test安装后在 Claude Code 中输入/flutter-add-integration-test确认已加载。
详细介绍
目录
项目设置
-
将所需的开发依赖项添加到
pubspec.yaml:flutter pub add 'dev:integration_test:{"sdk":"flutter"}' flutter pub add 'dev:flutter_test:{"sdk":"flutter"}' -
创建目录结构:
project_root/ ├── integration_test/ │ └── app_test.dart # 测试用例 └── test_driver/ └── integration_test.dart # 宿主驱动脚本 -
在
test_driver/integration_test.dart创建宿主驱动脚本:import 'package:integration_test/integration_test_driver.dart'; Future<void> main() => integrationDriver(); -
在生产代码的关键组件上添加
ValueKey,以便可靠地定位:FloatingActionButton( key: const ValueKey('increment_fab'), onPressed: _increment, child: const Icon(Icons.add), )
编写测试
- 在
main()顶部初始化绑定:这会替换默认的测试绑定。 - 使用
tester.pumpWidget(const MyApp())加载完整应用。 - 每次交互后使用
tester.pumpAndSettle(),等待动画和异步操作完成。 - 使用
expect(find.byKey(ValueKey('foo')), findsOneWidget)断言组件是否可见。 - 使用
tester.scrollUntilVisible(finder, 500.0)滚动到屏幕外的组件。
测试文件结构
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:my_app/main.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('End-to-end test', () {
testWidgets('complete user flow', (tester) async {
// Load full app
await tester.pumpWidget(const MyApp());
// Interact with widgets
await tester.tap(find.byKey(const ValueKey('login_button')));
await tester.pumpAndSettle();
// Assert navigation happened
expect(find.byType(HomePage), findsOneWidget);
});
});
}
执行目标
根据目标平台选择执行方式:
本地设备(Android/iOS)
flutter test integration_test/
对于 iOS,如果使用默认的 Swift Package Manager(SPM)集成(Flutter 3.44+)运行,请确保先解析依赖包:
xcodebuild -resolvePackageDependencies -workspace ios/Runner.xcworkspace -scheme Runner
Chrome(Web)
# Terminal 1: Start ChromeDriver
chromedriver --port=4444
# Terminal 2: Run tests
flutter drive \
--driver=test_driver/integration_test.dart \
--target=integration_test/app_test.dart \
-d chrome
无头 Web
flutter drive \
--driver=test_driver/integration_test.dart \
--target=integration_test/app_test.dart \
-d web-server
Firebase Test Lab(Android)
# 1. Build debug APK
flutter build apk --debug
# 2. Build instrumentation test APK
pushd android && ./gradlew app:assembleAndroidTest && popd
# 3. Upload both APKs to Firebase Test Lab via console or gcloud:
gcloud firebase test android run \
--type instrumentation \
--app build/app/outputs/flutter-apk/app-debug.apk \
--test build/app/outputs/apk/androidTest/debug/app-debug-androidTest.apk
性能分析
使用 binding.traceAction() 包裹测试操作,以采集性能时间线:
void main() {
final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('scrolling performance', (tester) async {
await tester.pumpWidget(const MyApp());
await binding.traceAction(() async {
final listFinder = find.byType(Scrollable);
await tester.fling(listFinder, const Offset(0, -500), 10000);
await tester.pumpAndSettle();
}, reportKey: 'scrolling_timeline');
});
}
性能分析驱动程序
使用此驱动程序采集并写入时间线数据:
import 'package:flutter_driver/flutter_driver.dart' as driver;
import 'package:integration_test/integration_test_driver.dart';
Future<void> main() {
return integrationDriver(
responseDataCallback: (data) async {
if (data != null) {
final timeline = driver.Timeline.fromJson(
data['scrolling_timeline'] as Map<String, dynamic>,
);
final summary = driver.TimelineSummary.summarize(timeline);
await summary.writeTimelineToFile(
'scrolling_timeline',
pretty: true,
includeSummary: true,
);
}
},
);
}
CI/CD 集成
GitHub Actions 工作流
- name: Run integration tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 33
script: flutter test integration_test/ --flavor dev
- 使用
reactivecircus/android-emulator-runner运行 Android 模拟器。 - 使用
actions/upload-artifact收集测试结果工件。 - 对于 Web,将
chromedriver作为服务运行,并使用-d chrome进行测试。
常见问题
| 错误 | 原因 | 修复方法 |
|---|---|---|
PumpAndSettleTimedOutException | 无限动画(例如 CircularProgressIndicator) | 改用 pump(),或关闭加载状态 |
| 找不到组件 | 在 SliverList 或 ListView 中延迟加载 | 交互前调用 scrollUntilVisible() |
| 测试卡住 | 生产代码中的网络调用 | 模拟 HTTP 客户端,或使用 --dart-define 绕过 |
No host driver specified | 缺少 test_driver/integration_test.dart | 创建宿主驱动文件 |
工作流:添加集成测试
任务进度
- 步骤 1:将
integration_test和flutter_test添加到dev_dependencies。 - 步骤 2:为生产代码中的目标组件分配
ValueKey。 - 步骤 3:创建
integration_test/app_test.dart并初始化绑定。 - 步骤 4:创建
test_driver/integration_test.dart并添加integrationDriver()。 - 步骤 5:编写测试用例:加载应用、交互、断言。
- 步骤 6:选择执行目标:
- 本地设备:
flutter test integration_test/ - Chrome:
flutter drive ... -d chrome - Firebase Test Lab:构建并上传 APK
- 本地设备:
- 步骤 7:反馈循环:
- 如果出现
PumpAndSettleTimedOutException→ 检查是否存在无限动画。 - 如果找不到组件 → 添加
scrollUntilVisible。 - 重新运行,直到所有测试通过。
- 如果出现
示例
标准集成测试
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:my_app/main.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('Counter app', () {
testWidgets('tap FAB, verify counter increments', (tester) async {
await tester.pumpWidget(const MyApp());
// Verify initial state
expect(find.text('0'), findsOneWidget);
// Tap the increment button
final fab = find.byKey(const ValueKey('increment_fab'));
await tester.tap(fab);
await tester.pumpAndSettle();
// Verify counter incremented
expect(find.text('1'), findsOneWidget);
});
});
}
Multi-Screen Navigation Flow
testWidgets('login and navigate to home', (tester) async {
await tester.pumpWidget(const MyApp());
// Enter credentials
await tester.enterText(find.byKey(const ValueKey('email_field')), '[email protected]');
await tester.enterText(find.byKey(const ValueKey('password_field')), 'password123');
// Submit login
await tester.tap(find.byKey(const ValueKey('login_button')));
await tester.pumpAndSettle();
// Verify navigation to home
expect(find.byType(HomePage), findsOneWidget);
expect(find.byType(LoginPage), findsNothing);
});
评论
支持 Markdown