flutter-add-integration-test

flutter-add-integration-test

熱門

設定 Flutter Driver 以進行應用程式互動,並將 MCP 動作轉換為永久性的整合測試。適用於為專案新增整合測試、透過 MCP 探索 UI 元件,或使用 integration_test 套件自動化使用者流程。

2525星標
152分支
更新於 2026/6/18
SKILL.md
readonlyread-only
name
flutter-add-integration-test
description

設定 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 節點與 widget Type
  • 互動:執行 tapenter_textscroll 來模擬使用者流程。
  • 等待:在導航或觸發動畫時,務必執行 waitFor 或使用 get_health 驗證狀態。
  • 排除未掛載 widget 問題:如果 widget 在樹中找不到,可能是因為它被延遲載入於 SliverListListView 中。請先執行 scrollscrollIntoView 強制 widget 掛載後再進行互動。

測試撰寫指南

使用 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 斷言 widget 的可見性。
  • 使用 await tester.scrollUntilVisible(itemFinder, 500.0, scrollable: listFinder); 滾動至特定的螢幕外 widget。

舊版 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
  • 如果測試無頭網頁: 使用 -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 主控台。

工作流程:端到端整合測試

複製並遵循此檢查清單來實作與驗證整合測試。

  • [ ] 任務進度:設定
    • [ ] 在 pubspec.yaml 中加入 integration_testflutter_test
    • [ ] 在應用程式進入點注入 enableFlutterDriverExtension()
    • [ ] 為目標 widget 指定 ValueKey
  • [ ] 任務進度:探索
    • [ ] 透過 MCP 執行 launch_app
    • [ ] 使用 get_widget_tree 繪製 widget 樹。
    • [ ] 使用 MCP 工具(tapenter_text)驗證互動路徑。
  • [ ] 任務進度:撰寫
    • [ ] 建立 integration_test/app_test.dart
    • [ ] 使用 WidgetTester API 撰寫測試案例。
    • [ ] 建立 test_driver/integration_test.dart,內容包含 integrationDriver()
  • [ ] 任務進度:執行與回饋迴圈
    • [ ] 執行 flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart
    • [ ] 回饋迴圈:檢視測試輸出 -> 若發生 PumpAndSettleTimedOutException,檢查是否有無限動畫 -> 若找不到 widget,加入 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('端到端測試', () {
    testWidgets('點擊浮動動作按鈕,驗證計數器', (tester) async {
      // 載入應用程式 widget。
      await tester.pumpWidget(const MyApp());

      // 驗證計數器從 0 開始。
      expect(find.text('0'), findsOneWidget);

      // 找到要點擊的浮動動作按鈕。
      final fab = find.byKey(const ValueKey('increment'));

      // 模擬點擊浮動動作按鈕。
      await tester.tap(fab);

      // 觸發影格並等待動畫完成。
      await tester.pumpAndSettle();

      // 驗證計數器增加 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,
        );
      }
    },
  );
}