單元測試簡介食譜中,您學習瞭如何使用 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 顯示了指定的 title 和 message。它將被相應地命名,並在下一節中進行填充。

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" 作為 title 和 "M" 作為 message。

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() 方法來建立 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 常量來驗證 title 和 message 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 的渲染是否與特定的點陣圖影像("golden file" 測試)匹配。

完整示例

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