如 官方文档 介绍,Flutter 的自动化测试分为三类:单元测试,Widget测试,集成测试。这三种测试各维度的对比:
单元测试 | Widget 测试 | 集成测试 | |
---|---|---|---|
可信度 | 低 | 中 | 最高 |
维护成本 | 低 | 中 | 最高 |
依赖 | 少 | 中 | 最多 |
执行速度 | 快 | 快 | 最慢 |
Talk is cheap, show me the code
单元测试
单元测试,需要添加依赖:
1
2
3
dev_dependencies:
test: any
mockito: any # 实际开发中建议指定最新版本
一般是测试一个类或某个方法的逻辑,外部依赖一般以 Mock 来实现:
1
2
3
4
5
6
7
8
9
10
11
12
class MockClient extends Mock implements http.Client{}
main() {
group('fetchPost',() {
final client = MockClient();
final url = "https://some.url.com/get/personal/info";
when(client.get(url)).thenAnswer((_) async => http.Response('{"name":"Tester","age":"20"}',200));
// 假设 fetchPersonlaInfo 是个通过网络请求用户信息,并把 json 组装成 User 的函数
expect(await fetchPesonalInfo(client), isA<User>());
})
}
跑单测:
1
dart test/xxx_test.dart
Widget 测试
Widget test 需要使用 flutter_test 包,此包已经随 flutter 发布 注意:flutter_test 与 test 有雷同函数,如果需要同时使用两个包,需要给其中一个使用别名。
flutter_test 提供以下功能:
WidgetTester
: 构建及与Widget 进行交互testWidgets()
: 创建 WidgetTesterFinder
: 搜索 widget (根据不同类型)Matcher
: Widget 特有的 Matcher,验证 widget 是否存在
1
2
3
4
5
6
7
8
void main() {
testWidgets("widget should have title", (WidgetTester tester) async {
final titleString = 'Title'
await tester.pumpWidget(MyWidget(title: titleString));
final titleFinder = find.text(titleString);
expect(titleFinder, findsOneWidget); # findsNothing findsWidgets findsNWidgets
});
}
需要注意的是 pumpWidget 构建的 widget 并不会因为 setState 而被调用。如果做了某些交互操作(比如点击:tester.tap(find.text('button'))
,需要手动调用 tester.pump 来触发刷新,如果涉及到动画,则要调用 tester.pumpAndSettle()
)
通过 WidgetTester ,可以在指定 widget 或坐标上执行各种交互操作,然后验证 Widget 的变化。
集成测试
真机或虚拟机上进行的测试。需要使用 flutter_driver
包。
集成测试默认会运行 test_driver/app.dart ,一般此文件如下:
1
2
3
4
5
6
7
8
9
10
11
import 'package:flutter_driver/driver_extension.dart';
import 'package:counter_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();
}
测试逻辑(test_deriver 目录下的文件):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void main() {
group('xxx', (){
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 {
driver?.close();
});
test('some test', () async {
expect(await driver.getText(find.byValueKey('button')), "0");
});
});
}
运行集成测试:
1
2
3
4
5
flutter drive --target=test_driver/app.dart # mobile
# web, 除 safari 外,需安装浏览器对应的 driver chromeDriver/GeckoDriver/Edge
./chromedriver --port=4444
flutter drive --target=test_driver/app.dart --browser-name=chromedriver --release
通过 FlutterDriver
可执行各种交互操作,如,滚动列表,点击等。另外,FlutterDriver 也可以用于性能测试,详见官网intergration-profile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Record a performance timeline as the app scrolls through the list of items.
final timeline = await driver.traceAction(() async {
await driver.scrollUntilVisible(
listFinder,
itemFinder,
dyScroll: -300.0,
);
expect(await driver.getText(itemFinder), 'Item 50');
});
// Convert the Timeline into a TimelineSummary that's easier to
// read and understand.
final summary = new TimelineSummary.summarize(timeline);
// Then, save the summary to disk.
await summary.writeSummaryToFile('scrolling_summary', pretty: true);
// Optionally, write the entire timeline to disk in a json format.
// This file can be opened in the Chrome browser's tracing tools
// found by navigating to chrome://tracing.
await summary.writeTimelineToFile('scrolling_timeline', pretty: true);