翻译首页
许多我们build的Widget不仅仅显示信息,也响应用户交互。包括可点击的按钮,在屏幕拖动控件,或者在文本框里输入文本。
为了测试那些互动,我们需要一种在测试环境模拟它们的方法。为了这么做,我们需要使用flutter_test
类库的WidgetTester
类。
这个WidgetTester
提供了输入文本,点击,拖拽的方法。
- enterText
- tap
- drag
在很多情况下,用户交互将会更新我们app的状态。在测试环境里,在状态改变后Flutter不会自动重新构建Widgets。为了确保我们的Widget树在我们模拟用户交互以后重新build,我们必须调用WidgetTester
提供的pump
或者pumpAndSettle
方法。
步骤:
- 创建一个Widget用于测试
- 在输入框里输入文本
- 确保点击按钮会添加todo
- 确保滑动删除todo
1. 创建一个Widget用于测试
在这个例子里,我们将会创建一个基础的todo app。它将会有3个需要我们测试的主要功能:
- 在
TextField
里输入文本 - 点击
FloatingActionButton
按钮在todo列表里添加文本 - 滑动从列表中删除一个item
为了保持焦点在测试上,这个例子将不会提供详细的构建todo app的界面。如果想学习更多关于如何构建app,请查看以下相关文章:
- Create and style a text field
- Handling Taps
- Create a basic list
- Implement Swipe to Dismiss
class TodoList extends StatefulWidget {
@override
_TodoListState createState() => _TodoListState();
}
class _TodoListState extends State {
static const _appTitle = 'Todo List';
final todos = [];
final controller = TextEditingController();
@override
Widget build(BuildContext context) {
return MaterialApp(
title: _appTitle,
home: Scaffold(
appBar: AppBar(
title: Text(_appTitle),
),
body: Column(
children: [
TextField(
controller: controller,
),
Expanded(
child: ListView.builder(
itemCount: todos.length,
itemBuilder: (BuildContext context, int index) {
final todo = todos[index];
return Dismissible(
key: Key('$todo$index'),
onDismissed: (direction) => todos.removeAt(index),
child: ListTile(title: Text(todo)),
background: Container(color: Colors.red),
);
},
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
todos.add(controller.text);
controller.clear();
});
},
child: Icon(Icons.add),
),
),
);
}
}
复制代码
2. 在输入框里输入文本
现在我们有了一个todo app,我们可以开始写我们的测试了!在这个示例里,我们将会从输入文本到TextField
开始。
我们可以这样完成任务:
- 在测试环境build一个Widget
- 使用
WidgetTester
的enterText
方法
testWidgets('Add and remove a todo', (WidgetTester tester) async {
// Build the Widget
await tester.pumpWidget(TodoList());
// Enter 'hi' into the TextField
await tester.enterText(find.byType(TextField), 'hi');
});
复制代码
注意: 这段代码基于上一段测试的代码。如果想学习Widget测试的核心概念,请查看下面的文章:
- Widget测试介绍
- 使用Finder查找child widget
3. 确保点击按钮会添加todo
当我们在TextField
里输入文本之后,我们希望点击FloatingActionButton
会添加 item 到列表里。
这些步骤包括3步:
- 使用
tap
方法点击按钮。 - 状态改变后使用
pump
方法重新build Widget。 - 确保 item 出现在屏幕的列表里。
testWidgets('Add and remove a todo', (WidgetTester tester) async {
// Enter text code...
// Tap the add button
await tester.tap(find.byType(FloatingActionButton));
// Rebuild the Widget after the state has changed
await tester.pump();
// Expect to find the item on screen
expect(find.text('hi'), findsOneWidget);
});
复制代码
3. 滑动从列表中删除一个item
最后,我们要确保在滑动删除一条todo后可以从列表中删除它。这将包括以下3个步骤:
- 使用
drag
方法执行滑动删除操作。 - 使用
pumpAndSettle
方法不断的重新build我们的Widget树直到dismiss动画完成。 - 确保 item 从屏幕中消失。
testWidgets('Add and remove a todo', (WidgetTester tester) async {
// Enter text and add the item...
// Swipe the item to dismiss it
await tester.drag(find.byType(Dismissible), Offset(500.0, 0.0));
// Build the Widget until the dismiss animation ends
await tester.pumpAndSettle();
// Ensure the item is no longer on screen
expect(find.text('hi'), findsNothing);
});
复制代码
完整代码:
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Add and remove a todo', (WidgetTester tester) async {
// Build the Widget
await tester.pumpWidget(TodoList());
// Enter 'hi' into the TextField
await tester.enterText(find.byType(TextField), 'hi');
// Tap the add button
await tester.tap(find.byType(FloatingActionButton));
// Rebuild the Widget with the new item
await tester.pump();
// Expect to find the item on screen
expect(find.text('hi'), findsOneWidget);
// Swipe the item to dismiss it
await tester.drag(find.byType(Dismissible), Offset(500.0, 0.0));
// Build the Widget until the dismiss animation ends
await tester.pumpAndSettle();
// Ensure the item is no longer on screen
expect(find.text('hi'), findsNothing);
});
}
class TodoList extends StatefulWidget {
@override
_TodoListState createState() => _TodoListState();
}
class _TodoListState extends State {
static const _appTitle = 'Todo List';
final todos = [];
final controller = TextEditingController();
@override
Widget build(BuildContext context) {
return MaterialApp(
title: _appTitle,
home: Scaffold(
appBar: AppBar(
title: Text(_appTitle),
),
body: Column(
children: [
TextField(
controller: controller,
),
Expanded(
child: ListView.builder(
itemCount: todos.length,
itemBuilder: (BuildContext context, int index) {
final todo = todos[index];
return Dismissible(
key: Key('$todo$index'),
onDismissed: (direction) => todos.removeAt(index),
child: ListTile(title: Text(todo)),
background: Container(color: Colors.red),
);
},
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
todos.add(controller.text);
controller.clear();
});
},
child: Icon(Icons.add),
),
),
);
}
}
复制代码