单元测试
Flutter apps test 分为 unittest、widget test、integration test
-
unittest
-
进阶参考:pub.dev/packages/te…
-
以上两个建议都看,很快就能看完
-
Integration Testing:docs.flutter.dev/testing/int…
-
添加依赖库
-
dev_dependencies: test: <latest_version>
- latest_version 代表版本,版本查看地址如下:
-
-
点击复制按钮,出现 提示,转移到目标yaml文件中粘贴即可:
-
-
使用时注意提示:
The current Dart SDK version is 2.17.0. Because my_flutter_driver depends on test >=1.21.5 which requires SDK version >=2.18.0-146.0.dev <3.0.0, version solving failed. pub get failed (1; Because my_flutter_driver depends on test >=1.21.5 which requires SDK version >=2.18.0-146.0.dev <3.0.0, version solving failed.)
我最终用了1.21.0:
-
-
-
需要有两个文件,目标文件,和执行测试文件。
-
counter.dart 是要被测试的目标文件,counter_test.dart 是执行测试的文件。
-
class Counter { int value = 0; void increment() => value++; void decrement() => value--; }
-
-
In this example, create two files: counter.dart and counter_test.dart.
The counter.dart file contains a class that you want to test and resides in the lib folder. The counter_test.dart file contains the tests themselves and lives inside the test folder.
In general, test files should reside inside a test folder located at the root of your Flutter application or package. Test files should always end with _test.dart, this is the convention used by the test runner when searching for tests.
When you’re finished, the folder structure should look like this:
-
意思是,我们应该在lib文件夹下创建目标文件counter.dart,在test文件夹下,创建所有的要执行测试的文件
counter_app/ lib/ counter.dart test/ counter_test.dart -
test 文件夹是自动生成的,或许flutter项目创建时,也或许是test被引入时。
-
-
在执行测试文件中:
import 'package:test/test.dart'; -
在我的flutter项目中并未找到,我用如下代替:
-
import 'package:flutter_test/flutter_test.dart';
-
-
命令行运行:
/Users/qingweiliu/Library/flutter/bin/flutter --no-color test --machine --start-paused --plain-name description test/counter_test.dart -
-
右键直接运行:
-
-
测试通过后:
- 左侧框是测试对象信息,右侧是测试通过的梳理。
上述写法其实是有错误的(但是官方demo里就是这么写的,不知现在更新了没有),只要不出现变编译问题,测试结果都是通过,我们修改如下:
test('description', (){
final counter = Counter();
counter.increment();
expect(counter.value, 2, reason: "start at 1 返回值错误");
});
重新运行看结果:错误的提示信息也已打印出来 "sart at 1 返回值错误"
-
Combine multiple tests in a group:同时进行多项测试(一个测试失败不影响下一个继续)
-
注意 test.dart 引用失败用flutter_test(import 'package:flutter_test/flutter_test.dart';)
-
import 'package:counter_app/counter.dart'; import 'package:test/test.dart'; void main() { group('Counter', () { test('value should start at 0', () { expect(Counter().value, 0); }); test('value should be incremented', () { final counter = Counter(); counter.increment(); expect(counter.value, 1); }); test('value should be decremented', () { final counter = Counter(); counter.decrement(); expect(counter.value, -1); }); }); } -
注意以下三种写法的问题:第三种写法,不会爆出异常,而是测试通过。
-
group('Counter', () { test('value should start at 0', () => { expect(Counter().value, 0, reason: "start at 0 返回值错误") }); test('value should be incremented', () { final counter = Counter(); counter.increment(); expect(counter.value, 1); }); test('value should be decremented', () => (){ final counter = Counter(); counter.decrement(); expect(counter.value, 20, reason: "返回值错误"); }); }); -
IDE运行:右侧 Run 'Counter' 或者点击左侧运行按钮
- Tests faile:1,passed:2, 测试通过2个,有一个case测试失败,未达到预期。左下角是每一个case 测试情况。差号的case 就是没通过。
-
-
命令行执行测试文件 .dart
- flutter test test/counter_test.dart
- 会将main() 方法里面的四个test都走一遍,这个时候,不需要group,四个test并列也可以了。
-
import 'package:flutter_test/flutter_test.dart'; import '../lib/counter.dart'; void main() { test('description', () => (){ final counter = Counter(); counter.increment(); expect(counter.value, 1); }); group('Counter', () { test('value should start at 0', () => { expect(Counter().value, 0, reason: "start at 0 返回值错误") }); test('value should be incremented', () { final counter = Counter(); counter.increment(); expect(counter.value, 1); }); test('value should be decremented', () => (){ final counter = Counter(); counter.decrement(); expect(counter.value, 20, reason: "返回值错误"); }); }); } - 命令行运行后效果:
- 最终日志意思是:3个测试成功,2个测试失败。
-
指定测试文件中的description执行测试
-
flutter --no-color test --machine --start-paused --plain-name Counter test/counter_test.dart -
--plain-name:这个命令行,只会执行counter_test.dart 里面的 group('Counter') 的测试。
-
测试尝试如下:测试通过2例,1个case测试未通过。
-
Widgets_Tests
参考文档:docs.flutter.dev/cookbook/te…
测试某个widget:
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const MyApp());
});
相较于test,testWidgets多了tester,允许对widget进行更多操作。
pumpWidget:
对目标widget进行重建
tester.pump();//立即刷新
pump(Duration duration): 刷新tester里面的东西,比如刷新widget
官方解释:
Repeatedly calls pump() with the given duration until there are no longer any frames scheduled. This, essentially, waits for all animations to complete.
个人感觉上面这句话比代码注释里解释的要好。
tester.pumpAndSettle();//刷新所有;比如多个widget、frames等
查找WidgetsTree里面某个widget:
testWidgets('MyWidget has a title and message', (tester) async {
await tester.pumpWidget(const MyWidget(title: 'T', message: 'M'));
final titleFinder = find.text('T');
expect(titleFinder, findsOneWidget);//有且仅有一个目标widget
expect(titleFinder, findsNothing);//一个都没有
expect(titleFinder, findsWidgets);//有一个或者多个
expect(titleFinder, findsNWidgets(10));//有10个
expect(find.text('0'), matchesGoldenFile(key, version: 10));//
}
matchesGoldenFile:
matchesGoldenFile(key, version: 10):Verifies that a widget’s rendering matches a particular bitmap image (“golden file” testing).
/// The [key] may be either a [Uri] or a [String] representation of a URL.
/// The [version] is a number that can be used to differentiate historical
/// golden files. This parameter is optional.
matchesGoldenFile 一般与expectLater 搭配。
/// await expectLater(
/// find.text('Save'),
/// matchesGoldenFile('save.png'),
/// );
/// await expectLater(
/// find.byType(MyWidget),
/// matchesGoldenFile('goldens/myWidget.png'),
/// );
测试WidgetsTree里面某个widget点击事件:
await tester.tap(find.byIcon(Icons.add));
其它:
tapAt(): 指定位置触摸; tap() 默认执行于中心位置:topAt(location:center).
press(): 与tap()不同的是,返回gesture对象,并允许继续操作。tap() 是一个void函数。
更多:
longPress()、longPressAt()、fling()、flingFrom()、drag()、dragFrom()、timeDrag()、timeDragFrom()。
filing() 会引发pump刷新。
Integration_tests:
- 对App或者App大部分功能进行测试;
- 可以直接在设备上运行测试;
- 与widget tests写法基本一致;
- 可以在Firebase Test Lab上进行集成测试.
- 参见:docs.flutter.dev/testing/int…
lib/
...
integration_test/
foo_test.dart
bar_test.dart
test/
# Other unit tests go here.
运行指定的test.dart
flutter test integration_test/foo_test.dart -d <DEVICE_ID>
运行所有的:
flutter test integration_test
详细使用参见:github.com/flutter/flu…
可以在TestLab里面进行集成测试:
Migrating from flutter_driver: 可忽略
参见:docs.flutter.dev/testing/int…
Example:
driver:测试点击:
test('tap on the first item (Alder), verify selected', () async {
// find the item by text
final item = find.text('Alder');
// Wait for the list item to appear.
await driver.waitFor(item);
// Emulate a tap on the tile item.
await driver.tap(item);
// Wait for species name to be displayed
await driver.waitFor(find.text('Alnus'));
// 'please select' text should not be displayed
await driver
.waitForAbsent(find.text('Please select a plant from the list.'));
});
integration_test 测试点击:
testWidgets('tap on the first item (Alder), verify selected',
(tester) async {
await tester.pumpWidget(const PlantsApp());
// wait for data to load
await tester.pumpAndSettle();
// find the item by text
final item = find.text('Alder');
// assert item is found
expect(item, findsOneWidget);
// Emulate a tap on the tile item.
await tester.tap(item);
await tester.pumpAndSettle();
// Species name should be displayed
expect(find.text('Alnus'), findsOneWidget);
// 'please select' text should not be displayed
expect(find.text('Please select a plant from the list.'), findsNothing);
});