Flutter学习之测试

Posted GY-93

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter学习之测试相关的知识,希望对你有一定的参考价值。

1. 单元测试

单元测试是针对一个函数和一个类进行测试

1.1 添加测试依赖

test或则flutter_test加入依赖文件,默认创建的flutter程序已经有了依赖

Test包提供了编写测试所需要的核心功能:

1.2 创建需要测试的类

单元测试通常是测试一个函数或则类,这个函数或则类被称之为一个单元

在这里,我创建了一个math_utils类,其中有一个求和的方法:

int sum(int num1, int num2) 
  return num1 + num2;

1.3 创建测试文件

我们在test目录下(注意:不是lib目录下)创建一个测试文件math_test.dart

  • 通常测试代码都会放在该目录下,并且测试文件不会带包到最终的应用程序中
  • 测试文件通常已_test.dart命名,这个是test runner寻找测试文件的惯例
import 'package:flutter_test/flutter_test.dart';
import 'package:test_demo/math_utils.dart';

void main() 
  test("math utils test", ()
    //调用方法执行操作 
    final result = sum(20, 30);
    //通过expect函数来检测结果正确与否, 第一个参数:实际值 , 第二个参数:预期值
    expect(result, 50);
  );

如何运行单元测试的用例了:

我们也可以在终端执行这个操作:

flutter test test/counter_test.dart

1.4 整合多个测试

如果对同一个类或则函数有多个测试,我们希望他们关联在一起进行测试,可以使用group

/*
* 测试类也需要main函数入口
* */

import 'package:flutter_test/flutter_test.dart';
import 'package:test_demo/math_utils.dart';

void main() 
  group("math all test", ()
    test("math utils sum test", ()
      //调用方法执行操作
      final result = sum(20, 30);
      //通过expect函数来检测结果正确与否, 第一个参数:实际值 , 第二个参数:预期值
      expect(result, 50);
    );
    
    test("math utils sub test", ()
        final result = sub(20, 30);
        expect(result, -10);
    );

    test("math utils mul test", ()
      final result = mul(20, 30);
      expect(result, 600);
    );

    test("math utils dev test", ()
      final result = div(20, 10);
      expect(result, 2);
    );
  );

测试结果可以在这里看到的

如果验证不通过会报错:

1.5 测试的初始化

有些时候我们需要测试多个功能, 但是我不清楚执行测试代码的时候,有些对象 有没有进行初始化,所以我们可以在一个方法中初始化我们所需要的对象,然后再进行测试

    group("Counter Class Test", () 
    Counter counter;
    
    // 所有测试test开始前会执行的代码
    setUpAll(() 
      counter = Counter();
    );

    test("counter default value", () 
      expect(counter.value, 0);
    );
    
    test("counter increment method", () 
      counter.increment();
      expect(counter.value, 1);
    );
    
    test("counter decrement method", () 
      counter.decrement();
      expect(counter.value, 0);
    );
  );
  • 注意:
    • 每一个test方法内使用的counter都是同一个对象,一个改变,另外一个地方访问的就是改变之后的对象
    • 所有的test方法都是按照顺序执行下来的,一个执行完成,在执行另外一个,串行执行

2. Widget测试

Widget测试需要先给pubspec.yamldev_dependencies段添加flutter_test依赖,在单元测试中我么已经介绍过了,其实在创建项目的时候已经自动添加过了

2.1 创建widget


import 'package:flutter/material.dart';
class GYKeywords extends StatelessWidget 
  final List<String> keywords;
  GYKeywords(this.keywords);

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      body: ListView(
        children: keywords.map((key) 
          return ListTile(
            leading: Icon(Icons.people),
            title: Text(key),
          );
        ).toList(),
      ),
    );
  

2.2 编写测试代码

创建对应的测试文件,编写对应的测试代码,首先我们应该了解下widget的函数

  • testWidgets:该函数是flutter_test库中用于测试Widget的函数
  • tester.pumpWidgetpumpWidget方法会建立并渲染我们提供的Widget;
  • find: find()方法是来创建我们的finders
    • findsNothing:验证没有可被查找的 widgets。
    • findsWidgets:验证一个或多个 widgets 被找到
    • findsNWidgets:验证特定数量的 widgets 被找到

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:test_demo/widget/constact.dart';

void main() 
  testWidgets("contact widget test", (WidgetTester widgetTester) async 
    /**
     * pumpWidget函数返回一个Future对象,是一个异步函数,所以我们需要等待这个函数执行完成我们才能去验证结果
     * 不然可能我这个函数都没有执行完成,就去检测结果了
     * 该函数是注入一个widget,因为widget跑起来调用一个runApp方法,而这里我又没有调用这个方法,
     * 这里使用pumpWidget这个方法使我们widget跑起来,然后测试
     */
   await widgetTester.pumpWidget(GYConstact(["abd","cba","nba"]));

    //通过find对象在GYConstact中查找Widget/Text, 目前我们这里是查找Text和Icons
    final abcText = find.text("abd");
    final cbaText = find.text("cba");
    final icons = find.byIcon(Icons.people);

    //实际值 一个返回的是一个find对象
    expect(abcText, findsOneWidget); //表示该文本对应的widget
    expect(cbaText, findsOneWidget);
    expect(icons, findsNWidgets(3));
  );

我们执行以上代码,发现报如下错误:

其实Scafflod是不能单独运行在页面上的,必须被MaterialApp包裹,因为MaterialApp中会帮我们初始化很多东西,例如MediaQuery

由错误信息我们可以知道是因为在Scafflodwidget中没有找到MediaQuery对象,其实前面我们学习屏幕适配时,我们知道MediaQuery对象是在MaterialAppwidget中初始化的,而我们这里有没有使用MaterialApp,所以会报如下错误。

  • **解决方法:**在外面包裹一层MaterialApp就可以了:
await widgetTester.pumpWidget(MaterialApp(home: GYConstact(["abd","cba","nba"])));

3. 集成测试

单元测试和Widget测试都是在测试独立的类或函数或Widget,它们并不能测试单独的模块形成的整体或者获取真实设备或模拟器上应用运行的状态;
这些任务可以交给集成测试来完成
集成测试有两个大的步骤

  • 发布一个可测试应用程序到真实设备或者模拟器上
  • 利用独立的测试套件去驱动应用程序,检查仪器是否完好可用;

3.1 创建可测试的应用程序

我们需要创建一个可以在模拟器和真时设备上运行的程序

这里我直接使用了官方的示例程序,但是不同的是我给里面两个Widget添加了两个key

  • 显示数字的Text Widget:ValueKey("counter")
  • 点击按钮的FloatingActionButton Widget:key: ValueKey("buttonKey")

3.2 添加测试依赖

集成测试用到的依赖和上面的单元测试、Widget测试依赖的库是不一样的,集成测试依赖的是flutter_driver,所以我们需要添加如下依赖到pubspec.yaml 文件的 dev_dependencies 位置:

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_driver:
    sdk: flutter
  test: any

3.3 创建一个测试文件

和单元测试、Widget测试不同是,集成测试的程序和待测试的应用并不在同一个进程内,所以我们通常会创建两个文件:

  • 文件一: 用于启动待测试的应用程序
  • 文件二:编写测试代码

3.4 编写安装应用代码

安装应用程序代码在app.dart中,分层两步完成

  • 让flutter driver的扩展可用
  • 运行应用程序
import 'package:flutter_driver/driver_extension.dart';
import 'package:test_demo/main.dart' as app;
void main() 
  //开启DriverExtension
  enableFlutterDriverExtension();

  //手动调用main函数,启动应用程序
  app.main();

3.5 编写集成测试代码

我们已经有了待测应用,现在我们可以编写测试文件了


import 'package:flutter_driver/flutter_driver.dart';
import 'package:test/test.dart';//这里不要导入flutter_test中的类

void main() 
  group("counter app test", ()
    FlutterDriver? flutterDriver;

    //初始化操作
    setUpAll(() async
        flutterDriver = await FlutterDriver.connect();
    );

    //测试结束操作 加入这段代码之后,运行程序之后不会停留在界面上,虽然测试提示通过,但是之后会显示黑屏, 提示Failed to stop app 
    tearDownAll(() 
      if (flutterDriver != null) 
        flutterDriver!.close();
      
    );

    //编写测试代码
    final counterTextFinder = find.byValueKey("counter");
    final buttonFinder = find.byValueKey("buttonKey");
    
    test("starts at 0", () async
      if (flutterDriver != null) 
        String result = await flutterDriver!.getText(counterTextFinder);
        expect(result, "0");
      
    );

    test("floatButton tap test", () async 
        if (flutterDriver != null) 
          //自动调用按钮点击一次
          await flutterDriver!.tap(buttonFinder);

          expect(await flutterDriver!.getText(counterTextFinder), "1");
        
    );
  );

3.6 运行集成测试

首先,启动安卓模拟器或者 ios 模拟器,或者直接把 iOS 或 android 真机连接到你的电脑上。

接着,在项目的根文件夹下运行下面的命令

flutter drive --target=test_driver/app.dart
  • 该指令的作用:
    • 创建 --target 目标应用并且把它安装在模拟器或真机中
    • 启动应用程序
    • 运行位于 test_driver/ 文件夹下的 app_test.dart 测试套件

运行结果,发现运行正常,并且结果app中的FloatingActionButton自动被点击了一次。

3.7 注意

这里我们有一个需要注意的点, 就是我们创建的集成测试文件不是放在test_driver文件下, 该文件夹的名称必须是test_driver,不然运行会报错

以上是关于Flutter学习之测试的主要内容,如果未能解决你的问题,请参考以下文章

Flutter学习之混合开发

Flutter学习之混合开发

flutter学习之Listview

Flutter学习之滚动监听

Flutter 学习之搭建环境

flutter学习之widget的显示和隐藏