关于 dart-test-fundamentals
dart-test-fundamentals 是面向 Dart 开发者的测试组织与配置技能,涵盖测试结构、生命周期和清理,并说明何时不应套用相关规范。
主要功能
- 01使用 test 和 group 组织测试
- 02应用 setup 与 teardown 生命周期方法
- 03配置平台、标签和超时
- 04采用资源清理与测试替身
- 05遵循断言和测试接缝指南
适用场景
- 01编写或整理 Dart 测试套件
- 02在 dart_test.yaml 中配置测试执行
- 03选择测试资源的清理方式
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ kevmoo/ dash_skills --skill dart-test-fundamentals安装后在 Claude Code 中输入/dart-test-fundamentals确认已加载。
附带文件evals/evals.json
详细介绍
Dart 测试基础
何时使用此技能
适用于以下情况:
- 编写新的测试文件。
- 使用
group组织测试套件。 - 通过
dart_test.yaml配置测试执行。 - 了解测试生命周期方法。
何时不使用(弃用防护规则)
遇到以下情况时,请勿使用此技能,也不要重构现有测试:
- 旧式单分组变更:除非明确要求,否则不要删除或重新格式化未修改的现有测试中的
group层级,因为这会造成不必要的差异变更。 - 其他断言框架:如果软件包已迁移到
package:checks或专用测试框架,请勿将测试改回旧式package:matcher用法。 - 无需设置的简单测试:没有共享状态或资源的简单独立测试不需要
group、setUp或addTearDown。不要添加形式主义的包装样板代码。
发现
查找可改进测试结构的候选项:
try-finally 清理
搜索使用 try-finally 清理,而非 addTearDown 的测试:
- 正则表达式:
\bfinally\s*\{(检查它是否用于测试中的资源清理)。
核心概念
1. 测试结构(test 和 group)
-
test:测试的基本单元。test('description', () { // assertions }); -
group:用于将测试组织成逻辑区块。- 可以嵌套分组。
- 描述会串联起来(例如,“分组描述 测试描述”)。
- 有助于限定
setUp和tearDown调用的作用范围。 - 命名:对于对应类名的分组,请使用
PascalCase(例如,group('MyClient', ...))。 - 避免单一分组:如果一个文件中只有一个
group调用,请勿将所有测试都包装在该分组中。- 注意:清理你没有创建的现有代码时,除非明确要求,否则不要删除分组。这可能会造成大量差异,令大多数工程师不愿接受!
-
命名测试
test('此处填写测试名称',:- 避免添加多余的“test”前缀。请改用
group。 - 在描述中写明预期行为或结果(例如,
'抛出 StateError'或'将 API 密钥添加到 URL')。 - 描述与分组名称串联后应通顺易读。
- 避免添加多余的“test”前缀。请改用
-
命名参数的位置:
- 对于
test和group调用,应将命名参数(例如,testOn、timeout、skip)放在描述字符串之后、回调闭包之前。这样可将测试逻辑放在最后,提高可读性。test('描述', testOn: 'vm', () { // assertions });
- 对于
2. 生命周期方法(setUp、tearDown)
setUp:在当前group(及其嵌套分组)中的每个test之前运行。tearDown:在当前group中的每个test之后运行。setUpAll:在该分组中的任何测试开始之前运行一次。tearDownAll:在该分组中的所有测试结束后运行一次。
最佳实践:
- 使用
setUp重置状态,以确保测试隔离。 - 避免在测试之间共享可变状态,除非每次都会重置。
3. 清理资源
- 清理在
test函数体内创建的资源时,可以考虑使用addTearDown,而非try-finally代码块。
避免:
test('can create and delete a file', () {
final file = File('temp.txt');
try {
file.writeAsStringSync('hello');
expect(file.readAsStringSync(), 'hello');
} finally {
if (file.existsSync()) file.deleteSync();
}
});
推荐:
test('can create and delete a file', () {
final file = File('temp.txt');
// Register teardown immediately after resource creation intent
addTearDown(() {
if (file.existsSync()) file.deleteSync();
});
file.writeAsStringSync('hello');
expect(file.readAsStringSync(), 'hello');
});
4. 配置(dart_test.yaml)
dart_test.yaml 文件用于配置测试运行器。常见配置包括:
平台
定义测试的运行平台(vm、chrome、node)。
platforms:
- vm
- chrome
标签
对测试进行分类,以便运行特定子集。
tags:
integration:
timeout: 2x
代码中的用法:
@Tags(['integration'])
import 'package:test/test.dart';
运行带标签的测试:dart test --tags integration
超时
设置测试的默认超时时间。
timeouts:
2x # Double the default timeout
5. 文件命名
- 测试文件必须以
_test.dart结尾,才能被测试运行器识别。 - 将测试放在
test/目录中。
6. 测试设计、接缝与真实测试替身
- 测试接缝规范(包集成测试与
lib/src/单元测试):- 包级测试与集成测试:导入
package:<pkg>/<pkg>.dart(公共入口接缝),并严格限定lib/<pkg>.dart导出的范围,仅供 公共使用者使用。 - 子系统单元测试(
lib/src/):导入内部的深层模块 (package:<pkg>/src/<subsystem>.dart)——例如未导出的解析器、状态 机、数据模型或接口简单、内部行为丰富的算法——以便直接对边界情况进行 单元测试;通过所属模块的入口测试仅有一个调用方的轻量辅助函数。
- 包级测试与集成测试:导入
- 真实实现与一方伪件(“可能失败的测试”):使用真实依赖和一方伪件进行测试,确保生产契约遭到破坏时测试会失败——文件系统 I/O 使用
package:test_descriptor(d.sandbox、d.dir、d.file)或Directory.systemTemp.createTempSync()(始终使用addTearDown(() => tempDir.deleteSync(recursive: true))注册清理);服务使用内存数据库或环回HttpServer实例;HTTP 使用package:http/testing.dart(MockClient);自定义接口使用手写伪件/存根;DOM 和 JS/Wasm 互操作使用@TestOn('browser')。 - 确定性的异步处理与匹配器诊断(
fakeAsync与package:checks):避免在单元测试中使用真实的await Future.delayed(...)延时(这会导致测试不稳定并拖慢测试套件);优先使用fakeAsync(package:fake_async)、可控的Completer,或注入时钟/运行器回调。使用emitsInOrder/expectLater验证Stream预期,而不要使用不传播错误的未等待stream.listen(expectAsync1(...));优先使用具体的匹配器 (throwsA(isA<StateError>().having((e) => e.message, 'message', contains('...'))),completion(...)、unorderedEquals(...)),而不是expect(x == y, isTrue)或 手动编写try { ...; fail('...'); } catch (e) {}代码块。 - 行为、结构和关系断言(不编写变更检测测试):
- 使用合成输入测试使用方行为:使用合成的单元测试输入,断言会_使用_常量、配置和模型的代码的可观察输出、状态转换和边界条件(例如,向验证器传入 280 个字符与 281 个字符,或向仓库分类器传入合成临时目录),而不是编写同义反复的变更检测测试,重复常量字面值、固定静态配置集中的项目成员关系(
publishedPackages.contains('foo'))、硬编码具名生产实体(classifyRepo('specific-repo')),或在单个静态模板常量中用子串匹配字面操作名称、CLI 标志或固定的工具版本/SHA。更新配置集、模板常量或数据集导致现有变更检测断言失败时,删除该变更检测断言,而不是修改其中的字面值以匹配。 - 对实时数据和模板断言结构与关系不变量:测试会独立于逻辑演进的实时目录、JSON 数据集、注册表或规范模板时,断言结构有效性和跨构件关系不变量(例如,
isNotEmpty、存在必需的架构键、mergedCount + openCount == totalCount,或工作流模板作业 ID 与分支保护检查名称一致),而不是硬编码条目总数(hasLength(33))、字面配置项或单个常量模板子串(确定性单元测试输入除外,可断言精确的数量/值相等)。
- 使用合成输入测试使用方行为:使用合成的单元测试输入,断言会_使用_常量、配置和模型的代码的可观察输出、状态转换和边界条件(例如,向验证器传入 280 个字符与 281 个字符,或向仓库分类器传入合成临时目录),而不是编写同义反复的变更检测测试,重复常量字面值、固定静态配置集中的项目成员关系(
- 直接执行与渲染验证:通过调用函数、运行 CLI 命令或直接渲染组件,验证运行时行为、控制流和 UI/CLI 输出。仅在检查静态文档漂移(
README.md中的--help代码块)、构建/包元数据同步(BUILD、pubspec.yaml)以及代码生成器输入/输出测试夹具时,使用原始文件文本读取(readAsStringSync())。
常用命令
dart test:运行所有测试。dart test test/path/to/file_test.dart:运行指定文件。dart test --name "substring":运行描述匹配的测试。
相关技能
dart-test-fundamentals 是组织和配置测试的核心技能。要在这些测试中编写断言,请参阅:
- dart-matcher-best-practices:如果项目使用传统的
package:matcher(expect调用),请使用此技能。
评论
支持 Markdown