Flutter自定义组件插件gherkin_widget_extension的使用

Flutter自定义组件插件gherkin_widget_extension的使用

目录

– 特性

  • 在小部件测试环境中运行用 Gherkin 编写的测试。
  • 提供一个名为 WidgetCucumberWorldCucumberWorld,设计用于小部件测试,并且有存储和共享数据的桶。
  • ✨ 支持无障碍:通过语义标签搜索小部件并检查其完整的语义。
  • 提供一个 Json 加载器,帮助使用 Json 数据构建小部件。
  • 测试失败时,截图和小部件树会转储到文件中。
  • 为小部件测试量身定制的 Gherkin 报告器。

– 入门指南

🥒 添加 gherkin_widget_extension 依赖

在项目的 pubspec.yaml 文件中,在 dev_dependencies 中添加 gherkin_widget_extension 库:

gherkin_widget_extension: ^1.0.0

然后运行 pub get 下载依赖。

✏️ 编写场景

test 文件夹中创建一个 features 文件夹。如果不存在 test 文件夹,则创建它。在 features 文件夹中创建一个 .feature 文件,例如 counter.feature 并编写第一个场景:

Feature: Counter
  The counter should be incremented when the button is pressed.

  @my_tag
  Scenario: Counter increases when the button is pressed
    Given I launch the counter application
    When I tap the "increment" button 10 times
    Then I expect the "counter" to be "10"

下一步:实现步骤定义。

🔗 声明步骤定义

步骤定义类似于将 Gherkin 句法与小部件交互的代码之间的链接。通常,given 步骤用于设置测试上下文,when 步骤表示测试的主要动作(当用户验证表单时,当用户应用他的选择时,等等),而 then 步骤则断言屏幕上的一切(文本、状态、语义等)。

test 文件夹中,创建一个 step_definitions 文件夹并在该文件夹内创建一个 steps.dart 文件并开始实现步骤定义:

import 'package:gherkin/gherkin.dart';
import 'package:gherkin_widget_extension/gherkin_widget_extension.dart';

StepDefinitionGeneric<WidgetCucumberWorld> givenAFreshApp() {
  return given<WidgetCucumberWorld>(
      'I launch the counter application', (context) async {
    // ...
  });
}

💡 建议

为了更好地理解,一个好的实践建议根据 Gherkin 关键字分割步骤定义文件(所有 Given 步骤定义在一个文件 given_steps.dart 中,所有 When 步骤定义在一个文件 when_steps.dart 中,等等)。将这些文件组织成代表功能的文件夹是一个加分项。

⚙️ 添加一些配置

gherkin 提供了广泛的可定制属性,详细信息可在 这里 查看。

test 文件夹中,创建一个新的文件 test_setup.dart 来声明自己的测试配置:

TestConfiguration TestWidgetsConfiguration({
  String featurePath = '*.feature',
}) {
  return TestConfiguration()
    ..features = [Glob(featurePath)]
    ..hooks = [WidgetHooks(dumpFolderPath: 'widget_tests_report_folder')]
    ..order = ExecutionOrder.sequential
    ..stopAfterTestFailed = false
    ..reporters = [
      WidgetStdoutReporter(),
      WidgetTestRunSummaryReporter(),
      XmlReporter(dirRoot: Directory.current.path)
    ]
    ..stepDefinitions = [
      givenAFreshApp(),
      whenButtonTapped(),
      thenCounterIsUpdated()
    ]
    ..defaultTimeout =
    const Duration(milliseconds: 60000 * 10);
}

包特有的特性

..hooks

该包提供了一个 WidgetHooks 类,实现了由 gherkin 包提供的 Hook 类。这个类处理小部件测试报告,如截图和小部件树渲染。

更多信息 在这里

..reporters

gherkin 包提供的 Reporters 在此包中得到了增强:

  • WidgetStdoutReporter: 打印每个 Gherkin 步骤及其状态到终端日志。
  • WidgetTestRunSummaryReporter: 在测试执行结束时打印整个测试执行的摘要到终端日志。
  • XmlReporter: 生成格式化的 XML 测试执行报告,适用于 GitLab CI 测试报告。

更多信息 在这里

🧪 设置测试运行器

test 文件夹中创建一个新文件 widget_test_runner.dart 并调用测试运行器方法:

void main() {
  testWidgetsGherkin('widget tests',
      testConfiguration: TestWidgetsConfiguration(featurePath: "test/features/*.feature"));
}

test 文件夹中创建另一个文件 flutter_test_config.dart 并声明测试执行配置以启用字体加载(截图所需):

Future<void> testExecutable(FutureOr<void> Function() testMain) async {
  await loadAppFonts();
  await testMain();
}

🪄 运行你的测试

打开终端并执行你之前创建的文件:

flutter test test/widget_test_runner.dart

你应该看到这样的日志: 测试运行示例

🎬️ 让我们开始吧!

编写你需要的任何 Gherkin 场景,使用 Cucumber 标签来运行某些场景,并探索 gherkin 包提供的所有选项。

– 使用方法

🌍 WidgetCucumberWorld 的优势

gherkin 包暴露了一个 CucumberWorld 用于存储数据并在同一场景中的所有步骤之间共享它们。CucumberWorld 是唯一的:每个场景只创建一个,并在场景完成后销毁,无论其状态如何。更多关于 CucumberWorld 的目标的信息 在这里

WidgetCucumberWorld 类继承自 gherkin 包中的 CucumberWorld 类,并公开以下项目:

  • WidgetTester tester: 允许与小部件进行交互(点击、泵送、获取元素等),
  • SemanticsHandle semantics: 在测试开始时创建,使与 Semantics 小部件进行交互成为可能,
  • String scenarioName: 存储当前场景名称 - 用于报告,
  • String json: 存储用于构建小部件的 Json 数据,
  • Bucket bucket: 存储来自步骤变量的测试数据,更多信息在下一节中。

无需创建 WidgetCucumberWorld 对象,包提供了一个名为 currentWorld 的对象,可以通过 context 对象访问:

context.world.tester;

🪣 数据桶

如果 CucumberWorld 是测试的背包,那么 Buckets 就是它的口袋。

Buckets 允许你在 CucumberWorld 中组织你的数据。确实,如果你的应用程序有不同的使用场景,比如票务销售、客户账户或交通信息等,你可能不希望将所有测试数据都存储在 CucumberWorld 中:你可以使用 Buckets 根据业务领域存储数据(例如:TicketSellBucket,AccountBucket,…)。

Buckets 是抽象和通用的,因此要使用它们,你需要创建一个实现 Bucket 类的类:

class ExampleBucket implements Bucket {
  String? buttonAction;
  int nbActions = 0;
}

在这个例子中,桶的名称是 ExampleBucket 并存储两个值:要交互的按钮的名称,以及该按钮被点击的次数。

在将数据存储到 Bucket 之前,它需要通过 WidgetCucumberWorld 对象 currentWorld 初始化:

currentWorld.bucket = ExampleBucket();

然后使用 currentWorld 设置和访问 Bucket 的数据:

currentWorld.readBucket<ExampleBucket>().nbActions = count;

请记住,需要指定 Bucket 的类型才能使用它并访问其数据(在这里是 <ExampleBucket>)。

👁️ 不要忘记可访问性

在移动应用程序中,可访问性至关重要并且必须进行测试。这个包提供了一个测试小部件语义的方法:

Finder widgetWithSemanticLabel(
  Type widgetType,
  String semanticLabel,
  {bool skipOffstage = true,
  Matcher? semanticMatcher}
)

这个方法允许你通过其类型、语义标签和完整的语义查找一个控件:

final widgetToFind = find.widgetWithSemanticLabel(
  Checkbox, 
  "Checkbox label",
  semanticMatcher: matchesSemantics(
    hasEnabledState: true,
    label: "Checkbox label",
    hasTapAction: true,
    isEnabled: true,
    isFocusable: true,
    textDirection: TextDirection.ltr,
    hasCheckedState: true,
    isChecked: true
  )
);

expect(widgetToFind, findsOneWidget);

expect 如果没有找到相应的控件会抛出 AssertionError

🧩 WidgetObject 模式和 TestWidgets 类的使用

根据“小部件对象”模式组织测试代码可以帮助维护和代码清晰度。“小部件对象”模式(为此包发明)强烈受到页面对象模式的启发,并应用于 Flutter 小部件测试。

在测试自动化中,页面对象模式要求为每个页面(或屏幕)创建一个类,不仅包含与该页面交互的所有方法(点击、检查等),还包括选择器到该页面(如何定位页面上的给定元素)。这样可以简化理解和维护。

在小部件测试上下文中,测试的是屏幕的一部分(或小部件树的一部分),而不是页面。但是,保持页面对象逻辑仍然是有用的。

抽象类 TestWidget 可以作为“小部件对象”类的接口来强制实现最重要的方法:

  • pumpItUp() - 指示如何泵送要测试的小部件

示例:

class MyWidgetObject implements TestWidgets {
  @override
  Future<void> pumpItUp() async {
    var widgetToPump = const MaterialTestWidget(
      child: MyApp(),
    );
    pumpWidget(widgetToPump);
  }
}

🔄 通过 JsonLoader 加载数据

应用程序经常使用从 API 获取的数据来构建屏幕上的组件,而小部件测试不能依赖于 API 及其潜在的缓慢性和不稳定性。小部件测试应该可靠且尽可能快。因此,API Json 响应可以存储到文件并通过提供的 JsonLoader 加载,以帮助构建要测试的小部件。

var jsonMap = await JsonLoader.loadJson("path/to/json/folder");

jsonMap 包含一个键为 Json 文件名、值为 Json 文件内容的映射。

📸 截图和小部件树渲染的钩子

📣 至少需要在 pubspec.yml 中声明一种字体以获得美观且易读的截图(否则将用方块代替字体)。

📣 flutter_test_config.dart 必须位于 test 目录的根目录下(参见 🧪 设置测试运行器 段落)。

钩子包含在 Cucumber 驱动的测试期间执行的方法(场景前后,步骤前后,…)。有关钩子的更多信息 在这里

这个包提供了一个名为 WidgetHooks 的钩子以改进报告并提供更多的测试失败信息,例如截图和小部件树渲染。在 TestConfiguration 中添加此钩子以享受其功能:

TestConfiguration()
  ..hooks = [
    WidgetHooks(dumpFolderPath:'widget_tests_report_folder')
  ]

参数 dumpFolderPath 是必需的

它代表在测试失败时截图和小部件渲染将被存储的报告文件夹。

📣 该包提供了一个名为 MaterialTestWidget 的自定义小部件。这个小部件必须封装要泵送的小部件以启用截图和小部件渲染。

📋 小部件测试报告器

MonochromePrinter

这个类扩展了 LogPrinter 类并简化了日志记录。它是其他提供的报告器的基础。Flutter 日志非常方便但可能在报告上下文中过于冗长。MonochromePrinter 允许你在日志控制台中打印消息而不带任何装饰/表情符号/任何东西。

WidgetStdoutReporter

这个报告器负责:

  • 打印正在运行的场景及其文件位置的名称,
  • 打印每个步骤及其状态和时间持续情况
    • 如果步骤成功
    • × 如果步骤失败
    • - 如果步骤跳过
  • 打印场景执行结果
    • PASSED
    • FAILED
  • 处理文本着色

示例:

WidgetStdoutReporter 示例

WidgetTestRunSummaryReporter

这个报告器负责打印测试执行摘要及其文本着色。它总结了:

  • 预期场景的总数
  • 成功场景的总数
  • 失败场景的总数
  • 跳过场景的总数

示例:

WidgetTestRunSummaryReporter 示例

XmlReporter

这个报告器生成一个名为 junit-report.xml 的 XML 文件,格式为 JUnit,适用于 GitLab CI。此文件将在根目录中创建。

这样,所有测试执行结果都将出现在管道的 Tests 标签中:

GitLab CI 报告

测试执行的详细信息也可以查看:

GitLab CI 报告详情

在失败的情况下,System output 包含:

  • 步骤的状态(通过、失败或跳过)的摘要
  • 异常堆栈跟踪
  • 小部件渲染的打印

在失败的情况下,还会提供指向截图的链接。

🦊 注意 GitLab Job 配置

将截图和 XML 文件作为工件公开,以便 GitLab CI 可以收集 Tests 标签中的信息。有关 GitLab 测试报告的更多信息 在这里

添加报告器到测试配置

要利用提供的报告器,它们需要添加到 TestConfiguration 中:

TestConfiguration()
  ..reporters = [
    WidgetStdoutReporter(),
    WidgetTestRunSummaryReporter(),
    XmlReporter(dirRoot:Directory.current.path)
  ]

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

1 回复

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


当然,以下是一个关于如何在Flutter中使用自定义组件插件 gherkin_widget_extension 的示例代码。假设 gherkin_widget_extension 是一个包含一些自定义小部件(widgets)的插件,并且你需要在你的Flutter项目中引用这些自定义小部件。

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

dependencies:
  flutter:
    sdk: flutter
  gherkin_widget_extension: ^x.y.z  # 替换为实际的版本号

然后,运行 flutter pub get 来获取依赖项。

接下来,在你的Flutter项目中使用这个插件。假设 gherkin_widget_extension 包含一个名为 CustomButton 的自定义按钮小部件,下面是如何在你的代码中引用和使用它的示例:

import 'package:flutter/material.dart';
import 'package:gherkin_widget_extension/gherkin_widget_extension.dart';  // 导入插件

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Custom Widget Demo'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            // 使用插件中的 CustomButton 小部件
            CustomButton(
              onPressed: () {
                // 点击按钮时的回调
                ScaffoldMessenger.of(context).showSnackBar(
                  SnackBar(content: Text('Button clicked!')),
                );
              },
              child: Text('Click Me'),
            ),
            SizedBox(height: 20),
            // 你可以在这里添加更多的自定义小部件
          ],
        ),
      ),
    );
  }
}

在这个示例中,我们:

  1. pubspec.yaml 文件中添加了 gherkin_widget_extension 依赖项。
  2. MyApp 类中创建了一个基本的 Flutter 应用。
  3. MyHomePage 类中创建了一个 Scaffold,并在其 body 中使用 Column 来布局子部件。
  4. Column 中添加了 CustomButton 小部件,并为其 onPressed 属性提供了一个回调函数,当按钮被点击时,显示一个 SnackBar

请注意,这个示例假设 CustomButton 是一个接受 onPressedchild 参数的自定义按钮小部件。具体的参数和用法可能会根据你实际使用的插件版本和文档有所不同。

务必查阅 gherkin_widget_extension 的官方文档或源码以获取准确的信息和用法示例。

回到顶部