跳至主要内容

Widget 测试简介

单元测试简介 菜谱中,您学习了如何使用 test 包测试 Dart 类。要测试 Widget 类,您需要 flutter_test 包提供的几个额外工具,该包与 Flutter SDK 一起提供。

flutter_test 包为测试 Widget 提供以下工具

  • WidgetTester 允许在测试环境中构建和与 Widget 交互。
  • testWidgets() 函数为每个测试用例自动创建一个新的 WidgetTester,并用于代替正常的 test() 函数。
  • Finder 类允许在测试环境中搜索 Widget。
  • 特定于 Widget 的 Matcher 常量有助于验证 Finder 是否在测试环境中定位了一个或多个 Widget。

如果这听起来让人不知所措,请不要担心。在本菜谱中,了解所有这些部分如何组合在一起,它使用以下步骤

  1. 添加 flutter_test 依赖。
  2. 创建要测试的 Widget。
  3. 创建 testWidgets 测试。
  4. 使用 WidgetTester 构建 Widget。
  5. 使用 Finder 搜索 Widget。
  6. 使用 Matcher 验证 Widget。

1. 添加 flutter_test 依赖

#

在编写测试之前,请在 pubspec.yaml 文件的 dev_dependencies 部分中包含 flutter_test 依赖项。如果使用命令行工具或代码编辑器创建新的 Flutter 项目,则此依赖项应该已经存在。

yaml
dev_dependencies:
  flutter_test:
    sdk: flutter

2. 创建要测试的 Widget

#

接下来,创建一个用于测试的 Widget。对于此菜谱,创建一个显示 titlemessage 的 Widget。

dart
class MyWidget extends StatelessWidget {
  const MyWidget({
    super.key,
    required this.title,
    required this.message,
  });

  final String title;
  final String message;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: Scaffold(
        appBar: AppBar(
          title: Text(title),
        ),
        body: Center(
          child: Text(message),
        ),
      ),
    );
  }
}

3. 创建 testWidgets 测试

#

有了要测试的 Widget 后,首先编写您的第一个测试。使用 flutter_test 包提供的 testWidgets() 函数来定义测试。testWidgets 函数允许您定义 Widget 测试并创建一个 WidgetTester 来使用。

此测试验证 MyWidget 是否显示给定的标题和消息。它已相应地命名,并且将在下一节中填充。

dart
void main() {
  // Define a test. The TestWidgets function also provides a WidgetTester
  // to work with. The WidgetTester allows you to build and interact
  // with widgets in the test environment.
  testWidgets('MyWidget has a title and message', (tester) async {
    // Test code goes here.
  });
}

4. 使用 WidgetTester 构建 Widget

#

接下来,通过使用 WidgetTester 提供的 pumpWidget() 方法在测试环境中构建 MyWidgetpumpWidget 方法构建并渲染提供的 Widget。

创建一个 MyWidget 实例,将 "T" 作为标题显示,将 "M" 作为消息显示。

dart
void main() {
  testWidgets('MyWidget has a title and message', (tester) async {
    // Create the widget by telling the tester to build it.
    await tester.pumpWidget(const MyWidget(title: 'T', message: 'M'));
  });
}

关于 pump() 方法的说明

#

在最初调用 pumpWidget() 后,WidgetTester 提供了其他方法来重建相同的 Widget。如果您正在使用 StatefulWidget 或动画,这将非常有用。

例如,点击按钮会调用 setState(),但 Flutter 不会自动在测试环境中重建您的 Widget。使用以下方法之一来要求 Flutter 重建 Widget。

tester.pump(Duration duration)
安排一个帧并触发 Widget 的重建。如果指定了 Duration,则会将时钟提前指定的时间量并安排一个帧。即使持续时间超过一个帧,它也不会安排多个帧。
tester.pumpAndSettle()
使用给定的持续时间重复调用 pump(),直到不再安排任何帧。这实际上是在等待所有动画完成。

这些方法提供了对构建生命周期的细粒度控制,这在测试期间特别有用。

5. 使用 Finder 搜索 Widget

#

在测试环境中有了 Widget 后,使用 Finder 在 Widget 树中搜索 titlemessage Text Widget。这允许验证 Widget 是否正在正确显示。

为此,请使用 flutter_test 包提供的顶级 find() 方法来创建 Finder。由于您知道要查找 Text Widget,因此请使用 find.text() 方法。

有关 Finder 类的更多信息,请参阅 在 Widget 测试中查找 Widget 菜谱。

dart
void main() {
  testWidgets('MyWidget has a title and message', (tester) async {
    await tester.pumpWidget(const MyWidget(title: 'T', message: 'M'));

    // Create the Finders.
    final titleFinder = find.text('T');
    final messageFinder = find.text('M');
  });
}

6. 使用 Matcher 验证 Widget

#

最后,使用 flutter_test 提供的 Matcher 常量验证标题和消息 Text Widget 是否出现在屏幕上。Matcher 类是 test 包的核心部分,并提供了一种通用的方法来验证给定值是否满足预期。

确保 Widget 恰好出现在屏幕上一次。为此,请使用 findsOneWidget Matcher

dart
void main() {
  testWidgets('MyWidget has a title and message', (tester) async {
    await tester.pumpWidget(const MyWidget(title: 'T', message: 'M'));
    final titleFinder = find.text('T');
    final messageFinder = find.text('M');

    // Use the `findsOneWidget` matcher provided by flutter_test to verify
    // that the Text widgets appear exactly once in the widget tree.
    expect(titleFinder, findsOneWidget);
    expect(messageFinder, findsOneWidget);
  });
}

其他 Matcher

#

除了 findsOneWidget 之外,flutter_test 还为常见情况提供了其他 Matcher。

findsNothing
验证未找到任何 Widget。
findsWidgets
验证找到一个或多个 Widget。
findsNWidgets
验证找到特定数量的 Widget。
matchesGoldenFile
验证 Widget 的渲染是否与特定位图图像(“黄金文件”测试)匹配。

完整示例

#
dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  // Define a test. The TestWidgets function also provides a WidgetTester
  // to work with. The WidgetTester allows building and interacting
  // with widgets in the test environment.
  testWidgets('MyWidget has a title and message', (tester) async {
    // Create the widget by telling the tester to build it.
    await tester.pumpWidget(const MyWidget(title: 'T', message: 'M'));

    // Create the Finders.
    final titleFinder = find.text('T');
    final messageFinder = find.text('M');

    // Use the `findsOneWidget` matcher provided by flutter_test to
    // verify that the Text widgets appear exactly once in the widget tree.
    expect(titleFinder, findsOneWidget);
    expect(messageFinder, findsOneWidget);
  });
}

class MyWidget extends StatelessWidget {
  const MyWidget({
    super.key,
    required this.title,
    required this.message,
  });

  final String title;
  final String message;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: Scaffold(
        appBar: AppBar(
          title: Text(title),
        ),
        body: Center(
          child: Text(message),
        ),
      ),
    );
  }
}