mcpField
D
dart-migrate-to-checks-package

dart-migrate-to-checks-package

github.com/dhruvanbhalara测试与质量3046 次安装9 周前更新
代码仓库

关于 dart-migrate-to-checks-package

dart-migrate-to-checks-package 是一项指导 Dart 测试从 package:matcher 迁移到 package:checks 的技能,适用于维护测试套件的开发者。它提供断言写法映射,并涵盖 Future 与 Stream 的异步断言。

主要功能

  1. 01映射常见断言语法
  2. 02指导依赖与导入调整
  3. 03支持级联属性检查
  4. 04说明 Future 异步断言
  5. 05说明 Stream 顺序断言
  6. 06提供迁移验证清单

适用场景

  1. 01将 expect 断言迁移为 check
  2. 02改写 Future 和 Stream 测试
  3. 03检查迁移后的分析与测试

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dhruvanbhalara/skills --skill dart-migrate-to-checks-package

安装后在 Claude Code 中输入/dart-migrate-to-checks-package确认已加载。

详细介绍

目录

为何迁移到 package:checks

package:checks 是由 Dart 团队维护的现代断言库,用于取代 package:matcher。与传统匹配器相比,它有以下优点:

  • 流畅的 API:使用 Dart 方法自然地串联断言,而不是嵌套调用辅助函数。
  • 类型安全:利用编译器检查预期属性,防止测试中出现类型不匹配。
  • 清晰的诊断信息:失败消息非常清晰,会准确指出失败的内容和原因(例如,显示集合之间的差异)。

依赖管理

要将 checks 库添加到你的包中:

  1. 使用终端将 checks 添加为 dev_dependency:
    dart pub add dev:checks
    
  2. 在所有正在迁移的测试文件中导入主包入口:
    import 'package:checks/checks.dart';
    
  3. 从 pubspec.yaml 中移除显式的 package:matcher 依赖(注意,package:test 会传递导入匹配器,这是可以接受的)。

断言映射与语法指南

按照以下映射指南迁移旧版断言:

场景旧版匹配器(package:matcher)现代 checks(package:checks)
相等性expect(value, equals(expected))check(value).equals(expected)
标识相同expect(value, same(expected))check(value).identicalTo(expected)
类型检查expect(value, isA<Type>())check(value).isA<Type>()
空值检查expect(value, isNull)check(value).isNull()
布尔值expect(value, isTrue)check(value).isTrue()
包含检查expect(list, contains(item))check(list).contains(item)
字符串前缀expect(str, startsWith(prefix))check(str).startsWith(prefix)
属性检查expect(user.name, equals('Alice'))check(user).has((u) => u.name, 'name').equals('Alice')
串联多个检查

使用 Dart 的级联运算符(..),即可对同一个对象断言多个属性,而无需编写单独的期望语句:

check(user)
  ..has((u) => u.name, 'name').equals('Alice')
  ..has((u) => u.age, 'age').isGreaterThan(20);

高级异步断言

1. Future

要验证 Future 的解析结果,始终对基础 check 语句使用 await:

// Verify completion value:
await check(fetchScore()).completes((score) => score.equals(100));

// Verify throws error:
await check(failingTask()).throws<HttpException>().has((e) => e.statusCode, 'statusCode').equals(500);
2. Stream

要断言 Stream 发出的多个连续项目,请使用 package:async 中的 StreamQueue 包装目标流:

import 'package:async/async.dart';

final queue = StreamQueue(eventStream);
await check(queue).emits((event) => event.equals('first_event'));
await check(queue).emits((event) => event.equals('second_event'));

工作流:执行语法迁移

迁移测试套件时,请遵循以下检查清单:

  • 依赖设置:安装 dev:checks 并运行 dart pub get。
  • 查找目标文件:定位包含传统 expect() 调用的测试文件。
  • 更新导入:替换 matcher 导入,或添加 import 'package:checks/checks.dart'。
  • 转换简单用例:将基本相等性检查和类型匹配从 expect 映射为 check。
  • 使用级联优化:将针对同一对象的多个断言组合到级联表达式中。
  • 更新异步调用:重写 Future/Stream 异步检查,使用 await 和 completes() 闭包。
  • 验证编译器检查:运行 dart analyze,解决任何类型不匹配警告。
  • 执行测试:运行 dart test,验证所有迁移后的断言均能成功执行。

示例

迁移对比

此示例演示如何将一个完整且高度保真的用户档案测试用例从旧版匹配器语法迁移到现代 checks 语法。

// --- Legacy Matcher Assertion Style ---
import 'package:test/test.dart';

class Account {
  final String email;
  final List<String> tags;
  Account(this.email, this.tags);
}

void badTest() {
  final account = Account('[email protected]', ['admin', 'tier1']);

  expect(account.email, equals('[email protected]'));
  expect(account.tags, contains('admin'));
  expect(account.tags.length, equals(2));
}

// --- Modern Checks Assertion Style ---
import 'package:checks/checks.dart';

void goodTest() {
  final account = Account('[email protected]', ['admin', 'tier1']);

  // Chain validations cleanly using cascades and property extractors
  check(account)
    ..has((a) => a.email, 'email').equals('[email protected]')
    ..has((a) => a.tags, 'tags').contains('admin')
    ..has((a) => a.tags.length, 'tags length').equals(2);
}
测试异常和 Future 完成
import 'package:checks/checks.dart';
import 'package:test/test.dart';

Future<String> fetchRole(bool fail) async {
  await Future.delayed(const Duration(milliseconds: 10));
  if (fail) throw StateError('Fetch failed');
  return 'Manager';
}

void main() {
  group('Asynchronous Tests', () {
    test('verifies successful completion', () async {
      await check(fetchRole(false)).completes(
        (role) => role.equals('Manager'),
      );
    });

    test('verifies throwing specific state errors', () async {
      await check(fetchRole(true)).throws<StateError>().has(
        (e) => e.message, 'message',
      ).equals('Fetch failed');
    });
  });
}

评论

支持 Markdown