flutter-add-integration-test

flutter-add-integration-test

热门

配置 Flutter Driver 实现应用交互,并将 MCP 操作转化为持久化的集成测试。适用于在项目中添加集成测试、通过 MCP 探索 UI 组件,或者基于 integration_test 包实现用户操作流程自动化的场景。

2783Star
163Fork
更新于 2026/8/5
SKILL.md
只读
名称
flutter-add-integration-test
描述

配置 Flutter Driver 实现应用交互,并将 MCP 操作转化为持久化的集成测试。适用于在项目中添加集成测试、通过 MCP 探索 UI 组件,或者基于 integration_test 包实现用户操作流程自动化的场景。

实现 Flutter 集成测试

目录

项目配置与依赖添加

配置项目以支持集成测试和 Flutter Driver 扩展。

  1. pubspec.yaml 中添加所需的开发依赖:
    flutter pub add 'dev:integration_test:{"sdk":"flutter"}'
    flutter pub add 'dev:flutter_test:{"sdk":"flutter"}'
    
  2. 在应用入口文件(通常为 lib/main.dart 或专门的 lib/main_test.dart)中启用 Flutter Driver 扩展:
    • 导入 package:flutter_driver/driver_extension.dart
    • 在调用 runApp() 之前先执行 enableFlutterDriverExtension();
  3. 给应用代码中的关键组件(Widget)添加 Key 参数(例如 ValueKey('login_button')),确保测试时能精准定位。

通过 MCP 进行交互式探索

在编写静态测试脚本之前,可利用 Dart/Flutter MCP 服务器工具动态探索并操作应用状态。

  • 启动应用:执行 launch_app 并指定 target: "lib/main_test.dart",以启动应用并获取 DTD URI。
  • 检查树结构:执行 get_widget_tree 查看可用的 KeyText 节点以及组件类型(Type)。
  • 交互操作:执行 tapenter_textscroll 来模拟真实用户操作路径。
  • 等待同步:在进行页面跳转或触发动画时,务必调用 waitFor 或通过 get_health 校验当前状态。
  • 排查未挂载组件:如果组件树中找不到某个 Widget,可能是由于 SliverListListView 采用了懒加载机制。此时可先执行 scrollscrollIntoView 强制其挂载显示,再进行后续交互。

测试编写规范

使用 flutter_test API 规范组织集成测试代码。

  • 在项目根目录下创建专用的 integration_test/ 文件夹。
  • 遵循 <name>_test.dart 的命名约定创建测试文件。
  • main() 函数开头调用 IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 初始化绑定。
  • 使用 await tester.pumpWidget(MyApp()); 加载应用 UI。
  • 在触发 tester.tap() 等交互后,调用 await tester.pumpAndSettle(); 触发帧刷新并等待动画完成。
  • 使用 expect(find.byKey(ValueKey('foo')), findsOneWidget);findsNothing 断言组件可见性。
  • 对于位于屏幕外的组件,使用 await tester.scrollUntilVisible(itemFinder, 500.0, scrollable: listFinder); 滚动至其可见。

针对旧版 flutter_driver 的兼容处理:

  • 如果需要维护或迁移旧版 flutter_driver 测试,请使用 driver.waitFor()driver.waitForAbsent()driver.tap()driver.scroll(),而非 WidgetTester API。

运行测试与性能分析

使用 flutter drive 命令运行测试。这需要一个位于 test_driver/integration_test.dart 的宿主驱动脚本(Host Driver Script),其中调用了 integrationDriver()

不同目标的执行命令:

  • 在 Chrome 上测试: 在独立终端启动 chromedriver --port=4444,然后运行:
    flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart -d chrome
  • 在无头浏览器(Headless Web)上测试: 运行时附带 -d web-server 参数。
  • 在 Android(本地设备)上测试: 运行 flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart
  • 在 Firebase Test Lab(Android)上测试:
    1. 打包 Debug APK:flutter build apk --debug
    2. 打包测试 APK:./gradlew app:assembleAndroidTest
    3. 将生成的两个 APK 上传至 Firebase Test Lab 控制台。

工作流:端到端集成测试

复制并参照以下清单完成集成测试的实现与验证:

  • [ ] 任务进度:环境配置
    • [ ] 将 integration_testflutter_test 添加到 pubspec.yaml
    • [ ] 在应用入口注入 enableFlutterDriverExtension()
    • [ ] 为目标组件分配 ValueKey
  • [ ] 任务进度:交互探索
    • [ ] 通过 MCP 运行 launch_app
    • [ ] 使用 get_widget_tree 梳理组件树。
    • [ ] 利用 MCP 工具(tapenter_text)验证交互路径。
  • [ ] 任务进度:代码撰写
    • [ ] 创建 integration_test/app_test.dart
    • [ ] 使用 WidgetTester API 编写测试用例。
    • [ ] 创建包含 integrationDriver()test_driver/integration_test.dart
  • [ ] 任务进度:运行与反馈循环
    • [ ] 运行 flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart
    • [ ] 反馈循环:检查测试输出 -> 若抛出 PumpAndSettleTimedOutException,检查是否存在无限循环动画 -> 若找不到组件,补充 scrollUntilVisible -> 重复运行直至测试全部通过。

示例代码

标准集成测试 (integration_test/app_test.dart)

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('tap on the floating action button, verify counter', (tester) async {
      // Load app widget.
      await tester.pumpWidget(const MyApp());

      // Verify the counter starts at 0.
      expect(find.text('0'), findsOneWidget);

      // Find the floating action button to tap on.
      final fab = find.byKey(const ValueKey('increment'));

      // Emulate a tap on the floating action button.
      await tester.tap(fab);

      // Trigger a frame and wait for animations.
      await tester.pumpAndSettle();

      // Verify the counter increments by 1.
      expect(find.text('1'), findsOneWidget);
    });
  });
}

宿主驱动脚本 (test_driver/integration_test.dart)

import 'package:integration_test/integration_test_driver.dart';

Future<void> main() => integrationDriver();

性能分析驱动脚本 (test_driver/perf_driver.dart)

如果在测试操作中使用了 binding.traceAction() 来捕获性能指标,请配合使用以下驱动脚本:

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,
        );
      }
    },
  );
}