关于 dart-matcher-best-practices
用于使用 package:matcher 编写和改进 Dart 测试的技能,涵盖集合、类型、属性、异步和异常断言,并说明何时不应重写现有检查。
主要功能
- 01选择集合匹配器
- 02检查类型与对象属性
- 03断言异步结果
- 04使用 throwsA 测试异常
- 05识别不应重构的情况
适用场景
- 01用 hasLength 替换手动长度检查
- 02改进 Map 查找断言
- 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!
原则
- 失败信息清晰易读:选择能够生成清晰错误信息的匹配器。
- 避免手动逻辑:不要使用
if语句或for循环进行断言;交由匹配器处理。 - 使用具体的匹配器:使用最具体的可用匹配器(例如,对于映射使用
containsPair,而不是手动检查键)。
相关技能
- dart-test-fundamentals:有关测试结构、生命周期和配置的核心概念。
评论
支持 Markdown