如何测试你的Flutter应用

引言

平时开发中,app的各项功能测试工作一直都是由测试组来完成的,他们做的一般是黑盒测试(也称功能测试),其实我们开发人员已知产品的内部工作过程,可以通过编写自动化测试代码的方式测试证明每种内部操作是否符合设计规格要求,也就是白盒测试,所以接下来我就给大家介绍几种基本的测试方法。
白盒测试和黑盒测试

flutter中几种自动化测试的类型

1.单元测试(unit test
2.组件测试(widget test
3.集成测试(integration test
这几种测试类型并不是独立存在的,一般而言,一个经过良好测试的app具有许多单元和组件测试,可按代码覆盖率进行跟踪,并具有足够的集成测试来涵盖所有重要的用例。

单元测试

单元测试顾名思义,是指测试单个函数、方法或者类。

单元测试的目的是在各种条件下验证逻辑单元的正确性。测试所依赖的一些外部数据或者操作一般通过模拟产生,并不会直接读取或操作服务器存储的数据。

那如何构建一个单元测试呢?

主要分为以下几步
1.添加测试包依赖

dev_dependencies:
  flutter_test:
    sdk: flutter
  test: 1.15.2

2.创建需要被测试的函数、方法或者类和用来测试他的测试类
被测试的文件一般放在项目的lib目录下,而用来测试文件一般放在和lib同级的test目录下,一般以被测试文件名加_test后缀命名

现在我们在lib目录下创建一个名为Counter的类,它含有一个value属性和两个方法increment()和decrement()

/// a value counter
class Counter {
  int value = 0;

  void increment() => value++;

  void decrement() => value--;
}

然后我们在test目录下创建counter_test文件,来测试Counter的increment()方法是否符合设计的预期

import 'package:test/test.dart';
import 'package:test_flutter_app/counter.dart';

void main() {
  test('Counter value should incremented', () {
    final counter = Counter();

    /// invoke increment function
    counter.increment();

    /// verify result
    expect(counter.value, 1);
  });
}

3.运行测试
通过IDE(Android Studio或者VS Code)打开counter_test.dart,点击run menu就可运行
或者通过命令行方式运行

flutter test test/counter_test.dart

是不是很简单?实际上单元测试可以做的事情远比这多和复杂,限于篇幅,这里就不展开讲了

组件测试

flutter中我们最常使用的就是widget了,Flutter SDK中附带的flutter_test工具包中提供了几种工具,让我们来测试我们的widget

  • WidgetTester 在测试环境构建widget并和和其进行交互
  • testWidgets() 它会为每个测试用例自动创建一个新的WidgetTester,并代替常规的test()函数使用
  • Finder 用来在测试环境中寻找指定widget
  • Matcher 验证指定widget是否符合预期

有了这几个基础工具,我们就可以来构建我们的widget测试了

分为以下几步

1.添加测试包依赖(和单元测试一致,省略)

2.创建一个待测试的widget

class MyWidget extends StatelessWidget {
  final String title;
  final String message;

  const MyWidget({
    Key key,
    @required this.title,
    @required this.message,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: Scaffold(
        appBar: AppBar(
          title: Text(title),
        ),
        body: Center(
          child: Text(message),
        ),
      ),
    );
  }
}

3.创建测试文件,编写测试逻辑

void main() {
  // 定义一个widget测试,testWidgets()方法提供了一个WidgetTester用来在测试环境
  // 构建需要测试的组件并与之交互
  testWidgets('一个有自定义title和文本的widget', (WidgetTester tester) async {
    // 通过tester构建待测试的widget
    await tester.pumpWidget(MyWidget(title: 'T', message: 'M'));

    // 创建finder,找到标题和文本
    final titleFinder = find.text('T');
    final messageFinder = find.text('M');

    // 使用 flutter_test 提供的 `findsOneWidget` matcher 来匹配一个组件的情况
    // 验证在tree上对应widget是否符合预期
    expect(titleFinder, findsOneWidget);
    expect(messageFinder, findsOneWidget);
  });
}

基本测试流程为
1.调用testWidgets()方法,这个是测试入口
2.使用生成的WidgetTester构建我们要测试的Widget
3.使用find找到我们想验证的元素
4.调用expect方法并配合Matcher验证结果是否符合预期

4.运行测试(和单元测试类似,省略)

集成测试

单元测试和组件测试对于测试单个类,函数或widget都非常方便。但是,他们通常不会测试各个部分在整体上如何协同工作,也不会捕获在真实设备上运行的应用程序的性能。这些任务要通过集成测试来执行。

接下来我们就来一步一步实现一个最简单的集成测试案例

1.首先,创建一个待测试的app

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '计数器App',
      home: MyHomePage(title: '计数器App首页'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              '你点击button的次数:',
            ),
            Text(
              '$_counter',
              // 给点击次数文本设置一个key值,方便读取和验证结果
              key: Key('counter'),
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        // 设置一个key给这个按钮,方便测试时寻找并点击它
        key: Key('increment'),
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

功能非常简单,每点击一下button,计数并改变显示的文本数量

2.添加flutter_driver依赖

dev_dependencies:
  flutter_test:
    sdk: flutter
  test: 1.15.2
  flutter_driver:
    sdk: flutter

3.创建测试文件
不同于单元测试和组件测试,集成测试需要创建两个文件
1.一个文件用来创建可测试的Flutter应用程序
2.一个文件用来运行测试指令集合

counter_app/
  lib/
    main.dart
  test_driver/
    app.dart
    app_test.dart

本例中,在test_driver目录下创建了app.dart和app_test.dart,注意测试文件必须和创建可测试的Flutter应用程序文件名一致,并以 _test 结尾

4.创建可测试的Flutter应用程序

void main() {
  // 开启flutter测试驱动扩展
  enableFlutterDriverExtension();

  // 启动你想测试的app或者应用
  app.main();
}

5.编写测试用例

void main() {
  group('计数器App', () {
    // 首先通过findre找到我们这次测试中需要使用到的两个widget
    final counterTextFinder = find.byValueKey('counter');
    final buttonFinder = find.byValueKey('increment');

    FlutterDriver driver;

    // 连接 flutter driver
    setUpAll(() async {
      driver = await FlutterDriver.connect();
    });

    // 测试用例跑完后断开连接
    tearDownAll(() async {
      if (driver != null) {
        driver.close();
      }
    });

    test('初始计数值 0', () async {
      // 验证计数器初始值
      expect(await driver.getText(counterTextFinder), "0");
    });

    test('点击按钮增加计数', () async {
      // 模拟点击增加计数按钮
      await driver.tap(buttonFinder);

      // 验证计数器值变为1
      expect(await driver.getText(counterTextFinder), "1");
    });
  });
}

6.运行测试

在命令行输入以下指令

flutter driver --target=你的工程路径/test_driver/app.dart

运行结果

lihuifeng@lihuifengdeMacBook-Pro test_flutter_app % flutter driver --target=/Volumes/WorkSpace/test_flutter_app/test_driver/app.dart
Using device vivo NEX A.
Starting application: /Volumes/WorkSpace/test_flutter_app/test_driver/app.dart
Installing build/app/outputs/flutter-apk/app.apk...                 7.0s
Running Gradle task 'assembleDebug'...                                  
Running Gradle task 'assembleDebug'... Done                         3.6s
✓ Built build/app/outputs/flutter-apk/app-debug.apk.
I/flutter (16989): Observatory listening on http://127.0.0.1:40395/Dmwm7lXlm2o=/
00:00 +0: 计数器App (setUpAll)

VMServiceFlutterDriver: Connecting to Flutter application at http://127.0.0.1:63926/Dmwm7lXlm2o=/
VMServiceFlutterDriver: Isolate found with number: 2159525238303759
VMServiceFlutterDriver: Isolate is paused at start.
VMServiceFlutterDriver: Attempting to resume isolate
VMServiceFlutterDriver: Connected to Flutter application.
00:02 +0: 计数器App 初始计数值 0

00:02 +1: 计数器App 点击按钮增加计数

00:02 +2: 计数器App (tearDownAll)

00:02 +2: All tests passed!

Stopping application instance.

至此flutter测试的几种方法都介绍完了,当然这只是最基础的用法,后续大家有兴趣,我可以结合项目写一些能实际使用的例子再给大家分享

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,657评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,889评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,057评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,509评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,562评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,443评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,251评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,129评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,561评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,779评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,902评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,621评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,220评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,838评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,971评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,025评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,843评论 2 354