Flutter学习之测试
    1. 单元测试
    • 1.1 添加测试依赖
    • 1.2 创建需要测试的类
    • 1.3 创建测试文件
    • 1.4 整合多个测试
    • 1.5 测试的初始化
    1. Widget测试
    • 2.1 创建widget
    • 2.2 编写测试代码
    1. 集成测试
    • 3.1 创建可测试的应用程序
    • 3.2 添加测试依赖
    • 3.3 创建一个测试文件
    • 3.4 编写安装应用代码
    • 3.5 编写集成测试代码
    • 3.6 运行集成测试
    • 3.7 注意

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);

  
  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 {
   await widgetTester.pumpWidget(MaterialApp(home: 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));
  });
}

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 测试套件

3.7 注意

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


在完成Flutter应用的测试后,开发者可以使用appuploader工具来简化iOS应用的发布流程。appuploader是一款专业的iOS开发助手,可以帮助开发者快速完成证书管理、应用打包和上传到App Store Connect等操作,大大提高了开发效率。

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐