flutter-testing-apps

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Testing Flutter Applications

Flutter应用测试

Contents

目录

Core Testing Strategies

核心测试策略

Balance your testing suite across three main categories to optimize for confidence, maintenance cost, dependencies, and execution speed.
在三个主要测试类别之间平衡你的测试套件,以在可信度、维护成本、依赖关系和执行速度方面实现最优。

Unit Tests

单元测试

Use unit tests to verify the correctness of a single function, method, or class under various conditions.
  • Mock all external dependencies.
  • Do not involve disk I/O, screen rendering, or user actions from outside the test process.
  • Execute using the
    test
    or
    flutter_test
    package.
使用单元测试来验证单个函数、方法或类在各种条件下的正确性。
  • 模拟所有外部依赖。
  • 不涉及磁盘I/O、屏幕渲染或测试进程外的用户操作。
  • 使用
    test
    flutter_test
    包执行。

Widget Tests

Widget测试

Use widget tests (component tests) to ensure a single widget's UI looks and interacts as expected.
  • Provide the appropriate widget lifecycle context using
    WidgetTester
    .
  • Use
    Finder
    classes to locate widgets and
    Matcher
    constants to verify their existence and state.
  • Test views and UI interactions without spinning up the full application.
使用Widget测试(组件测试)确保单个Widget的UI外观和交互符合预期。
  • 使用
    WidgetTester
    提供合适的Widget生命周期上下文。
  • 使用
    Finder
    类定位Widget,使用
    Matcher
    常量验证其存在和状态。
  • 在不启动完整应用的情况下测试视图和UI交互。

Integration Tests

集成测试

Use integration tests (end-to-end or GUI testing) to validate how individual pieces of an app work together and to capture performance metrics on real devices.
  • Add the
    integration_test
    package as a dependency.
  • Run on physical devices, OS emulators, or Firebase Test Lab.
  • Prioritize integration tests for routing, dependency injection, and critical user flows.
使用集成测试(端到端或GUI测试)验证应用各个部分的协同工作情况,并在真实设备上捕获性能指标。
  • 添加
    integration_test
    包作为依赖。
  • 在物理设备、操作系统模拟器或Firebase Test Lab上运行。
  • 优先为路由、依赖注入和关键用户流程编写集成测试。

Architectural Testing Guidelines

架构测试指南

Design your application for observability and testability. Ensure all components can be tested both in isolation and together.
  • ViewModels: Write unit tests for every ViewModel class. Test the UI logic without relying on Flutter libraries or testing frameworks.
  • Repositories & Services: Write unit tests for every service and repository. Mock the underlying data sources (e.g., HTTP clients, local databases).
  • Views: Write widget tests for all views. Pass faked or mocked ViewModels and Repositories into the widget tree to isolate the UI.
  • Fakes over Mocks: Prefer creating
    Fake
    implementations of your repositories (e.g.,
    FakeUserRepository
    ) over using mocking libraries when testing ViewModels and Views to ensure well-defined inputs and outputs.
设计具备可观测性和可测试性的应用。确保所有组件都能被单独测试或协同测试。
  • ViewModels:为每个ViewModel类编写单元测试。在不依赖Flutter库或测试框架的情况下测试UI逻辑。
  • Repositories & Services:为每个服务和仓库编写单元测试。模拟底层数据源(如HTTP客户端、本地数据库)。
  • Views:为所有视图编写Widget测试。将伪造或模拟的ViewModel和仓库传入Widget树以隔离UI。
  • 优先使用Fake而非Mock:在测试ViewModel和视图时,优先创建仓库的
    Fake
    实现(如
    FakeUserRepository
    ),而非使用模拟库,以确保输入和输出的定义清晰。

Plugin Testing Guidelines

插件测试指南

When testing plugins, combine Dart tests with native platform tests to ensure full coverage across the method channel.
  • Dart Tests: Use Dart unit and widget tests for the Dart-facing API. Mock the platform channel to validate Dart logic.
  • Native Unit Tests: Implement native unit tests for isolated platform logic.
    • Android: Configure JUnit tests in
      android/src/test/
      .
    • iOS/macOS: Configure XCTest tests in
      example/ios/RunnerTests/
      and
      example/macos/RunnerTests/
      .
    • Linux/Windows: Configure GoogleTest tests in
      linux/test/
      and
      windows/test/
      .
  • Native UI Tests: Use Espresso (Android) or XCUITest (iOS) if the plugin requires native UI interactions.
  • Integration Tests: Write at least one integration test for each platform channel call to verify Dart-to-Native communication.
  • End-to-End Fallback: If integration tests cannot cover a flow (e.g., mocking device state), synthesize calls to the method channel entry point using native unit tests, and test the Dart public API using Dart unit tests.
测试插件时,结合Dart测试与原生平台测试,确保方法通道的全面覆盖。
  • Dart测试:针对Dart面向的API使用Dart单元测试和Widget测试。模拟平台通道以验证Dart逻辑。
  • 原生单元测试:为独立的平台逻辑实现原生单元测试。
    • Android:在
      android/src/test/
      中配置JUnit测试。
    • iOS/macOS:在
      example/ios/RunnerTests/
      example/macos/RunnerTests/
      中配置XCTest测试。
    • Linux/Windows:在
      linux/test/
      windows/test/
      中配置GoogleTest测试。
  • 原生UI测试:如果插件需要原生UI交互,使用Espresso(Android)或XCUITest(iOS)。
  • 集成测试:为每个平台通道调用至少编写一个集成测试,以验证Dart到原生的通信。
  • 端到端测试备选方案:如果集成测试无法覆盖某个流程(如模拟设备状态),则使用原生单元测试合成对方法通道入口点的调用,并使用Dart单元测试测试Dart公共API。

Workflows

工作流程

Workflow: Implementing a Component Test Suite

工作流程:实现组件测试套件

Copy and track this checklist when implementing tests for a new architectural feature.
  • Task Progress
    • Create
      Fake
      implementations for any new Repositories or Services.
    • Write Unit Tests for the Repository (mocking the API/Database).
    • Write Unit Tests for the ViewModel (injecting the Fake Repositories).
    • Write Widget Tests for the View (injecting the ViewModel and Fake Repositories).
    • Write an Integration Test for the critical path involving this feature.
    • Run validator -> review coverage -> fix missing edge cases.
在为新架构功能实现测试时,复制并遵循此检查清单。
  • 任务进度
    • 为任何新的仓库或服务创建
      Fake
      实现。
    • 为仓库编写单元测试(模拟API/数据库)。
    • 为ViewModel编写单元测试(注入Fake仓库)。
    • 为视图编写Widget测试(注入ViewModel和Fake仓库)。
    • 为涉及此功能的关键路径编写集成测试。
    • 运行验证器 -> 检查覆盖率 -> 修复遗漏的边缘情况。

Workflow: Running Integration Tests

工作流程:运行集成测试

Follow conditional logic based on the target platform when executing integration tests.
  1. If testing on Mobile (Local):
    • Connect the Android/iOS device or emulator.
    • Run:
      flutter test integration_test/app_test.dart
  2. If testing on Web:
    • Install and launch ChromeDriver:
      chromedriver --port=4444
    • Run:
      flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart -d chrome
  3. If testing on Linux (CI System):
    • Invoke an X server using
      xvfb-run
      to provide a display environment.
    • Run:
      xvfb-run flutter test integration_test/app_test.dart -d linux
  4. If testing via Firebase Test Lab:
    • Build the Android test APKs:
      flutter build apk --debug
      and
      ./gradlew app:assembleAndroidTest
    • Upload the App APK and Test APK to the Firebase Console.
执行集成测试时,根据目标平台遵循相应的逻辑。
  1. 如果在移动端(本地)测试
    • 连接Android/iOS设备或模拟器。
    • 运行:
      flutter test integration_test/app_test.dart
  2. 如果在Web端测试
    • 安装并启动ChromeDriver:
      chromedriver --port=4444
    • 运行:
      flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart -d chrome
  3. 如果在Linux(CI系统)测试
    • 使用
      xvfb-run
      调用X服务器以提供显示环境。
    • 运行:
      xvfb-run flutter test integration_test/app_test.dart -d linux
  4. 如果通过Firebase Test Lab测试
    • 构建Android测试APK:
      flutter build apk --debug
      ./gradlew app:assembleAndroidTest
    • 将应用APK和测试APK上传到Firebase控制台。

Examples

示例

Example: ViewModel Unit Test

示例:ViewModel单元测试

Demonstrates testing a ViewModel using a Fake Repository.
dart
import 'package:flutter_test/flutter_test.dart';

void main() {
  group('HomeViewModel tests', () {
    test('Load bookings successfully', () {
      // Inject fake dependencies
      final viewModel = HomeViewModel(
        bookingRepository: FakeBookingRepository()..createBooking(kBooking),
        userRepository: FakeUserRepository(),
      );

      // Verify state
      expect(viewModel.bookings.isNotEmpty, true);
    });
  });
}
演示如何使用Fake仓库测试ViewModel。
dart
import 'package:flutter_test/flutter_test.dart';

void main() {
  group('HomeViewModel tests', () {
    test('Load bookings successfully', () {
      // Inject fake dependencies
      final viewModel = HomeViewModel(
        bookingRepository: FakeBookingRepository()..createBooking(kBooking),
        userRepository: FakeUserRepository(),
      );

      // Verify state
      expect(viewModel.bookings.isNotEmpty, true);
    });
  });
}

Example: View Widget Test

示例:视图Widget测试

Demonstrates testing a View by pumping a localized widget tree with fake dependencies.
dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  group('HomeScreen tests', () {
    late HomeViewModel viewModel;
    late FakeBookingRepository bookingRepository;

    setUp(() {
      bookingRepository = FakeBookingRepository()..createBooking(kBooking);
      viewModel = HomeViewModel(
        bookingRepository: bookingRepository,
        userRepository: FakeUserRepository(),
      );
    });

    testWidgets('renders bookings list', (WidgetTester tester) async {
      await tester.pumpWidget(
        MaterialApp(
          home: HomeScreen(viewModel: viewModel),
        ),
      );

      // Verify UI state
      expect(find.byType(ListView), findsOneWidget);
      expect(find.text('Booking 1'), findsOneWidget);
    });
  });
}
演示如何通过注入伪造依赖的本地化Widget树来测试视图。
dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  group('HomeScreen tests', () {
    late HomeViewModel viewModel;
    late FakeBookingRepository bookingRepository;

    setUp(() {
      bookingRepository = FakeBookingRepository()..createBooking(kBooking);
      viewModel = HomeViewModel(
        bookingRepository: bookingRepository,
        userRepository: FakeUserRepository(),
      );
    });

    testWidgets('renders bookings list', (WidgetTester tester) async {
      await tester.pumpWidget(
        MaterialApp(
          home: HomeScreen(viewModel: viewModel),
        ),
      );

      // Verify UI state
      expect(find.byType(ListView), findsOneWidget);
      expect(find.text('Booking 1'), findsOneWidget);
    });
  });
}

Example: Integration Test

示例:集成测试

Demonstrates a full end-to-end test using the
integration_test
package.
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 initial state
      expect(find.text('0'), findsOneWidget);

      // Find and tap the button
      final fab = find.byKey(const ValueKey('increment'));
      await tester.tap(fab);

      // Trigger a frame to allow animations/state to settle
      await tester.pumpAndSettle();

      // Verify updated state
      expect(find.text('1'), findsOneWidget);
    });
  });
}
演示使用
integration_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 initial state
      expect(find.text('0'), findsOneWidget);

      // Find and tap the button
      final fab = find.byKey(const ValueKey('increment'));
      await tester.tap(fab);

      // Trigger a frame to allow animations/state to settle
      await tester.pumpAndSettle();

      // Verify updated state
      expect(find.text('1'), findsOneWidget);
    });
  });
}