mcpField
F
flutter-add-integration-test

flutter-add-integration-test

github.com/flutter测试与质量3.1k38.6k 次安装2 天前更新
代码仓库

关于 flutter-add-integration-test

面向 Flutter 开发者的集成测试指南,介绍项目配置、测试编写与执行,并说明如何借助 Dart/Flutter MCP server 探索应用界面和交互。

主要功能

  1. 01配置集成测试依赖与入口
  2. 02通过 MCP 检查组件树
  3. 03模拟点击、输入和滚动
  4. 04编写端到端测试
  5. 05运行测试并分析性能

适用场景

  1. 01探索应用交互路径后编写测试
  2. 02验证跨组件的用户流程
  3. 03在 Chrome 或 Android 上运行测试
  4. 04排查未挂载组件及动画超时

MCP 相关

指南建议使用 Dart/Flutter MCP server 启动应用、查看组件树,并通过点击、输入和滚动探索交互。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/flutter/agent-plugins --skill flutter-add-integration-test

安装后在 Claude Code 中输入/flutter-add-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. 在应用代码的关键组件中添加 Key 参数(例如 ValueKey('login_button')),确保测试能够可靠地定位它们。

通过 MCP 进行交互式探索

使用 Dart/Flutter MCP 服务工具,在编写静态测试之前交互式地探索和操作应用状态。

  • 启动:执行 launch_app,并设置 target: "lib/main_test.dart",以启动应用并获取 DTD URI。
  • 检查:执行 get_widget_tree,以查找可用的 Key、Text 节点和组件 Type。
  • 交互:执行 tap、enter_text 和 scroll,以模拟用户流程。
  • 等待:导航或触发动画时,务必执行 waitFor,或使用 get_health 验证状态。
  • 排查未挂载的组件:如果在组件树中找不到某个组件,它可能是在 SliverList 或 ListView 中延迟加载的。执行 scroll 或 scrollIntoView,使组件挂载后再与其交互。

测试编写指南

使用 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 中放置主机驱动脚本,并在其中调用 integrationDriver()。

有条件的执行目标:

  • **如果在 Chrome 上测试:**在单独的终端中启动 chromedriver --port=4444,然后运行: flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart -d chrome
  • **如果测试无头 Web:**使用 -d web-server 运行。
  • **如果在 Android(本地)上测试:**运行 flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart。
  • 如果在 Firebase Test Lab(Android)上测试:
    1. 构建调试 APK:flutter build apk --debug
    2. 构建测试 APK:./gradlew app:assembleAndroidTest
    3. 将两个 APK 上传到 Firebase Test Lab 控制台。

工作流:端到端集成测试

复制并遵循此清单,以实现并验证集成测试。

  • 任务进度:设置
    • 将 integration_test 和 flutter_test 添加到 pubspec.yaml。
    • 在应用入口点注入 enableFlutterDriverExtension()。
    • 为目标控件分配 ValueKey。
  • 任务进度:探索
    • 通过 MCP 运行 launch_app。
    • 使用 get_widget_tree 映射控件树。
    • 使用 MCP 工具(tap、enter_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,
        );
      }
    },
  );
}

评论

支持 Markdown