Flutter Test 基础

avatar
Android @奇舞团Android团队

级别:★☆☆☆☆
标签:「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 进行测试。

Flutter Test Run

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

FlutterLearningRecord

三、参考学习网址


笔者微信:可加并拉入《aTaller技术交流群》。

笔者微信

关注我们的途径有:
aTaller(简书)
aTaller(掘金)
aTaller(微信公众号)

推荐文章:
Flutte 开发小技巧
Flutter 常用 Widget 介绍
Flutter 图片加载
Flutter 混合栈复用原理
Flutter Platform Channel 使用与源码分析
Flutter Platform View 使用及原理简析
奇舞周刊