mcpField
D
dart-matcher-best-practices

dart-matcher-best-practices

github.com/kevmoo测试与质量148266 次安装2 周前更新
代码仓库

关于 dart-matcher-best-practices

用于使用 package:matcher 编写和改进 Dart 测试的技能,涵盖集合、类型、属性、异步和异常断言,并说明何时不应重写现有检查。

主要功能

  1. 01选择集合匹配器
  2. 02检查类型与对象属性
  3. 03断言异步结果
  4. 04使用 throwsA 测试异常
  5. 05识别不应重构的情况

适用场景

  1. 01用 hasLength 替换手动长度检查
  2. 02改进 Map 查找断言
  3. 03测试 Future 结果与异常

安装

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

$npx skills add https://github.com/kevmoo/dash_skills --skill dart-matcher-best-practices

安装后在 Claude Code 中输入/dart-matcher-best-practices确认已加载。

附带文件evals/evals.json

详细介绍

Dart 匹配器最佳实践

何时使用此技能

在以下情况下使用此技能:

  • 使用 expect 和 package:matcher 编写断言。
  • 将旧式手动检查迁移为更简洁的匹配器。
  • 调试令人困惑的测试失败。
何时不使用(规避规则)

在以下情况下,不要应用此技能或重构断言:

  • 非集合属性:属性碰巧名为 .length,但属于非集合类(例如,Vector 的几何标量大小、缓冲区的持续时间或线段距离)。hasLength(n) 期望接收 Iterable、Map 或 String 上的 int。
  • 自定义领域布尔值:布尔检查测试的是任意领域属性(例如,expect(switch.isEnabled, isTrue))。不要臆造不存在的匹配器。
  • 其他断言框架:包已采用 package:checks(check(x)...)。不要将 checks 改回 package:matcher。
  • 已符合惯用写法的断言:测试已使用声明式匹配器(hasLength、isEmpty、isNotEmpty、contains、containsPair、isA、throwsA)。不要处理,也不要做任何修改。

发现候选项

要查找可改进匹配器用法的候选项,请搜索以下欠佳模式:

欠佳的长度检查

搜索应使用 hasLength 的长度检查:

  • 正则表达式:expect\([^,]+.length,\s*
欠佳的布尔值检查

搜索对具有专用匹配器的布尔属性进行的检查:

  • 正则表达式:expect\([^,]+.isEmpty,\s*(true|equals\(true\))
  • 正则表达式:expect\([^,]+.isNotEmpty,\s*(true|equals\(true\))
  • 正则表达式:expect\([^,]+.contains\(.*\),\s*(true|equals\(true\))
欠佳的 Map 查找

搜索未使用 containsPair 的手动 Map 查找:

  • 正则表达式:expect\([^,]+\[.*\],\s*

核心匹配器

1. 集合(hasLength、contains、isEmpty、unorderedEquals、containsPair)
  • hasLength(n):

    • 优先使用 expect(list, hasLength(n)),而不是 expect(list.length, n)。
    • 失败时会提供更好的错误消息(显示实际列表内容)。
  • isEmpty / isNotEmpty:

    • 优先使用 expect(list, isEmpty),而不是 expect(list.isEmpty, true)。
    • 优先使用 expect(list, isNotEmpty),而不是 expect(list.isNotEmpty, true)。
  • contains(item):

    • 无需手动迭代即可验证元素是否存在。
    • 优先于 expect(list.contains(item), true)。
  • unorderedEquals(items):

    • 无论顺序如何,都能验证内容。
    • 优先于 expect(list, containsAll(items))。
  • containsPair(key, value):

    • 验证 Map 是否包含特定的键值对。
    • 优先于检查 expect(map[key], value) 或 expect(map.containsKey(key), true)。
2. 类型检查(isA<T> 和 TypeMatcher<T>)
  • isA<T>():

    • 优先用于内联断言:expect(obj, isA<Type>())。
    • 比 TypeMatcher<Type>() 更简洁易读。
    • 支持使用 .having() 链式添加约束。
  • TypeMatcher<T>:

    • 定义顶层可复用匹配器时优先使用。
    • 使用 const:const isMyType = TypeMatcher<MyType>();
    • 这里也可以链式调用 .having(),但生成的匹配器不是 const。
3. 对象属性(having)

在 isA<T>() 或其他 TypeMatcher 上使用 .having() 检查属性。

  • 描述性名称:在闭包中使用有意义的参数名称(例如, (e) => e.message),而不是 p0 等通用名称,以提高可读性。
expect(person, isA<Person>()
    .having((p) => p.name, 'name', 'Alice')
    .having((p) => p.age, 'age', greaterThan(18)));

这会提供详细的失败消息,准确指出哪个属性检查失败。

4. 异步断言
  • completion(matcher):

    • 等待 Future 完成,并检查其值。
    • 优先使用 await expectLater(...),确保 Future 完成后再继续测试。
    • await expectLater(future, completion(equals(42)))。
  • throwsA(matcher):

    • 检查 Future 或函数是否抛出异常。
    • await expectLater(future, throwsA(isA<StateError>()))。
    • expect(() => function(), throwsA(isA<ArgumentError>()))(同步函数抛出异常时,可以使用 expect)。
5. 异常测试(避免使用 try/catch 和 fail())
  • 优先使用 throwsA,而不是命令式的 try/catch:
    • 旧版测试通常会使用 try { fn(); fail('should throw'); } catch (e) { expect(e, isA<T>()); }。
    • 替换为声明式匹配器:
      // 同步:
      expect(() => parseData('invalid'), throwsA(isA<FormatException>()));
      
      // 异步:
      await expectLater(client.fetch('bad-url'), throwsA(isA<HttpException>()));
      
    • 常见的内置异常匹配器包括 throwsArgumentError、 throwsStateError、throwsRangeError、throwsFormatException、 throwsNoSuchMethodError 和 throwsUnsupportedError。
6. 使用 expectLater

测试异步行为时,使用 await expectLater(...) 确保执行顺序正确。

// GOOD: Waits for future to complete before checking side effects
await expectLater(future, completion(equals(42)));
expect(sideEffectState, equals('done'));

// BAD: Side effect check might run before future completes
expect(future, completion(equals(42)));
expect(sideEffectState, equals('done')); // Race condition!

原则

  1. 失败信息清晰易读:选择能够生成清晰错误信息的匹配器。
  2. 避免手动逻辑:不要使用 if 语句或 for 循环进行断言;交由匹配器处理。
  3. 使用具体的匹配器:使用最具体的可用匹配器(例如,对于映射使用 containsPair,而不是手动检查键)。

相关技能

评论

支持 Markdown