Home Flutter 自动化测试
Post
Cancel

Flutter 自动化测试

官方文档 介绍,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(): 创建 WidgetTester
  • Finder : 搜索 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);
This post is licensed under CC BY 4.0 by the author.