跳到主內容

Widget 測試簡介

瞭解更多關於 Flutter 中的 Widget 測試。

單元測試簡介教程中,你學習瞭如何使用 test 包來測試 Dart 類。要測試 Widget 類,你需要使用 Flutter SDK 自帶的 flutter_test 包所提供的額外工具。

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 依賴

#

在編寫測試之前,請將 flutter_test 依賴項包含在 pubspec.yaml 檔案的 dev_dependencies 部分中。如果使用命令列工具或程式碼編輯器建立一個新的 Flutter 專案,此依賴項通常已經配置好了。

yaml
dev_dependencies:
  flutter_test:
    sdk: flutter

2. 建立用於測試的 Widget

#

接下來,建立一個用於測試的 Widget。在本教程中,建立一個顯示 title(標題)和 message(訊息)的 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 樹中搜索 titlemessageText 元件。這可以驗證 Widget 是否顯示正確。

為此,請使用 flutter_test 包提供的頂層 find() 方法來建立 Finders。既然你知道要尋找的是 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 元件。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 還為常見情況提供了其他匹配器。

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)),
      ),
    );
  }
}