不论是 Email 客户端还是音乐应用,绝大多数 app 都使用到列表来展示内容。我们期望使用集成测试来验证列表中的内容,并需要一种方法去滚动列表来查找特定的项。
为了在集成测试中检验滚动列表,我们可以使用 flutter_driver
这个 package 中的 FlutterDriver
类:
在本章节,我们将学习如何在滚动列表中验证是否正在显示特定的 Widget,并讨论不同方法的优缺点。
本教程包含以下步骤:
1. 创建带有列表的 app 2. 测试 app 3. 编写列表滚动的测试用例 4. 运行测试1. 创建带有列表的 app
在本章节,我们创建一个带有长列表的 app。为了能够在本章节中专注于测试,我们将使用在 如何在 Flutter 上创建长列表 文章中创建的 app。如果你不确定如何处理内容列表,请自行查看相关章节的介绍。
正如我们在 集成测试简介 文章中做的那样,我们还将向集成测试内我们需要互动的 widget 添加 key。
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp(
items: List<String>.generate(10000, (i) => "Item $i"),
));
}
class MyApp extends StatelessWidget {
final List<String> items;
MyApp({Key key, @required this.items}) : super(key: key);
@override
Widget build(BuildContext context) {
final title = 'Long List';
return MaterialApp(
title: title,
home: Scaffold(
appBar: AppBar(
title: Text(title),
),
body: ListView.builder(
// Add a key to the ListView. This makes it possible to
// find the list and scroll through it in the tests.
key: Key('long_list'),
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(
'${items[index]}',
// Add a key to the Text widget for each item. This makes
// it possible to look for a particular item in the list
// and verify that the text is correct
key: Key('item_${index}_text'),
),
);
},
),
),
);
}
}
2. 测试 app
接下来,我们需要创建 app 的测试版本,这段代码位于 test_driver/app.dart
文件中。
import 'package:flutter_driver/driver_extension.dart';
import 'package:scrollable_app/main.dart' as app;
void main() {
// This line enables the extension.
enableFlutterDriverExtension();
// Call the `main()` function of the app or call `runApp` with
// any widget you are interested in testing.
app.main();
}
3. 编写列表滚动的测试用例
现在,我们可以编写我们的测试用例了!在这个例子中,我们需要滚动列表并校验特定的列表项是否存在于列表中。FlutterDriver
类为我们的滚动列表提供了三个方法:
scroll()
方法允许我们按给定的数量滚动特定的列表。scrollIntoView()
方法找到已经被渲染的特定的 Widget,并将它完全滚动到视图中。某些 Widget,比如ListView.builder
,只有在将要显示的时候才会去渲染列表项。sc
rollUntilVisible()
方法会滚动列表直到特定的 Widget 显示出来。
scrollUntilVisible
方法通常来说是最优的方式,为什么呢?
(1)如果只使用 scroll()
方法,我们可能错误地假定列表中每一项的高度,这可能导致滚动的太多或太少。
(2)如果使用 scrollIntoView()
方法,我们假定 Widget 已被实例化和渲染。为了验证 app 在不同的设备了能够很好的运行,我们可以对具有不同屏幕大小的设备运行集成测试。因为 ListView.builder
是只有在需要的时候才会渲染列表项,所以是否渲染特定的 Widget 取决于屏幕的大小。
所以,我们既不需要知道所有列表项的高度,也不需要知道一个特定的 Widget 在不同的屏幕大小的设备上是否被渲染,我们只需要调用 scrollUntilVisible()
方法反复滚动列表直到找到要查找的列表项。
让我们看一下如何通过 scrollUntilVisible()
方法去寻找列表中特定的一项,这段代码位于 test_driver/app_test.dart
文件中。
// Imports the Flutter Driver API.
import 'package:flutter_driver/flutter_driver.dart';
import 'package:test/test.dart';
void main() {
group('Scrollable App', () {
FlutterDriver driver;
// Connect to the Flutter driver before running any tests.
setUpAll(() async {
driver = await FlutterDriver.connect();
});
// Close the connection to the driver after the tests have completed.
tearDownAll(() async {
if (driver != null) {
await driver.close();
}
});
test('verifies the list contains a specific item', () async {
// Create two SerializableFinders and use these to locate specific
// widgets displayed by the app. The names provided to the byValueKey
// method correspond to the Keys provided to the widgets in step 1.
final listFinder = find.byValueKey('long_list');
final itemFinder = find.byValueKey('item_50_text');
await driver.scrollUntilVisible(
// Scroll through the list
listFinder,
// Until finding this item
itemFinder,
// To scroll down the list, provide a negative value to dyScroll.
// Ensure that this value is a small enough increment to
// scroll the item into view without potentially scrolling past it.
//
// To scroll through horizontal lists, provide a dxScroll
// property instead.
dyScroll: -300.0,
);
// Verify that the item contains the correct text.
expect(
await driver.getText(itemFinder),
'Item 50',
);
});
});
}
4. 运行测试
我们在项目根目录下使用以下命令来运行测试:flutter drive --target=test_driver/app.dart