Flutter自定义匹配查找插件finder_matcher_gen的使用

Flutter自定义匹配查找插件finder_matcher_gen的使用

动机 ✨

我发现自己在编写自定义的查找器(Finder)和匹配器(Matcher)来抽象化小部件测试中的验证代码。实现这些功能可能会很繁琐。

Finder-Matcher-Gen尝试通过为这些自定义类生成代码来解决这个问题。

查找器 before-finder after-finder
匹配器 before-matcher after-matcher

安装 💻

为了开始使用Finder Matcher Gen,必须在你的机器上安装Dart SDK。

pubspec.yaml文件中添加finder_matcher_gen

运行以下命令:

flutter pub add finder_matcher_annotation
flutter pub add finder_matcher_gen --dev
flutter pub add build_runner --dev

或者手动添加到你的pubspec.yaml文件。

dependencies:
  finder_matcher_annotation: ^[version]

dev_dependencies:
  finder_matcher_gen: ^[version]
  build_runner: ^[version]

运行以下命令进行安装:

flutter pub get

将以下代码复制粘贴到你的测试文件中以导入:

import 'package:finder_matcher_annotation/finder_matcher_annotation.dart';

注解使用 🚀

Finder-matcher-gen 使用注解声明来生成代码。该工具提供了两个注解:@Match[@MatchDeclaration](/user/MatchDeclaration) 注解。

@Match 注解

@Match 注解应用到一个声明(通常是在测试的 main() 函数)中,以指定要生成查找器或匹配器的小部件。

@Match(finders: [], matchers: [])
void main() {
    // 测试代码
}

@Match 注解接受两个参数:一个用于查找器的 Type 列表;一个用于匹配器的 MatchWidget 列表。

查找器

对于名为 TrafficLightLampWidget 的小部件,将小部件类型传递给 @Match 注解的 finders 参数以生成查找器对应物。

@Match(finders: [TrafficLightLampWidget])

注意: 可以传递任意数量的小部件类型到 finders 参数以生成查找器对应物。

匹配器

MatchWidget 列表传递给 matchers 参数。MatchWidgets 接受三个参数:

  • 必需的 type:此小部件的运行时表示,用于生成匹配器对应物。
  • 必需的 matchSpecification:一个 MatchSpecification 枚举,用于定义要为该小部件生成的匹配器类型。
  • 可选的 secondaryType:另一个小部件的运行时表示,该匹配器将使用。
@Match(matchers: [ 
    MatchWidget(TrafficLightLampWidget, MatchSpecification.matchesOneWidget),
])

有关可以设置的不同匹配规范,请点击这里了解更多信息。

[@MatchDeclaration](/user/MatchDeclaration) 注解

在大多数情况下,小部件中定义的声明(获取器、字段、函数)对小部件的身份至关重要。换句话说,它们将用于断言该小部件的行为。

使用 [@MatchDeclaration](/user/MatchDeclaration) 注解标记小部件字段、获取器或函数,以将其标记为在验证代码中使用。[@MatchDeclaration](/user/MatchDeclaration) 注解接受一个 defaultValue 参数,用于与测试环境中找到的实际值进行比较。如果未提供默认值,则将为此声明添加构造函数字段。

class RedTrafficLightLampWidget extends StatelessWidget{
    [@MatchDeclaration](/user/MatchDeclaration)()
    final Color lightColor;

    [@MatchDeclaration](/user/MatchDeclaration)(defaultValue: 'STOP')
    final String text;
} 

下面的代码展示了提供默认值和不提供默认值的结果。

/// 其中 `_lightColor` 是此生成代码的构造函数字段
return widget.lightColor == _lightColor && widget.text == 'STOP';

静态分析

在使用此注解时,常见的错误是传递了错误的数据类型(不同于注解属性的数据类型)给 defaultValue

幸运的是,此包提供了静态分析,可以在发生此类错误时抛出异常。

注意: [@MatchDeclaration](/user/MatchDeclaration) 注解只能用于获取器、字段和非空方法。

生成代码 🏭

运行以下命令以生成自定义查找器和匹配器代码。

flutter pub run build_runner build

成功运行后,你应该会注意到两个新生成的文件:

  • 包含生成查找器的文件 <my_test_file>.finders.dart
  • 包含生成匹配器的文件 <my_test_file>.matchers.dart

有关更多信息,请参阅生成部分以了解如何使用生成的文件。

为集成测试配置生成

在项目的顶级文件夹中创建一个 build.yaml 文件。在新创建的文件中插入以下代码。

targets:
  $default:
    sources:
      - integration_test/**
      - lib/**
      - test/**
      # 注意,包括这些在默认目标中很重要。
      - pubspec.*
      - $package$

注意: 你还可以包含需要生成匹配器和查找器的自定义文件夹。


详细文档 📄

有关使用此工具的更多详细信息,请访问文档


Bug/请求 🐛

如果你遇到任何问题,请随时打开一个问题。如果你觉得库缺少功能,请在Github上提出一个建议,我会考虑它。也欢迎提交拉取请求。


持续集成 🤖

Finder Matcher Gen 配备了一个由 Very Good Workflows 支持的 GitHub Actions 工作流,但你也可以添加自己的 CI/CD 解决方案。

开箱即用,在每个拉取请求和推送时,CI 会格式化、检查和测试代码。这确保了代码的一致性和正确性。该项目使用 Very Good Analysis 进行严格的分析选项。使用 Very Good Workflows 执行代码覆盖率。


示例代码

example/lib/main.dart

import 'package:example/models.dart';
import 'package:example/widgets.dart';
import 'package:finder_matcher_annotation/finder_matcher_annotation.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        useMaterial3: true,
        primarySwatch: Colors.blue,
        sliderTheme: const SliderThemeData(
          trackHeight: 10,
        ),
      ),
      home: const HomeScreen(),
    );
  }
}

class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key});

  [@override](/user/override)
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  final tasks = <TaskModel>[];

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(title: const Text('Tasks')),
      body: tasks.isEmpty
          ? Center(
              child: Padding(
                padding: const EdgeInsets.all(32.0).copyWith(bottom: 100),
                child: const Text(
                  "Your task list is empty. Tap on the 'Add Task' button to add a task.",
                  style: TextStyle(
                    fontWeight: FontWeight.w700,
                    fontSize: 16,
                  ),
                ),
              ),
            )
          : TaskListView(tasks: tasks),
      floatingActionButton: AppFloatingActionButton(
        onPress: () async {
          final newTask = await showModalBottomSheet(
            context: context,
            builder: (c) => const AddTargetBottomSheet(),
          );

          if (newTask != null) {
            tasks.add(newTask);
            setState(() {});
          }
        },
      ),
    );
  }
}

class TaskListView extends StatelessWidget {
  const TaskListView({
    Key? key,
    required this.tasks,
  }) : super(key: key);

  [@MatchDeclaration](/user/MatchDeclaration)()
  final List<TaskModel> tasks;

  [@override](/user/override)
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: tasks.length,
      itemBuilder: (context, index) => ItemTask(taskModel: tasks[index]),
      physics: const BouncingScrollPhysics(),
    );
  }
}

更多关于Flutter自定义匹配查找插件finder_matcher_gen的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter自定义匹配查找插件finder_matcher_gen的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何使用Flutter中的finder_matcher_gen插件进行自定义匹配查找的示例代码。这个插件通常用于自动生成用于查找Widget的匹配器,从而简化测试代码。

首先,确保你已经在pubspec.yaml文件中添加了finder_matcher_gen依赖:

dependencies:
  flutter:
    sdk: flutter
  test: ^1.16.0 # 确保你有合适的test包版本
dev_dependencies:
  build_runner: ^1.0.0 # 用于生成代码
  finder_matcher_gen: ^x.y.z # 替换为最新版本号

然后,你需要创建一个用于定义匹配器的YAML文件。例如,创建一个名为matchers.yaml的文件:

matchers:
  myCustomButton:
    type: ByType
    value: ElevatedButton
    child:
      type: ByText
      value: My Button Text

接下来,在你的Flutter项目中,运行以下命令来生成匹配器代码:

flutter pub run build_runner build

这会在你的项目中生成一个matchers.g.dart文件,其中包含了基于matchers.yaml定义的匹配器。

现在,你可以在测试代码中使用这些生成的匹配器。例如,创建一个名为my_widget_test.dart的测试文件:

import 'package:flutter_test/flutter_test.dart';
import 'package:your_app/matchers.g.dart'; // 导入生成的匹配器文件
import 'package:your_app/main.dart'; // 导入你的应用主文件

void main() {
  testWidgets('finds my custom button', (WidgetTester tester) async {
    await tester.pumpWidget(MyApp()); // 加载你的应用

    // 使用生成的匹配器查找Widget
    expect(find.byType(MyCustomButtonMatcher), findsOneWidget);
  });
}

请注意,上面的代码中MyCustomButtonMatcher是从matchers.g.dart文件中生成的匹配器类。这个类是根据matchers.yaml文件中定义的规则生成的,用于匹配具有特定属性的ElevatedButton

完整的流程如下:

  1. 定义匹配规则(matchers.yaml)。
  2. 运行flutter pub run build_runner build生成匹配器代码。
  3. 在测试代码中使用生成的匹配器。

这个示例展示了如何使用finder_matcher_gen插件来简化和标准化Widget查找,使得测试代码更加清晰和易于维护。

回到顶部