vitest-bdd-3layer
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinese目的
Purpose
在 DashPlayer 中用 Vitest + 三层模型 编写 BDD 测试,让“场景编写”与“实现细节”解耦。
目标效果:
- 场景层只写业务句子(给定/当/那么)
- 步骤层维护业务动作词典
- 实现层收敛 mock / fixture / adapter
Write BDD tests in DashPlayer using Vitest + Three-layer Model to decouple "scenario writing" from "implementation details".
Target Outcomes:
- Scenario layer only contains business sentences (Given/When/Then)
- Step layer maintains a dictionary of business actions
- Implementation layer centralizes mocks / fixtures / adapters
适用范围
Scope of Application
- 业务规则清晰、需要可读验收场景的模块
- service + IPC + renderer 契约验证
- 希望后续可扩展为更多业务域(settings / ai-trans / watch-history)
不适用:
- 纯算法工具函数(普通单测更高效)
- 高波动、易 flaky 的强时序用例
- Modules with clear business rules that require readable acceptance scenarios
- Contract verification for service + IPC + renderer
- Modules expected to be extended to more business domains later (settings / ai-trans / watch-history)
Not Applicable:
- Pure algorithm utility functions (regular unit tests are more efficient)
- Highly volatile, easily flaky strong timing use cases
三层目录规范(必须遵守)
Mandatory Three-layer Directory Structure
以 为例:
settings-
场景层(Scenario Layer)
src/backend/application/services/__tests__/Xxx.bdd.test.ts- 只能写 业务语句
given/when/then - 禁止直接 、
new Service、vi.spyOnstore.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、依赖注入与底层调用
Take as an example:
settings-
Scenario Layer
src/backend/application/services/__tests__/Xxx.bdd.test.ts- Only write business statements
given/when/then - Direct use of ,
new Service,vi.spyOnis prohibitedstore.set/get
-
Step Dictionary Layer
src/backend/application/services/__tests__/bdd/xxx.steps.ts- Organize capabilities with English method names:
given* / when* / then* - Scenario layer only calls methods from the step layer
-
Fixture/Adapter Layer
src/backend/application/services/__tests__/bdd/xxx.fixture.ts- Unified management of mocks, fixtures, dependency injection and underlying calls
工具类约定
Utility Class Conventions
复用:
src/test/bdd.ts推荐导入方式(避免 ESM 命名导出陷阱):
thents
import bdd from '@/test/bdd'
const { scenario, given, when, then } = bddReuse:
src/test/bdd.tsRecommended import method (to avoid ESM named export pitfalls):
thents
import bdd from '@/test/bdd'
const { scenario, given, when, then } = bdd标准落地流程
Standard Implementation Process
1) 先写场景清单(业务句子)
1) First write the scenario list (business sentences)
每条场景必须是一句中文:
给定<前置条件>,当<触发动作>,那么<业务结果>Each scenario must follow the structure:
Given <precondition>, When <trigger action>, Then <business result>2) 定义步骤词典方法
2) Define step dictionary methods
使用英文命名,示例:
givenInvalidProviderValues()whenQueryEngineSelection()thenProvidersNormalizedToNone()
规则:
- 只做前置状态准备
given* - 只触发行为
when* - 只做业务结果断言
then*
Use English naming, examples:
givenInvalidProviderValues()whenQueryEngineSelection()thenProvidersNormalizedToNone()
Rules:
- only prepares preconditions
given* - only triggers actions
when* - only asserts business results
then*
3) 在实现层补齐 fixture
3) Complete fixtures in the implementation layer
实现层负责:
- service 实例创建
- store/mock 初始化
- spy 管理(如 )
fs.existsSync - 数据读写 helper()
setValue/getValue
The implementation layer is responsible for:
- Service instance creation
- Store/mock initialization
- Spy management (e.g., )
fs.existsSync - Data read/write helpers ()
setValue/getValue
4) 场景层只编排,不实现
4) Scenario layer only orchestrates, does not implement
场景层示例:
ts
scenario('设置:引擎选择行为', () => {
it('给定 provider 值非法,当查询引擎选择,那么应归一化为 none', async () => {
await given('给定:provider 值非法', () => steps.givenInvalidProviderValues())
await when('当:查询引擎选择', () => steps.whenQueryEngineSelection())
await then('那么:应归一化为 none', () => steps.thenProvidersNormalizedToNone())
})
})Example of scenario layer:
ts
scenario('Settings: Engine Selection Behavior', () => {
it('Given invalid provider values, when querying engine selection, then should normalize to none', async () => {
await given('Given: invalid provider values', () => steps.givenInvalidProviderValues())
await when('When: query engine selection', () => steps.whenQueryEngineSelection())
await then('Then: should normalize to none', () => steps.thenProvidersNormalizedToNone())
})
})评审清单(8项)
Review Checklist (8 Items)
- 场景名是“给定/当/那么”中文句式
- 场景层无技术细节(无 mock/new/spyon)
- 步骤方法命名统一 前缀
given/when/then - 步骤层不堆叠复杂 mock 逻辑
- 实现层集中管理依赖和替身
- Then 断言面向业务结果,不是私有实现
- 场景间状态隔离(beforeEach 重置)
- 可通过
yarn test:bdd
- Scenario names follow the "Given-When-Then" sentence structure
- No technical details in the scenario layer (no mock/new/spyOn)
- Step method names uniformly use the prefix
given/when/then - Step layer does not stack complex mock logic
- Implementation layer centrally manages dependencies and test doubles
- Then assertions focus on business results, not private implementations
- State isolation between scenarios (reset via beforeEach)
- Passes when running
yarn test:bdd
常见误用(必须避免)
Common Misuses (Must Avoid)
- 在场景层直接写 mock 或访问 store
- 步骤方法名中英混用(触发命名规则告警)
- 一个场景覆盖多个业务承诺
- 只断言内部调用次数,不断言业务结果
- 步骤层直接包含大量底层构造逻辑(应下沉实现层)
- Directly writing mocks or accessing the store in the scenario layer
- Mixing Chinese and English in step method names (triggers naming rule alerts)
- One scenario covers multiple business commitments
- Only asserting internal call counts, not business results
- Step layer directly contains a large amount of underlying construction logic (should be delegated to the implementation layer)
运行命令
Run Commands
bash
yarn test:bdd
yarn test:bdd:watchbash
yarn test:bdd
yarn test:bdd:watch扩展建议
Extension Suggestions
- 新业务域按同样三层创建:
xxx.bdd.test.tsbdd/xxx.steps.tsbdd/xxx.fixture.ts
- 优先复用 ,不要重复造 DSL。
src/test/bdd.ts
- Create new business domains following the same three-layer structure:
xxx.bdd.test.tsbdd/xxx.steps.tsbdd/xxx.fixture.ts
- Prioritize reusing , do not reinvent the DSL.
src/test/bdd.ts