Widget 測試簡介
瞭解更多關於 Flutter 中的 Widget 測試。
在單元測試簡介教程中,你學習瞭如何使用 test 包來測試 Dart 類。要測試 Widget 類,你需要使用 Flutter SDK 自帶的 flutter_test 包所提供的額外工具。
flutter_test 包為測試 Widget 提供了以下工具:
WidgetTester允許在測試環境中構建 Widget 並與之互動。testWidgets()函式會自動為每個測試用例建立一個新的WidgetTester,它是普通test()函式的替代品。Finder類允許在測試環境中搜索 Widget。- Widget 特有的
Matcher常量有助於驗證Finder是否在測試環境中找到了一個或多個 Widget。
如果這聽起來很複雜,請不要擔心。透過本教程,你將瞭解所有這些部分是如何組合在一起的,我們將遵循以下步驟:
- 新增
flutter_test依賴。 - 建立一個用於測試的 Widget。
- 建立一個
testWidgets測試。 - 使用
WidgetTester構建 Widget。 - 使用
Finder搜尋 Widget。 - 使用
Matcher驗證 Widget。
1. 新增 flutter_test 依賴
#
在編寫測試之前,請將 flutter_test 依賴項包含在 pubspec.yaml 檔案的 dev_dependencies 部分中。如果使用命令列工具或程式碼編輯器建立一個新的 Flutter 專案,此依賴項通常已經配置好了。
dev_dependencies:
flutter_test:
sdk: flutter
2. 建立用於測試的 Widget
#接下來,建立一個用於測試的 Widget。在本教程中,建立一個顯示 title(標題)和 message(訊息)的 Widget。
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 是否顯示了給定的標題和訊息。它的標題已相應命名,具體實現將在下一節中補充。
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() 方法在測試環境中構建 MyWidget。pumpWidget 方法會構建並渲染所提供的 Widget。
建立一個 MyWidget 例項,將“T”顯示為標題,將“M”顯示為訊息。
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 樹中搜索 title 和 message 的 Text 元件。這可以驗證 Widget 是否顯示正確。
為此,請使用 flutter_test 包提供的頂層 find() 方法來建立 Finders。既然你知道要尋找的是 Text Widget,請使用 find.text() 方法。
有關 Finder 類的更多資訊,請參閱在 Widget 測試中查詢 Widget 教程。
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。
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 的渲染是否與特定的點陣圖影像匹配(“金標/基準”測試)。
完整示例
#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)),
),
);
}
}