级别:★☆☆☆☆
标签:「Flutter Test」「Flutter 单元测试」「Flutter Widget Test」
作者: ITWYW
审校: aTaller团队
前言
笔者最近看了一些关于 Flutter Test 的内容,会整理分享约3篇文章。本文是第一篇,笔者会在本文中介绍Flutter Test 的基本概念,集成并使用 Flutter Test的方式,Flutter 单元测试的基础知识,Flutter Widget Test 的基础知识等。
零、Flutter Test 基础概念
Flutter Test基础概念的内容摘抄自 测试 Flutter App 介绍
1. 单元测试
单元测试:测试单一功能、方法或类。
例如,被测单元的外部依赖性通常被模拟出来,如
package:mockito
。 单元测试通常不会读取/写入磁盘、渲染到屏幕,也不会从运行测试的进程外部接收用户操作。单元测试的目标是在各种条件下验证逻辑单元的正确性。
2. Widget 测试
widget 测试:(在其它UI框架称为 组件测试) 测试的单个widget。
测试widget涉及多个类,并且需要提供适当的widget生命周期上下文的测试环境。 例如,它应该能够接收和响应用户操作和事件,执行布局并实例化子widget。widget测试因此比单元测试更全面。 然而,就像一个单元测试一样,一个widget测试的环境被一个比完整的UI系统简单得多的实现所取代。
小部件测试的目标是验证小部件的UI如预期的那样的外观和交互。
3. 集成测试
集成测试: 测试一个完整的应用程序或应用程序的很大一部分。
通常,集成测试可以在真实设备或OS仿真器上运行,例如iOS Simulator或Android Emulator。 被测试的应用程序通常与测试驱动程序代码隔离,以避免结果偏差。
集成测试的目标是验证应用程序作为一个整体正确运行,它所组成的所有widget如预期的那样相互集成。 您还可以使用集成测试来验证应用的性能。
下边笔者会以 Flutter 单元测试为例,分享下 集成并使用 Flutter Test的方式。
一、集成并使用 Flutter Test
1. Flutter 单元测试集成方式
在 pubspec.yaml 文件中添加如下配置,并保存,在终端执行 flutter pub get
dev_dependencies:
flutter_test:
sdk: flutter
上述配置,flutter_test 是创建 项目之后,默认包含的配置,flutter_test 依赖是用于 单元测试、Widget Test 的依赖。
2. 执行单元测试、Widget 测试的方式
执行单元测试可直接在终端执行如下命令:
2.1 flutter test 命令
flutter test
flutter test 文件目录../文件名.dart
2.2 flutter test 使用示例
使用如下的命令,执行单元测试或 Widget 测试。flutter test 默认测试的文件为伴随项目创建一起生成的 widget_test.dart 文件。flutter test + 待测试文件的目录/待测试文件 用于测试指定文件。
wangyongwangdeiMac:flutter_tutorial wangyongwang$ flutter test test/widget_test.dart
00:03 +15: All tests passed!
wangyongwangdeiMac:flutter_tutorial wangyongwang$ flutter test
00:02 +15: All tests passed!
wangyongwangdeiMac:test_demo wangyongwang$ flutter test
00:08 +1: All tests passed!
3. 单元测试、Widget 测试基础 API
3.1 test API 基本使用
// 引入 flutter_test.dart
import 'package:flutter_test/flutter_test.dart';
// test
test('测试描述信息', (){
// 待测试代码
});
// 如果多个待测试内容直接有关联,可以考虑使用 group API 把多个测试整合在一起。
group('groupTest', () {
test('test1', () {
// 待测试代码
});
test('test2', () {
// test2 和 test1 之间存在某些关联
});
});
// Widget Test 应用场景:在 Widget树中查找子 Widget 是否存在、读取文本、验证 Widget 属性的值是否正确。
testWidgets('测试描述', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: App.MyApp(),
),
);
final appBar = find.byKey(Key(App.HomeAppBarKeyString));
expect(appBar, findsOneWidget);
});
3.2 test API 示例
使用test API,在test API 的匿名函数回调中执行待测试代码。
test('my first unit test', () {
var answer = 68;
// 单元测试通过
expect(answer, 68);
// 单元测试失败
// expect(answer, 77);
});
上述使用expect,用于断言第一个参数和第二个参数要匹配。匹配的情况下,测试就会通过,不匹配的情况下,测试就会失败。
我们可以简单看下 expect 这个API
/// Assert that `actual` matches `matcher`.
///
/// See [test_package.expect] for details. This is a variant of that function
/// that additionally verifies that there are no asynchronous APIs
/// that have not yet resolved.
///
/// See also:
///
/// * [expectLater] for use with asynchronous matchers.
void expect(
dynamic actual,
dynamic matcher, {
String reason,
dynamic skip, // true or a String
}) {
TestAsyncUtils.guardSync();
test_package.expect(actual, matcher, reason: reason, skip: skip);
}
目前笔者只使用到了前2个参数。
值得注意的是 expectLater
这个方法,expectLater
使用场景为:异步网络请求情况的断言。
下边笔者举一个异步网络请求的测试的例子。
import 'package:dio/dio.dart';
class QiNetwork extends Object {
static Future<Response> request({String urlString}) async {
Response response = await Dio().get(urlString);
return response;
}
}
import 'package:flutter_tutorial/testSourceCode/qi_network.dart';
Future testNetwork() async {
Response response;
test('测试网络请求', () async {
String testURLString = 'https://api.github.com/orgs/flutterchina/repos';
await QiNetwork.request(urlString: testURLString).then((value) {
response = value;
expectLater(response.statusCode, 200);
List responseList = response.data;
Map responseFirstListMap = responseList.first;
print(responseFirstListMap['name']);
expect(responseFirstListMap['name'], 'dio');
});
});
}
void main() async {
await testNetwork();
}
测试通过输出信息如下
wangyongwangdeiMac:flutter_tutorial wangyongwang$ flutter test
00:01 +0: ... 测试网络请求 00:02 +0: ... 测试网络请求 00:02 +1: ... 测试网络请求 00:03 +1: ... 测试网络请求 00:03 +1: /Users/wangyongwang/Documents/GitHub/aTaller/FlutterLearningRecord/flutter_tutorial/test/widget_test.dart: 测试网络请求
dio
00:03 +2: ... 测试网络请求 00:03 +2: All tests passed!
3.3 其他测试方式
除了使用 flutter test 还可以点击 test API 上方的 Run ,z可以直接对指定的某一个test API 进行测试。

3.4 group API 示例
下边笔者把关于字符串相关的操作的 test 放在了同一个 group 方法中。这里笔者的目的是根据字符串相关的这种关联,把相关 test 放大了同一个 group。
group('String', () {
test('.split() splits the string on the delimiter', () {
var string = 'foo,bar,baz';
expect(string.split(','), equals(['foo', 'bar', 'baz']));
});
test('.trim() removes surrounding whitespace', () {
var string = ' foo ';
expect(string.trim(), equals('foo'));
});
});
笔者后来看到了官方文档的实例,才发现官方文档给出的实例的关联更合理。
笔者简单修改了下,官方提供的 group 的示例,代码如下:
class QiCounter {
int countValue = 0;
int increment() {
return ++countValue;
}
int decrease() {
return --countValue;
}
}
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_tutorial/testSourceCode/qi_counter.dart';
void testCounter() {
test('testCounter', () {
final counter = QiCounter();
expect(counter.countValue, 0);
expect(counter.increment(), 1);
expect(counter.increment(), 2);
expect(counter.decrease(), 1);
});
}
void testGroupCounter() {
group("Counter Group Test", () {
test('Counter初始化', () {
final counter = QiCounter();
expect(counter.countValue, 0);
});
test('Counter Increment', () {
final counter = QiCounter();
expect(counter.increment(), 1);
expect(counter.countValue, 1);
});
test('Counter Increment', () {
final counter = QiCounter();
expect(counter.increment(), 1);
expect(counter.decrease(), 0);
expect(counter.countValue, 0);
expect(counter.decrease(), -1);
expect(counter.countValue, -1);
});
});
test('testCounter', () {
final counter = QiCounter();
expect(counter.countValue, 0);
expect(counter.increment(), 1);
expect(counter.increment(), 2);
expect(counter.decrease(), 1);
});
}
void main() async {
testCounter();
testGroupCounter();
}
上述代码的测试结果为:
wangyongwangdeiMac:flutter_tutorial wangyongwang$ flutter test
00:02 +1: ... Counter Group Test Counter初始化 00:03 +6: All tests passed!
3.5 更多 test 示例
更多 test 的示例,笔者参照着做了一些常识,大家有需要去 FlutterLearningRecord widget_test.dart 查看。
3.6 Widget Test API
Widget Test 使用的API 为 testWidgets,同样,第一个参数为描述测试信息,第二个参数为一个异步匿名函数。这个匿名函数带有一个WidgetTester 类型的参数tester,我们可以使用 tester.pumpWidget 指定要对哪个页面进行测试,可以使用find.byKey 获取 Widget 的 finder,之后可以使用 expect 断言会找到一个 Widget(findsOneWidget),或者断言找不到相应的 Widget(findsNothing)。
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_tutorial/main.dart' as App;
void main() {
testWidgets("My First Widget ", (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: App.MyApp(),
),
);
final appBar = find.byKey(Key(App.HomeAppBarKeyString));
expect(appBar, findsOneWidget);
final appBarNone = find.byKey(Key('noneKey'));
expect(appBarNone, findsNothing);
// expect(appBarNone, findsOneWidget);
});
}
main.dart 中的代码比较多,笔者就不贴出来了,有需要的话,大家去 FlutterLearningRecord widget_test.dart 查看即可。
二、 Demo
三、参考学习网址
关注我们的途径有:
aTaller(简书)
aTaller(掘金)
aTaller(微信公众号)
推荐文章:
Flutte 开发小技巧
Flutter 常用 Widget 介绍
Flutter 图片加载
Flutter 混合栈复用原理
Flutter Platform Channel 使用与源码分析
Flutter Platform View 使用及原理简析
奇舞周刊
网友评论