Widget 测试简介
在单元测试简介教程中,你学习了如何使用 test
包来测试 Dart 类。要测试小部件类,你需要 flutter_test
包提供的一些额外工具,该包随 Flutter SDK 一同提供。
flutter_test
包提供了以下用于测试小部件的工具:
WidgetTester
允许在测试环境中构建小部件并与其交互。testWidgets()
函数会自动为每个测试用例创建一个新的WidgetTester
,并用于替代普通的test()
函数。Finder
类允许在测试环境中查找小部件。- 小部件特有的
Matcher
常量有助于验证Finder
是否在测试环境中找到了一个或多个小部件。
如果这听起来让人不知所措,请不要担心。通过本教程,你将了解所有这些部分是如何协同工作的,本教程将采用以下步骤:
- 添加
flutter_test
依赖。 - 创建一个待测试的小部件。
- 创建一个
testWidgets
测试。 - 使用
WidgetTester
构建小部件。 - 使用
Finder
查找小部件。 - 使用
Matcher
验证小部件。
1. 添加 flutter_test
依赖
#在编写测试之前,请将 flutter_test
依赖项包含在 pubspec.yaml
文件的 dev_dependencies
部分中。如果你使用命令行工具或代码编辑器创建新的 Flutter 项目,此依赖项应该已经存在。
dev_dependencies:
flutter_test:
sdk: flutter
2. 创建一个待测试的小部件
#接下来,创建一个用于测试的小部件。在本教程中,创建一个显示 title
和 message
的小部件。
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
测试
#有了待测试的小部件后,就可以开始编写第一个测试了。使用 flutter_test
包提供的 testWidgets()
函数来定义测试。testWidgets
函数允许你定义小部件测试并创建一个 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
构建小部件
#接下来,使用 WidgetTester
提供的 pumpWidget()
方法在测试环境中构建 MyWidget
。pumpWidget
方法构建并渲染提供的小部件。
创建一个 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
提供了其他方法来重建相同的小部件。如果你正在处理 StatefulWidget
或动画,这会很有用。
例如,点击按钮会调用 setState()
,但 Flutter 不会自动在测试环境中重建你的小部件。使用以下方法之一来请求 Flutter 重建小部件。
tester.pump(Duration duration)
- 安排一个帧并触发小部件的重建。如果指定了
Duration
,它会按该时长推进时钟并安排一个帧。即使持续时间长于一个帧,它也不会安排多个帧。
tester.pumpAndSettle()
- 反复调用
pump()
并指定给定持续时间,直到不再有任何帧被安排。这实质上是等待所有动画完成。
这些方法提供了对构建生命周期的细粒度控制,这在测试时特别有用。
5. 使用 Finder
查找小部件
#在测试环境中有了小部件后,使用 Finder
在小部件树中搜索 title
和 message
文本小部件。这可以验证小部件是否正确显示。
为此,使用 flutter_test
包提供的顶级 find()
方法来创建 Finders
。由于你知道你正在查找 Text
小部件,因此请使用 find.text()
方法。
有关 Finder
类的更多信息,请参阅在小部件测试中查找小部件教程。
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
验证小部件
#最后,使用 flutter_test
提供的 Matcher
常量验证标题和消息 Text
小部件是否出现在屏幕上。Matcher
类是 test
包的核心部分,并提供了一种验证给定值是否符合预期的通用方法。
确保小部件在屏幕上仅出现一次。为此,使用 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
- 验证没有找到任何小部件。
findsWidgets
- 验证找到一个或多个小部件。
findsNWidgets
- 验证找到特定数量的小部件。
matchesGoldenFile
- 验证小部件的渲染与特定的位图图像匹配(“黄金文件”测试)。
完整示例
#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)),
),
);
}
}