Flutter模式匹配插件matcher的使用

Flutter模式匹配插件matcher的使用

简介

matcher 是一个用于指定测试期望(如单元测试)的支持包。它提供了一套第三代断言机制,灵感来源于 Hamcrest。通过 matcher,可以编写更具表达力和可读性的测试代码。

更多关于测试的信息,请参阅 Unit Testing with Dart

使用 matcher

基本用法

所有的期望都以调用 expect()expectAsync() 开始。可以使用任何 matchers 包与 expect() 结合来进行复杂的验证:

import 'package:test/test.dart';

void main() {
  test('.split() splits the string on the delimiter', () {
    expect('foo,bar,baz', allOf([
      contains('foo'),
      isNot(startsWith('bar')),
      endsWith('baz')
    ]));
  });
}

如果传递的是非 matcher 的值,它将被 equals() 包装。

异常匹配器

可以使用 throwsA() 函数或类似 throwsFormatException 的 matcher 来测试异常:

import 'package:test/test.dart';

void main() {
  test('.parse() fails on invalid input', () {
    expect(() => int.parse('X'), throwsFormatException);
  });
}

Future 匹配器

对于更高级的异步操作,matcher 提供了多种有用的函数和 matcher。completion() matcher 可以用来测试 Future;它确保测试不会在 Future 完成之前结束,并对 Future 的值运行 matcher。

import 'dart:async';
import 'package:test/test.dart';

void main() {
  test('Future.value() returns the value', () {
    expect(Future.value(10), completion(equals(10)));
  });

  test('Future.error() throws the error', () {
    expect(Future.error('oh no'), throwsA(equals('oh no')));
    expect(Future.error(StateError('bad state')), throwsStateError);
  });
}

Stream 匹配器

test 包提供了强大的 matcher 来处理 异步流。这些 matcher 表达力强且可组合,使得编写关于流发出值的复杂期望变得简单。

例如:

import 'dart:async';
import 'package:test/test.dart';

void main() {
  test('process emits status messages', () {
    var stdoutLines = Stream.fromIterable([
      'Ready.',
      'Loading took 150ms.',
      'Succeeded!'
    ]);

    expect(stdoutLines, emitsInOrder([
      'Ready.',
      startsWith('Loading took'),
      emitsAnyOf(['Succeeded!', 'Failed!']),
      emitsDone
    ]));
  });
}

流 matcher 还可以匹配 async 包的 StreamQueue 类,允许从流中请求事件而不是推送给消费者。

import 'dart:async';
import 'package:async/async.dart';
import 'package:test/test.dart';

void main() {
  test('process emits a WebSocket URL', () async {
    var stdout = StreamQueue(Stream.fromIterable([
      'WebSocket URL:',
      'ws://localhost:1234/',
      'Waiting for connection...'
    ]));

    await expectLater(stdout, emitsThrough('WebSocket URL:'));

    var url = Uri.parse(await stdout.next);
    expect(url.host, equals('localhost'));

    await expectLater(stdout, emits('Waiting for connection...'));
  });
}

内置的流 matcher 包括:

  • emits():匹配单个数据事件。
  • emitsError():匹配单个错误事件。
  • emitsDone:匹配单个完成事件。
  • mayEmit():如果匹配内部 matcher,则消耗事件。
  • mayEmitMultiple():尽可能多次匹配事件。
  • emitsAnyOf():匹配多个可能的 matcher 中的一个或多个。
  • emitsInOrder():按顺序匹配多个 matcher。
  • emitsInAnyOrder():以任意顺序匹配多个 matcher。
  • neverEmits():匹配在没有匹配内部 matcher 的情况下完成的流。

还可以使用 StreamMatcher() 定义自定义流 matcher。

最佳实践

优先使用语义上有意义的 matcher 而不是比较派生值

具有测试知识的 matcher 可以生成更有意义的消息,不需要阅读测试源码就能理解测试失败的原因。例如,比较 expect(someList.length, 1)expect(someList, hasLength(1)) 的失败信息:

// expect(someList.length, 1);
Expected: <1>
Actual: <2>

// expect(someList, hasLength(1));
Expected: an object with length of <1>
Actual: ['expected value', 'unexpected value']
Which: has length of <2>

优先使用 TypeMatcher 而不是 predicate,如果匹配可以有多种失败方式

predicate 工具是测试任意(同步)值属性的便捷快捷方式,但它会丢弃上下文,失败信息不透明。不同的失败模式无法在输出中区分,因为输出由单一的 “description” 参数决定。使用 isA<SomeType>()TypeMatcher.having API 以结构化的方式提取和测试派生属性,可以将该结构的上下文带入失败消息,从而使不同原因的失败消息可区分且可操作。

以上就是 matcher 插件在 Flutter 中的使用方法和一些最佳实践。希望对你有所帮助!


更多关于Flutter模式匹配插件matcher的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter模式匹配插件matcher的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何在Flutter项目中使用matcher插件进行模式匹配的代码示例。matcher插件主要用于测试框架中的匹配器,但它在处理字符串模式匹配时也非常有用,特别是与RegExp结合使用时。

首先,确保你的pubspec.yaml文件中已经包含了test依赖,因为matcher库是作为test包的一部分提供的。如果你正在编写测试代码,这通常是默认包含的。如果你需要在生产代码中使用一些匹配功能,你可能只需要直接使用Dart的RegExp类。不过,这里我们还是以matcher的常用方式展示。

# pubspec.yaml
dependencies:
  flutter:
    sdk: flutter

dev_dependencies:
  test: ^1.20.1  # 确保版本是最新的,或者根据需要调整

然后,你可以在你的测试文件中使用matcher进行模式匹配。以下是一个简单的示例,展示如何使用matches匹配器来检查字符串是否符合特定的正则表达式模式。

// test/matcher_example_test.dart

import 'package:test/test.dart';

void main() {
  test('检查字符串是否符合电子邮件模式', () {
    String email = 'test@example.com';
    RegExp emailPattern = RegExp(
        r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$');

    // 使用 matcher 库的 matches 匹配器
    expect(email, matches(emailPattern));
  });

  test('检查字符串是否符合电话号码模式', () {
    String phoneNumber = '+1234567890';
    RegExp phonePattern = RegExp(r'^\+\d{10,15}$');

    // 使用 matcher 库的 matches 匹配器
    expect(phoneNumber, matches(phonePattern));
  });

  test('检查字符串是否包含特定单词', () {
    String text = 'Flutter is awesome!';
    expect(text, contains('Flutter'));
    // 注意:contains 不是一个 matcher 库的特定功能,但它经常与 matcher 库一起使用
  });
}

在这个示例中,我们定义了三个测试:

  1. 检查字符串是否符合电子邮件模式:使用RegExp定义一个电子邮件模式的正则表达式,并使用expect函数和matches匹配器来检查字符串是否符合该模式。
  2. 检查字符串是否符合电话号码模式:类似地,我们定义了一个电话号码模式的正则表达式,并进行匹配。
  3. 检查字符串是否包含特定单词:虽然contains不是matcher库特有的功能,但它经常与matcher库一起用于字符串测试。这里只是为了展示多种匹配方式。

运行这些测试时,Flutter测试框架会验证每个expect语句,如果字符串不符合指定的模式或条件,测试将失败。

你可以通过运行flutter test命令来执行这些测试。确保你的测试文件位于test目录下,这样Flutter测试框架才能正确识别并执行它们。

回到顶部