单元测试介绍 部分,我们学习了使用 test 包测试 Dart 类的方法。为了测试 Widget 类,我们需要使用 flutter_test 包提供的额外工具,这些工具是跟 Flutter SDK 一起发布的。

flutter_test 包提供了以下工具用于测试 Widget:

  • The WidgetTester, which allows us to build and interact with Widgets in a test environment.

  • WidgetTester,使用该工具可在测试环境下建立 Widget 并与其交互。

  • The testWidgets function. This function will automatically create a new WidgetTester for each test case, and is used in place of the normal test function.

  • testWidgets 函数,此函数会自动为每个测试创建一个 WidgetTester,用来代替普通的 test 函数。

  • Finder classes. These allow us to search for Widgets in the test environment.

  • Finder 类,允许我们在测试环境下查找 Widget。

  • Widget-specific Matcher constants, which help us verify whether a Finder locates a Widget or multiple Widgets in the test environment.

  • Widget-specific Matcher 常量,该常量在测试环境下帮助我们验证 Finder 是否定位到一个或多个 Widgets。

如果觉得太复杂,别担心!让我们通过示例把这些内容整合起来。

步骤:

  • 添加一个 flutter_test 依赖

  • 创建一个测试用的 Widget

  • 创建一个 testWidgets 测试方法

  • 使用 WidgetTester 建立 Widget

  • 使用 Finder 查找 Widget

  • 使用 Matcher 验证 Widget 是否正常工作

一. 添加一个 flutter_test 依赖

我们开始编写测试之前,需要先给 pubspec.yaml 文件的 dev_dependencies 段添加 flutter_test 依赖。如果使用命令行或编译器新建一个 Flutter 项目,那么依赖已经默认添加了。

  1. dev_dependencies:
  2. flutter_test:
  3. sdk: flutter

二. 创建一个测试用的 Widget

接下来,我们需要创建一个可以测试的 Widget!在此例中,我们创建了一个 Widget 显示一个标题信息

  1. class MyWidget extends StatelessWidget {
  2. final String title;
  3. final String message;
  4. const MyWidget({
  5. Key key,
  6. @required this.title,
  7. @required this.message,
  8. }) : super(key: key);
  9. @override
  10. Widget build(BuildContext context) {
  11. return MaterialApp(
  12. title: 'Flutter Demo',
  13. home: Scaffold(
  14. appBar: AppBar(
  15. title: Text(title),
  16. ),
  17. body: Center(
  18. child: Text(message),
  19. ),
  20. ),
  21. );
  22. }
  23. }

三. 创建一个 testWidgets 测试方法

现在我们有了一个可以测试的 Widget,可以开始编写第一个测试了!第一步,我们用 flutter_test 包提供的 testWidgets 函数定义一个测试。testWidgets 函数可以定义一个 Widget 测试并创建一个可以使用的 WidgetTester

我们的测试会验证 MyWidget 是否显示给定的标题和信息。

  1. void main() {
  2. // Define a test. The TestWidgets function will also provide a WidgetTester
  3. // for us to work with. The WidgetTester will allow us to build and interact
  4. // with Widgets in the test environment.
  5. testWidgets('MyWidget has a title and message', (WidgetTester tester) async {
  6. // Test code will go here!
  7. });
  8. }

四. 使用 WidgetTester 建立 Widget

下一步,为了在测试环境中建立 MyWidget,我们可以使用 WidgetTester 提供的 pumpWidget 方法。pumpWidget 方法会建立并渲染我们提供的 Widget。

在这个示例中,我们将创建一个显示标题“T”和信息“M”的 MyWidget 示例。

  1. void main() {
  2. testWidgets('MyWidget has a title and message', (WidgetTester tester) async {
  3. // Create the Widget tell the tester to build it
  4. await tester.pumpWidget(MyWidget(title: 'T', message: 'M'));
  5. });
  6. }

备注

初次调用 pumpWidget 之后,WidgetTester 会提供其他方式来重建相同的 Widget。这对使用 StatefulWidget 或者动画会非常有用。

例如,如果我们点击调用 setState 的按钮,在测试环境中,Flutter 并不会自动重建你的 Widget。我们需要用以下列举的方法来让 Flutter 再一次建立我们的 Widget。

    • tester.pump()
    • Triggers a rebuild of the Widget after a given duration.
  • tester.pump():在一段给定时间后重建 Widget。

    • tester.pumpAndSettle()
    • Repeatedly calls pump with the given duration until there are no longer any frames scheduled. This essentially waits for all animations to complete.
  • tester.pumpAndSettle():在给定期间内不断重复调用 pump 直到完成所有绘制帧。一般需要等到所有动画全部完成。这些方法在构建周期中保证细粒度控制,这在测试中非常有用。

五. 使用 Finder 查找 Widget

现在让我们在测试环境中建立 Widget。我们需要用 Finder 通过 Widget 树来查找 标题信息 Text Widgets。这样可以验证这些 Widgets 是否正确显示。

在这个示例中,我们使用 flutter_test 包提供的顶级 find 方法来创建我们的 Finders。因为我们要查找的是 Text widgets,所以可以使用 find.text 方法。

关于 Finderclasses 的更多信息,请参阅 Finding Widgets in a Widget Test 章节。

  1. void main() {
  2. testWidgets('MyWidget has a title and message', (WidgetTester tester) async {
  3. await tester.pumpWidget(MyWidget(title: 'T', message: 'M'));
  4. // Create our Finders
  5. final titleFinder = find.text('T');
  6. final messageFinder = find.text('M');
  7. });
  8. }

六. 使用 Matcher 验证 Widget 是否正常工作

最后,让我们来用 flutter_test 提供的 Matcher 常量验证 Text Widgets 显示的标题和信息。Matcher 类是 test 包里的核心部分,它提供一种通用方法来验证给定值是否符合我们的预期。

在这个示例中,我们要确保 Widget 只在屏幕中出现一次。因此,可以使用 findsOneWidgetMatcher

  1. void main() {
  2. testWidgets('MyWidget has a title and message', (WidgetTester tester) async {
  3. await tester.pumpWidget(MyWidget(title: 'T', message: 'M'));
  4. final titleFinder = find.text('T');
  5. final messageFinder = find.text('M');
  6. // Use the `findsOneWidget` matcher provided by flutter_test to verify our
  7. // Text Widgets appear exactly once in the Widget tree
  8. expect(titleFinder, findsOneWidget);
  9. expect(messageFinder, findsOneWidget);
  10. });
  11. }

其他的 Matchers

除了 findsOneWidgetflutter_test 还为常见情况提供了其他的 matchers。

完整示例:

  1. import 'package:flutter/material.dart';
  2. import 'package:flutter_test/flutter_test.dart';
  3. void main() {
  4. // Define a test. The TestWidgets function will also provide a WidgetTester
  5. // for us to work with. The WidgetTester will allow us to build and interact
  6. // with Widgets in the test environment.
  7. testWidgets('MyWidget has a title and message', (WidgetTester tester) async {
  8. // Create the Widget tell the tester to build it
  9. await tester.pumpWidget(MyWidget(title: 'T', message: 'M'));
  10. // Create our Finders
  11. final titleFinder = find.text('T');
  12. final messageFinder = find.text('M');
  13. // Use the `findsOneWidget` matcher provided by flutter_test to verify our
  14. // Text Widgets appear exactly once in the Widget tree
  15. expect(titleFinder, findsOneWidget);
  16. expect(messageFinder, findsOneWidget);
  17. });
  18. }
  19. class MyWidget extends StatelessWidget {
  20. final String title;
  21. final String message;
  22. const MyWidget({
  23. Key key,
  24. @required this.title,
  25. @required this.message,
  26. }) : super(key: key);
  27. @override
  28. Widget build(BuildContext context) {
  29. return MaterialApp(
  30. title: 'Flutter Demo',
  31. home: Scaffold(
  32. appBar: AppBar(
  33. title: Text(title),
  34. ),
  35. body: Center(
  36. child: Text(message),
  37. ),
  38. ),
  39. );
  40. }
  41. }