| name | vitest-bdd-3layer |
| description | DashPlayer 三层 Vitest-BDD 技能。Use when developers want business-readable BDD scenarios without coupling scenario writing to code implementation details. Triggers on: "BDD 三层", "行为测试分层", "给定当那么", "Vitest BDD", "不想在场景里写实现". |
目的
在 DashPlayer 中用 Vitest + 三层模型 编写 BDD 测试,让“场景编写”与“实现细节”解耦。
目标效果:
- 场景层只写业务句子(给定/当/那么)
- 步骤层维护业务动作词典
- 实现层收敛 mock / fixture / adapter
适用范围
- 业务规则清晰、需要可读验收场景的模块
- service + IPC + renderer 契约验证
- 希望后续可扩展为更多业务域(settings / ai-trans / watch-history)
不适用:
- 纯算法工具函数(普通单测更高效)
- 高波动、易 flaky 的强时序用例
三层目录规范(必须遵守)
以 settings 为例:
-
场景层(Scenario Layer)
src/backend/application/services/__tests__/Xxx.bdd.test.ts
- 只能写
given/when/then 业务语句
- 禁止直接
new Service、vi.spyOn、store.set/get
-
步骤词典层(Step Dictionary Layer)
src/backend/application/services/__tests__/bdd/xxx.steps.ts
- 用英文方法名组织能力:
given* / when* / then*
- 场景层仅调用步骤层方法
-
实现层(Fixture/Adapter Layer)
src/backend/application/services/__tests__/bdd/xxx.fixture.ts
- 统一管理 mock、fixture、依赖注入与底层调用
工具类约定
复用:src/test/bdd.ts
推荐导入方式(避免 ESM then 命名导出陷阱):
import bdd from '@/test/bdd'
const { scenario, given, when, then } = bdd
标准落地流程
1) 先写场景清单(业务句子)
每条场景必须是一句中文:
给定<前置条件>,当<触发动作>,那么<业务结果>
2) 定义步骤词典方法
使用英文命名,示例:
givenInvalidProviderValues()
whenQueryEngineSelection()
thenProvidersNormalizedToNone()
规则:
given* 只做前置状态准备
when* 只触发行为
then* 只做业务结果断言
3) 在实现层补齐 fixture
实现层负责:
- service 实例创建
- store/mock 初始化
- spy 管理(如
fs.existsSync)
- 数据读写 helper(
setValue/getValue)
4) 场景层只编排,不实现
场景层示例:
scenario('设置:引擎选择行为', () => {
it('给定 provider 值非法,当查询引擎选择,那么应归一化为 none', async () => {
await given('给定:provider 值非法', () => steps.givenInvalidProviderValues())
await when('当:查询引擎选择', () => steps.whenQueryEngineSelection())
await then('那么:应归一化为 none', () => steps.thenProvidersNormalizedToNone())
})
})
评审清单(8项)
常见误用(必须避免)
- 在场景层直接写 mock 或访问 store
- 步骤方法名中英混用(触发命名规则告警)
- 一个场景覆盖多个业务承诺
- 只断言内部调用次数,不断言业务结果
- 步骤层直接包含大量底层构造逻辑(应下沉实现层)
运行命令
yarn test:bdd
yarn test:bdd:watch
扩展建议
- 新业务域按同样三层创建:
xxx.bdd.test.ts
bdd/xxx.steps.ts
bdd/xxx.fixture.ts
- 优先复用
src/test/bdd.ts,不要重复造 DSL。