关键在于按职责拆分测试场景:正常流程、边界输入、非法输入、副作用相关;用工厂函数生成结构化数据;隔离不可控依赖;命名体现行为而非实现细节。

对包含复杂逻辑的工具函数做全面单元测试,关键不是堆砌用例数量,而是让每个测试明确表达“输入—行为—输出”的契约关系,并能稳定复现。
按职责拆分测试场景,不写大而全的 test
一个工具函数若同时处理格式化、校验、转换、异常等多类逻辑,就应分组验证:
- 正常流程:给定典型合法输入,检查返回值是否符合预期(如日期字符串转时间戳)
- 边界输入:空值、undefined、null、极小/极大数值、超长字符串等,确认是否合理处理或抛出明确错误
- 非法输入:格式错误的 JSON、非法正则、不支持的编码名等,验证是否拒绝并给出可读错误信息
- 副作用相关:若函数依赖 Date.now()、Math.random() 或 Intl API,需单独覆盖其受控行为
用工厂函数生成结构化测试数据
避免在每个 it 里重复构造相似输入。例如处理时间的工具函数,可定义:
const timeFactory = {
today: () => new Date(2026, 9, 14),
leapYearFeb29: () => new Date(2024, 1, 29),
invalidDateStr: '2026-02-30'
};
测试时直接调用 timeFactory.today(),语义清晰,也便于统一调整基准时间,避免因系统时钟漂移导致测试不稳定。
主动隔离不可控依赖
工具函数一旦读取外部状态,测试就容易失败。必须提前控制:
- 用
vi.useFakeTimers()(Vitest)或jest.useFakeTimers()冻结时间,再用vi.setSystemTime()设定固定时刻 - 对
Math.random()使用vi.mock()或vi.spyOn(Math, 'random').mockReturnValue(0.123) - Intl 相关逻辑,优先通过参数传入预设 locale / timeZone,而非直接调用全局构造器
命名体现行为,而非实现细节
测试名要让人一眼看懂“它在保护什么”。推荐 BDD 风格:
- ✅
it('returns "2026-10-14" when formatting today in UTC' - ✅
it('throws TypeError when input is null' - ❌
it('test case 3'或it('checks format function'
每个 it 应只验证一个明确行为,失败时能立刻定位问题环节,而不是翻半天代码猜哪里出了岔子。


















