Flutter集成测试插件flutter_gherkin_integration的使用

Flutter集成测试插件flutter_gherkin_integration的使用

简介

flutter_gherkin 是一个完全功能的Gherkin解析器和测试运行器。它适用于Flutter和Dart 2。

此实现尽可能接近其他Gherkin实现,并特别遵循各种形式的Cucumber

作为Dart包可用:

  flutter_gherkin: ^x.x.x

基本示例

以下是一个基本的Gherkin特征文件示例:

# Comment
Feature: Addition

  @tag
  Scenario: 1 + 0
    Given I start with 1
    When I add 0
    Then I end up with 1

  Scenario: 1 + 1
    Given I start with 1
    When I add 1
    Then I end up with 2

使用integration_test包的支持

注意:该库现在更倾向于使用integration_test包和代码生成而不是flutter_driver和运行时解释,并且flutter_driver实现最终将被弃用。

入门步骤

  1. 在你的应用的pubspec.yaml文件中添加以下dev_dependencies
dev_dependencies:
  integration_test:
  build_runner:
  flutter_gherkin:
  1. 添加以下build.yaml文件到项目的根目录。此文件允许Dart代码生成器针对你的应用lib文件夹外的文件。
targets:
  $default:
    sources:
      - lib/**
      - pubspec.*
      - $package$
      # Allows the code generator to target files outside of the lib folder
      - integration_test/**.dart
  1. 添加以下文件(和文件夹)\test_driver\integration_test_driver.dart。这是运行测试的入口点。更多信息参见:https://flutter.dev/docs/testing/integration-tests
import 'package:integration_test/integration_test_driver.dart' as integration_test_driver;

Future<void> main() {
  // The Gherkin report data send back to this runner by the app after
  // the tests have run will be saved to this directory
  integration_test_driver.testOutputsDirectory = 'integration_test/gherkin/reports';

  return integration_test_driver.integrationDriver(
    timeout: Duration(minutes: 90),
  );
}
  1. 创建一个名为integration_test的文件夹,这将包含所有Gherkin特征文件和生成的测试文件。

  2. 添加以下文件(和文件夹)integration_test\features\counter.feature,内容如下。这是一个基本的特征文件,可以转换为可运行测试的测试文件。

Feature: Counter

Scenario: User can increment the counter
  Given I expect the "counter" to be "0"
  When I tap the "increment" button
  Then I expect the "counter" to be "1"
  1. 添加以下文件(和文件夹)integration_test\gherkin_suite_test.dart。注意属性@GherkinTestSuite(),这表示代码生成器会为此文件创建一个部分文件,其中包含生成的Gherkin测试。不用担心初始错误,因为这些错误会在测试生成后消失。
import 'package:flutter_gherkin_integration/flutter_gherkin_integration_test.dart'; // 注意新的导入名称
import 'package:flutter_test/flutter_test.dart';
import 'package:gherkin/gherkin.dart';

// 要测试的应用程序。
import 'package:example_with_integration_test/main.dart' as app;

part 'gherkin_suite_test.g.dart';

@GherkinTestSuite()
void main() {
  executeTestSuite(
    FlutterTestConfiguration.DEFAULT([])
      ..reporters = [
        StdoutReporter(MessageLevel.error)
          ..setWriteLineFn(print)
          ..setWriteFn(print),
        ProgressReporter()
          ..setWriteLineFn(print)
          ..setWriteFn(print),
        TestRunSummaryReporter()
          ..setWriteLineFn(print)
          ..setWriteFn(print),
        JsonReporter(
          writeReport: (_, __) => Future<void>.value(),
        ),
      ],
    (World world) => app.main(),
  );
}
  1. 我们现在需要通过在项目根目录下运行构建命令来生成测试。类似于json_serializable,这将创建一个.g.dart部分文件,其中包含以代码格式呈现的Gherkin测试,可以通过使用integration_test包来运行这些测试。
flutter pub run build_runner build
  1. integration_test\gherkin_suite_test.dart文件中的错误应该已经消失,你可以查看integration_test\gherkin_suite_test.g.dart文件,其中包含了特征文件integration_test\features\counter.feature中描述的Gherkin测试的编码版本。

  2. 我们现在可以从项目根目录下运行测试,使用以下命令:

flutter drive --driver=test_driver/integration_test_driver.dart --target=integration_test/gherkin_suite_test.dart
  1. 你可以通过在integration_test\gherkin_suite_test.dart文件的第12行设置断点并添加以下内容到你的.vscode\launch.json文件来调试测试:
{
  "name": "Debug integration_test",
  "program": "test_driver/integration_test_driver.dart",
  "cwd": "example_with_integration_test/",
  "request": "launch",
  "type": "dart",
  "args": [
    "--target=integration_test/gherkin_suite_test.dart",
  ],
}
  1. 自定义世界需要扩展FlutterWorld,注意FlutterDriverWorld

  2. 如果你更改了任何特征文件,你需要使用以下命令重新生成测试:

# 你可能需要首先运行清理命令,如果你刚刚更改了特征文件
flutter pub run build_runner clean

flutter pub run build_runner build

包升级注意事项

该包即将有一个主要版本来支持null-safety,然后另一个主要版本来支持使用integration_test包和WidgetTester运行测试。我们仍将保持与使用flutter_driver运行测试的兼容性,并尽最大努力使切换到使用integration_test包变得无缝。由于我们必须重构大量代码,因此不幸的是,一些不可避免的破坏性变化将会发生。

目录

入门

请参阅https://docs.cucumber.io/gherkin/了解有关Gherkin语法和行为驱动开发(BDD)的信息。

请参阅示例说明文档,以获取快速入门指南,了解如何运行示例特性和应用程序。

第一步是创建一个启用Flutter驱动程序的应用版本,以便可以自动化。一个很好的指导如何做到这一点可以在这里找到。但是简单来说,在你的项目中创建一个名为test_driver的文件夹,并在其中创建一个名为app.dart的文件,并粘贴以下代码:

import '../lib/main.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_driver/driver_extension.dart';

void main() {
  // 这一行启用了扩展
  enableFlutterDriverExtension();

  // 调用你的应用的`main()`函数或调用`runApp`与任何你想测试的小部件。
  runApp(MyApp());
}

这段代码只是启用了Flutter驱动程序扩展,这是自动化应用所必需的,然后运行你的应用。

要开始使用Flutter进行BDD,第一步是编写一个特征文件并在其中写一个测试场景。

首先创建一个名为test_driver的文件夹(这与当前的集成测试一致,因为我们需要使用Flutter驱动程序来自动化应用)。在文件夹内创建一个名为features的文件夹,然后创建一个名为counter.feature的文件。

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

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

现在我们已经创建了一个场景,我们需要实现场景中的步骤。步骤只是继承自基础步骤定义类或其任何变体GivenThenWhenAndBut的类。

虽然示例有些牵强,但它展示了过程。

此库有一些内置的步骤定义,方便使用。第一个步骤使用了内置的步骤,但第二个步骤When I tap the "increment" button 10 times是一个自定义步骤,必须实现。要实现一个步骤,我们需要创建一个简单的步骤定义类。

import 'package:flutter_driver/flutter_driver.dart';
import 'package:flutter_gherkin_integration/flutter_gherkin.dart';
import 'package:gherkin/gherkin.dart';

StepDefinitionGeneric TapButtonNTimesStep() {
  return when2<String, int, FlutterWorld>(
    'I tap the {string} button {int} times',
    (key, count, context) async {
      final locator = find.byValueKey(key);
      for (var i = 0; i < count; i += 1) {
        await FlutterDriverUtils.tap(context.world.driver, locator);
      }
    },
  );
}

可以看到,when2方法被调用,指定了两个输入参数。第三个类型FlutterWorld是一个特殊的上下文对象,允许从上下文对象访问Flutter驱动程序,从而与你的应用交互。如果你不需要自定义上下文对象或强类型参数,可以完全省略类型参数。

输入参数是通过正则表达式模式从已知参数类型{string}{int}中检索的。它们只是特殊的语法,表示你在步骤文本的这些位置期望字符串和整数。因此,当要执行的步骤是When I tap the "increment" button 10 times时,参数"increment"10将以正确的类型传递给步骤。请注意,在模式中你可以使用任何正则表达式捕获组来指示任何输入参数。例如,正则表达式RegExp(r"When I tap the {string} (button|icon) {int} times")表示3个参数,并且会匹配以下步骤文本中的任意一项:

When I tap the "increment" button 10 times    // 传递3个参数 "increment", "button" & 10
When I tap the "plus" icon 2 times       // 传递3个参数 "plus", "icon" & 2

值得注意的是,此库不依赖于镜子(反射),原因有很多,但主要是为了便于维护,并符合Flutter不允许反射的原则。总的来说,这使得代码更容易理解和维护,也更容易为用户调试。缺点是我们必须稍微明确地提供自定义代码实例,如步骤定义、钩子、报告器和自定义参数。

现在我们有了一个可测试的应用、一个特征文件和一个自定义步骤定义,我们需要创建一个类来调用此库并实际运行测试。创建一个名为app_test.dart的文件,并插入以下代码:

import 'dart:async';
import 'package:flutter_gherkin_integration/flutter_gherkin.dart';
import 'package:gherkin/gherkin.dart';
import 'hooks/hook_example.dart';
import 'steps/colour_parameter.dart';
import 'steps/given_I_pick_a_colour_step.dart';
import 'steps/tap_button_n_times_step.dart';

Future<void> main() {
  final config = FlutterTestConfiguration()
    ..features = [RegExp('features/*.*.feature')]
    ..reporters = [
      ProgressReporter(),
      TestRunSummaryReporter(),
      JsonReporter(path: './report.json')
    ] // 可以包括 "StdoutReporter()" 无需消息级别参数即可获得详细的日志信息
    ..hooks = [HookExample()]
    ..stepDefinitions = [TapButtonNTimesStep(), GivenIPickAColour()]
    ..customStepParameterDefinitions = [ColourParameter()]
    ..restartAppBetweenScenarios = true
    ..targetAppPath = "test_driver/app.dart";
    // ..tagExpression = "@smoke" // 取消注释以查看基于标签表达式的示例
  return GherkinRunner().execute(config);
}

这段代码简单地创建了一个配置对象,并调用此库,该库将立即解析你的特征文件并运行测试。配置文件非常重要,并将在下文中进一步详细说明。然而,所有操作只是提供了一个RegExp,指定一个或多个特征文件的路径,设置了报告器为ProgressReporter报告,打印场景和步骤的结果到标准输出(控制台)。TestRunSummaryReporter在所有测试执行完成后打印运行摘要。最后,它指定了上述创建的可测试应用的路径test_driver/app.dart。重要的是,它指示库运行测试时应针对哪个应用。

最后,要在命令行上运行测试,请运行以下命令:

dart test_driver/app_test.dart

有关调试测试的信息,请参阅调试

注:你可能需要确保dart可通过将其添加到路径变量来访问。

配置

配置是此库中的重要组成部分,因为它不仅指定了要运行的内容,还指定了要运行的步骤、钩子和报告器的类。与其他实现不同,此库不依赖于反射,因此需要显式告知要使用的类。

可以在配置文件中指定以下参数:

特征文件

必需

一个Pattern的迭代器,指定要运行的*.feature文件的位置。请参阅:https://api.dart.dev/stable/2.12.4/dart-core/Pattern-class.html

标签表达式

默认值为null

一个中缀布尔表达式,用于根据其标签定义要运行的功能和场景。参见标签

排序

默认值为ExecutionOrder.random

场景运行的顺序。随机顺序可能突出显示任何应修复的测试间依赖关系。使用ExecutionOrder.sortedfilename顺序处理特征文件。

步骤定义

默认值为Iterable<StepDefinitionBase>

放置任何自定义步骤定义类的实例,这些类与特征文件中定义的任何自定义步骤匹配。

import 'dart:async';
import 'package:flutter_gherkin_integration/flutter_gherkin.dart';
import 'package:gherkin/gherkin.dart';
import 'steps/given_I_pick_a_colour_step.dart';
import 'steps/tap_button_n_times_step.dart';

Future<void> main() {
  final config = FlutterTestConfiguration()
    ..features = [RegExp('features/*.*.feature')]
    ..reporters = [StdoutReporter()]
    ..stepDefinitions = [TapButtonNTimesStep(), GivenIPickAColour()]
    ..restartAppBetweenScenarios = true
    ..targetAppPath = "test_driver/app.dart";
  return GherkinRunner().execute(config);
}

默认语言

默认值为en

这指定了特征文件的默认语言。请参阅:https://cucumber.io/docs/gherkin/reference/#overview了解支持的语言。

请注意,这可以在特征文件本身中通过语言块覆盖。

# language: de
Funktionalität: Calculator
  Tests the addition of two numbers

  Szenariogrundriss: Add two numbers
    Gegeben sei the numbers <number_one> and <number_two>
    Wenn they are added
    Dann the expected result is <result>

    Beispiele:
      | number_one | number_two | result |
      | 12         | 5          | 17     |
      | 20         | 5          | 25     |
      | 20937      | 1          | 20938  |
      | 20.937     | -1.937     | 19     |
# language: fr
Fonctionnalité: Counter
  The counter should be incremented when the button is pressed.

  @smoke
  Scénario: Counter increases when the button is pressed
    Etant donné que I pick the colour red
    Et I expect the "counter" to be "0"
    Quand I tap the "increment" button 10 times
    Alors I expect the "counter" to be "10"

自定义步参数定义

默认值为CustomParameter<dynamic>

放置任何自定义步参数的实例。这些将在场景运行时与步骤匹配,并将其结果传递给可执行步骤。请参阅自定义参数

import 'dart:async';
import 'package:flutter_gherkin_integration/flutter_gherkin.dart';
import 'package:gherkin/gherkin.dart';
import 'steps/given_I_pick_a_colour_step.dart';
import 'steps/tap_button_n_times_step.dart';
import 'steps/colour_parameter.dart';

Future<void> main() {
  final config = FlutterTestConfiguration()
    ..features = [RegExp('features/*.*.feature')]
    ..reporters = [StdoutReporter()]
    ..stepDefinitions = [TapButtonNTimesStep(), GivenIPickAColour()]
    ..customStepParameterDefinitions = [ColourParameter()]
    ..restartAppBetweenScenarios = true
    ..targetAppPath = "test_driver/app.dart";

  return GherkinRunner().execute(config);
}

钩子

钩子是一段可在测试运行的某些点处执行的自定义代码。钩子可以在以下测试运行点处运行:

  • 测试运行前
  • 所有测试完成后
  • 每个场景之前
  • 每个场景之后

创建一个钩子很简单。只需继承Hook并重写表示您希望在过程中运行代码的点的方法。请注意,并非所有方法都需要重写,只需重写需要运行自定义代码的点。

import 'package:gherkin/gherkin.dart';

class HookExample extends Hook {
  /// 为该钩子分配优先级。
  /// 优先级高的先运行,所以优先级为10的先运行,优先级为2的后运行
  @override
  int get priority => 1;

  /// 在任何场景在测试运行前执行
  @override
  Future<void> onBeforeRun(TestConfiguration config) async {
    print("before run hook");
  }

  /// 在所有场景在测试运行后完成时执行
  @override
  Future<void> onAfterRun(TestConfiguration config) async {
    print("after run hook");
  }

  /// 在场景及其步骤执行前运行
  @override
  Future<void> onBeforeScenario(
      TestConfiguration config, String scenario) async {
    print("running hook before scenario '$scenario'");
  }

  /// 在场景执行后运行
  @override
  Future<void> onAfterScenario(
      TestConfiguration config, String scenario) async {
    print("running hook after scenario '$scenario'");
  }
}

最后确保在配置文件中的钩子集合中添加该钩子。

import 'dart:async';
import 'package:flutter_gherkin_integration/flutter_gherkin.dart';
import 'package:gherkin/gherkin.dart';
import 'hooks/hook_example.dart';
import 'steps/given_I_pick_a_colour_step.dart';
import 'steps/tap_button_n_times_step.dart';

Future<void> main() {
  final config = FlutterTestConfiguration()
    ..features = [RegExp('features/*.*.feature')]
    ..reporters = [ProgressReporter()]
    ..hooks = [HookExample()]
    ..stepDefinitions = [TapButtonNTimesStep(), GivenIPickAColour()]
    ..restartAppBetweenScenarios = true
    ..targetAppPath = "test_driver/app.dart";
  return GherkinRunner().execute(config);
}

附件

附件是你可以在运行场景时附加的数据。这可能是简单的文本数据,甚至是截图这样的图像。这些附件可以由报告器用来提供更多的上下文信息。例如,当一个步骤失败时,可以附加一些上下文信息到场景中,然后由报告器用来显示为什么步骤失败。

附件通常通过钩子(例如onAfterStep)附加。

import 'package:gherkin/gherkin.dart';

class AttachScreenshotOnFailedStepHook extends Hook {
  /// 在步骤执行后运行
  @override
  Future<void> onAfterStep(World world, String step, StepResult stepResult) async {
    if (stepResult.result == StepExecutionResult.fail) {
      world.attach('Some info.','text/plain');
      world.attach('{"some", "JSON"}}', 'application/json');
    }
  }
}

截图

为了在步骤失败时拍摄截图,你可以使用预定义的钩子AttachScreenshotOnFailedStepHook并将其包含在测试配置的钩子配置中。这个钩子将拍摄截图并作为附件添加到场景中。如果使用JsonReporter,截图将嵌入到报告中,可以用来生成HTML报告,最终会在失败的步骤下显示截图。

import 'dart:async';
import 'package:flutter_gherkin_integration/flutter_gherkin.dart';
import 'package:gherkin/gherkin.dart';
import 'hooks/hook_example.dart';
import 'steps/colour_parameter.dart';
import 'steps/given_I_pick_a_colour_step.dart';
import 'steps/tap_button_n_times_step.dart';

Future<void> main() {
  final config = FlutterTestConfiguration()
    ..features = [RegExp('features/*.*.feature')]
    ..reporters = [
      ProgressReporter(),
      TestRunSummaryReporter(),
      JsonReporter(path: './report.json')
    ]
    ..hooks = [HookExample(), AttachScreenshotOnFailedStepHook()]
    ..stepDefinitions = [TapButtonNTimesStep(), GivenIPickAColour()]
    ..customStepParameterDefinitions = [ColourParameter()]
    ..restartAppBetweenScenarios = true
    ..targetAppPath = "test_driver/app.dart";

  return GherkinRunner().execute(config);
}

报告器

报告器是可以报告测试运行状态的类。这可以简单地将场景结果记录到控制台。该库有一系列内置报告器:

你应该在配置中提供至少一个报告器,否则很难知道发生了什么。

注:欢迎PR新的报告器!

import 'dart:async';
import 'package:flutter_gherkin_integration/flutter_gherkin.dart';
import 'steps/colour_parameter.dart';
import 'steps/given_I_pick_a_colour_step.dart';
import 'steps/tap_button_n_times_step.dart';

Future<void> main() {
  final config = FlutterTestConfiguration()
    ..features = [RegExp('features/*.*.feature')]
    ..reporters = [StdoutReporter()]
    ..stepDefinitions = [TapButtonNTimesStep(), GivenIPickAColour()]
    ..customStepParameterDefinitions = [ColourParameter()]
    ..restartAppBetweenScenarios = true
    ..targetAppPath = "test_driver/app.dart";

  return GherkinRunner().execute(config);
}

创建世界

默认值为null

虽然不建议在同一个场景中共享步骤之间的状态,但我们生活在现实世界中,因此有时可能需要共享某些信息,例如登录凭证等,以便后续步骤使用。世界上下文对象是在每个场景中创建一次,并在每个场景结束时销毁。此配置属性允许你指定一个自定义的World类来创建,然后可以在你的步骤类中访问它。

import 'dart:async';
import 'package:flutter_gherkin_integration/flutter_gherkin.dart';
import 'steps/given_I_pick_a_colour_step.dart';
import 'steps/tap_button_n_times_step.dart';

Future<void> main() {
  final config = FlutterTestConfiguration()
    ..features = [RegExp('features/*.*.feature')]
    ..reporters = [StdoutReporter()]
    ..stepDefinitions = [TapButtonNTimesStep(), GivenIPickAColour()]
    ..createWorld = (TestConfiguration config) async => await createMyWorldInstance(config)
    ..restartAppBetweenScenarios = true
    ..targetAppPath = "test_driver/app.dart";
    
  return GherkinRunner().execute(config);
}

记录Flutter进程输出

默认值为false

如果为true,Flutter进程的输出将记录到stdout / stderr流。在调试应用构建或启动故障时很有用。

Flutter构建超时

默认值为90秒

指定等待Flutter构建完成、安装应用并进入可测试状态的时间。较慢的机器可能需要超过默认的90秒才能完成此过程。

在Flutter驱动程序连接到应用之前

一个异步方法,当Flutter驱动程序尝试连接到正在测试的应用之前调用。

在Flutter驱动程序成功连接到应用之后

一个异步方法,当Flutter驱动程序成功连接到正在测试的应用之后调用。

Flutter驱动程序最大连接尝试次数

默认值为3

指定在测试中断之前对正在运行的应用尝试连接的Flutter驱动程序的最大次数。

Flutter驱动程序重新连接延迟

默认值为2秒

指定在Flutter驱动程序连接尝试失败后等待的时间,以重新连接到正在运行的应用。

特定于Flutter的配置选项

FlutterTestConfiguration将自动创建一些默认的Flutter选项,如众所周知的步骤定义、提供对Flutter驱动程序实例的访问的Flutter世界上下文对象,以及在场景之间重启您的测试应用的能力。大多数情况下,如果您正在测试Flutter应用,应使用此配置对象。

在场景之间重启应用

默认值为true

为了避免测试在一个由先前测试更改的应用上开始,建议在每个场景之间重启正在测试的Flutter应用。虽然这将略微增加执行时间,但将限制测试因运行在一个由先前测试更改的应用上而失败的情况。请注意,在更复杂的应用中,可能还需要使用AfterScenario钩子将应用重置到测试可以运行的基础状态。例如,如果重启应用会导致锁屏,则需要注销。

目标应用路径

默认值为lib/test_driver/app.dart

这应该指向能够启用Flutter驱动程序扩展的可测试应用,从而能够自动化。当测试运行开始时,此应用将启动,并且如果restartAppBetweenScenarios配置属性设置为true,则将重新启动。

构建

默认值为true

此可选参数让你指定是否应在运行第一个测试之前构建目标应用。这默认为true

在测试结束后保持应用运行

默认值为false

此可选参数将在测试完成后保持Flutter应用运行。这默认为false

构建风味

默认为空字符串

此可选参数让你指定要测试的Flutter风味。Flutter的风味类似于Android Build VariantsiOS Scheme Configuration。关于Flutter和Android/iOS方面的完整指南,请参阅flavoring flutter文档。

构建模式

默认值为BuildMode.Debug

此可选参数让你指定在编译应用时偏好的构建模式。Flutter Gherkin支持--debug--profile模式。更多详情请参阅Flutter的构建模式文档。

dartDefineArgs

默认值为[]

传递给构建参数的--dart-define参数。包括每个参数的名称和值。例如,--dart-define=MY_VAR="true"变成['MY_VAR="true"']

目标设备ID

默认为空字符串

此可选参数允许你指定设备的目标ID,如同flutter run --device-id命令。要列出已连接的设备,请运行flutter devices。如果你只有一个设备连接,则不需要提供此参数。

应用运行协议端点URI

一个观察者URL,测试运行器可以连接到而不是创建新的正在运行的应用实例 该URL的形式为http://127.0.0.1:51540/EM72VtRsUV0=/,并且通常在标准输出中以Connecting to service protocol: http://127.0.0.1:51540/EM72VtRsUV0=/的形式打印出来。 你需要在启动Flutter应用时添加--verbose标志才能看到此输出,并确保运行的应用调用了enableFlutterDriverExtension()

特征文件

步骤定义

步骤定义是特征文件中一个文本步骤的编码表示。每个步骤都以GivenThenWhenAndBut开头。需要注意的是,所有的步骤实际上是相同的,但在语义上有所不同。关键字在匹配步骤时不考虑。因此,下面两个步骤实际上被视为相同,并将导致调用相同的步骤定义。

注意:在本实现中,步骤最多允许5个输入参数。如果你发现自己需要更多,你可能需要考虑将步骤做得更独立或者使用Table参数。

Given there are 6 kangaroos
Then there are 6 kangaroos

然而,你选择的领域语言会影响在每个上下文中最佳的工作关键字。更多信息请参阅:https://docs.cucumber.io/gherkin/reference/#steps

Given

Given步骤用于描述系统的初始状态。执行一个Given步骤通常会将系统置于一个明确定义的状态。

要实现一个Given步骤,你可以继承Given类。

Given Bob has logged in

实现如下:

import 'package:gherkin/gherkin.dart';

StepDefinitionGeneric GivenWellKnownUserIsLoggedIn() {
  return given1(
    RegExp(r'(Bob|Mary|Emma|Jon) has logged in'),
    (wellKnownUsername, context) async {
      // 实现你的代码
    },
  );
}

如果在一个块中需要多个Given,最好使用附加的关键字AndBut

Given Bob has logged in
And opened the dashboard

Then

Then步骤用于描述预期的结果或结果。它们通常包含一个断言,可以是通过或失败的。

Then I expect 10 apples

实现如下:

import 'package:gherkin/gherkin.dart';

StepDefinitionGeneric ThenExpectAppleCount() {
  return then1(
    'I expect {int} apple(s)',
    (count, context) async {
      // 示例代码
      final actualCount = await _getActualCount();
      context.expectMatch(actualCount, count);
    },
  );
}

断言

注意

expect库目前仅在其自己的test函数块中工作;因此,如果将其与Then步骤一起使用,将导致错误。因此,添加了expectMatchexpectAthis.expectcontext.expect方法,这些方法模仿了except的基本功能,即断言给定的条件为真。Dart测试库中的Matcher仍然可以使用。

步骤超时

默认情况下,如果步骤超出配置文件中的defaultTimeout参数,步骤将超时。在某些情况下,你可能希望拥有一个较长或较短的运行步骤,因此在这种情况下,你可以选择性地为该步骤提供自定义超时。要做到这一点,需要在步骤的super调用中传入一个Duration对象。

例如,下面的代码将步骤的超时设置为10秒。

import 'package:flutter_driver/flutter_driver.dart';
import 'package:flutter_gherkin_integration/flutter_gherkin.dart';
import 'package:gherkin/gherkin.dart';

StepDefinitionGeneric TapButtonNTimesStep() {
  return given2<String, int, FlutterWorld>(
    'I tap the {string} button {int} times',
    (key, count, context) async {
      final locator = find.byValueKey(key);
      for (var i = 0; i < count; i += 1) {
        await FlutterDriverUtils.tap(context.world.driver, locator);
      }
    },
  );
}

多行字符串

多行字符串可以跟随步骤,并将作为最终参数传递给该步骤。要表示一个多行字符串,前缀和后缀可以是第三双引号或单引号""" ... """''' ... '''

例如:

Given I provide the following "review" comment
"""
Some long review comment.
That can span multiple lines

Skip lines

Maybe even include some numbers
1
2
3
"""

匹配的步骤定义将是:

import 'package:gherkin/gherkin.dart';

StepDefinitionGeneric GivenTheMultiLineComment() {
  return given1(
    'I provide the following {string} comment',
    (comment, context) async {
      // 实现步骤
    },
  );
}

数据表

import 'package:gherkin/gherkin.dart';

/// 此步骤期望一个跟在其后的多行字符串
///
/// 例如:
///
/// `When I add the users`
///  | Firstname | Surname | Age | Gender |
///  | Woody     | Johnson | 28  | Male   |
///  | Edith     | Summers | 23  | Female |
///  | Megan     | Hill    | 83  | Female |
StepDefinitionGeneric WhenIAddTheUsers() {
  return when1(
    'I add the users',
    (Table dataTable, context) async {
      for (var row in dataTable.rows) {
        // 对每一行做些事情
        row.columns.forEach((columnValue) => print(columnValue));
      }

      // 或者将表格作为映射(列值由标题键入)
      final columns = dataTable.asMap();
      final personOne = columns.elementAt(0);
      final personOneName = personOne["Firstname"];
      print('Name of first user: `$personOneName` ');
    },
  );
}

更多关于Flutter集成测试插件flutter_gherkin_integration的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter集成测试插件flutter_gherkin_integration的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


flutter_gherkin_integration 是一个用于 Flutter 集成测试的插件,它允许你使用 Gherkin 语法编写行为驱动开发(BDD)风格的测试。Gherkin 是一种自然语言风格的语法,通常用于描述软件的行为和功能。通过使用 flutter_gherkin_integration,你可以在 Flutter 项目中编写和执行 Gherkin 风格的集成测试。

以下是如何在 Flutter 项目中使用 flutter_gherkin_integration 的步骤:

1. 添加依赖

首先,你需要在 pubspec.yaml 文件中添加 flutter_gherkin_integration 依赖。

dev_dependencies:
  flutter_gherkin_integration: ^1.0.0
  flutter_test:
    sdk: flutter

然后运行 flutter pub get 以安装依赖。

2. 创建 Gherkin 文件

test 目录下创建一个新的目录,例如 features,并在其中创建 .feature 文件。例如,创建一个 login.feature 文件:

Feature: Login Feature
  As a user
  I want to login to the app
  So that I can access my account

  Scenario: Successful login
    Given I am on the login screen
    When I enter "username" in the username field
    And I enter "password" in the password field
    And I press the login button
    Then I should see the home screen

3. 编写步骤定义

test 目录下创建一个 steps 目录,并在其中创建 login_steps.dart 文件。在该文件中,你将定义 Gherkin 步骤的实现。

import 'package:flutter_gherkin_integration/flutter_gherkin_integration.dart';
import 'package:flutter_test/flutter_test.dart';

class LoginSteps extends Given {
  @override
  Future<void> executeStep() async {
    // 实现 Given 步骤
    expect(find.text('Login Screen'), findsOneWidget);
  }
}

class EnterUsername extends When {
  @override
  Future<void> executeStep(String username) async {
    // 实现 When 步骤
    await tester.enterText(find.byType(TextField), username);
  }
}

class EnterPassword extends When {
  @override
  Future<void> executeStep(String password) async {
    // 实现 When 步骤
    await tester.enterText(find.byType(TextField), password);
  }
}

class PressLoginButton extends And {
  @override
  Future<void> executeStep() async {
    // 实现 And 步骤
    await tester.tap(find.text('Login'));
    await tester.pump();
  }
}

class SeeHomeScreen extends Then {
  @override
  Future<void> executeStep() async {
    // 实现 Then 步骤
    expect(find.text('Home Screen'), findsOneWidget);
  }
}

4. 配置测试运行器

test 目录下创建一个 test_driver 目录,并在其中创建 gherkin_test.dart 文件。在该文件中,你将配置 Gherkin 测试运行器。

import 'package:flutter_gherkin_integration/flutter_gherkin_integration.dart';
import 'package:flutter_test/flutter_test.dart';
import '../steps/login_steps.dart';

void main() {
  final config = FlutterTestConfiguration()
    ..features = ['test/features/login.feature']
    ..steps = [
      LoginSteps(),
      EnterUsername(),
      EnterPassword(),
      PressLoginButton(),
      SeeHomeScreen(),
    ];

  executeTests(config);
}

5. 运行测试

你可以使用以下命令运行集成测试:

flutter drive --target=test_driver/gherkin_test.dart
回到顶部