Vitest retry 配置详解:失败重试次数、延迟与条件控制(config/retry)
Vitest retry 配置详解失败重试次数、延迟与条件控制config/retry【免费下载链接】vitestNext generation testing framework powered by Vite.项目地址: https://gitcode.com/GitHub_Trending/vi/vitestVitest 的retry配置用于在测试失败时按指定次数自动重试是处理网络抖动、偶发超时、资源竞态等flaky tests不稳定测试的最直接手段。本文以 docs/config/retry.md 为主线完整讲解retry的三种配置形态——纯数字、对象count/delay/condition以及测试文件内的逐测试覆盖并结合仓库源码剖析其底层实现帮助你精准控制何时重试、重试几次、每次间隔多久。配置总览retry的完整类型定义为Type:number | { count?: number, delay?: number, condition?: RegExp }Default:0即默认不重试CLI:--retry times、--retry.count times、--retry.delay ms、--retry.condition pattern从源码看这一类型定义在 packages/vitest/src/runtime/runner/types.ts 中的SerializableRetryexport type SerializableRetry number | { count?: number // 失败后重试次数default 0 delay?: number // 两次重试之间的延迟毫秒数default 0 condition?: RegExp // 与错误消息匹配的 RegExpdefault undefined所有错误都重试 }而 packages/vitest/src/node/types/config.ts 中对应的test.retry字段注释明确指出函数形式的 condition 不能在配置文件中使用因为配置文件会被序列化后传给 worker 线程函数无法被序列化。基础用法数字形式最简单的做法是给retry传入一个数字表示失败后重试的次数// vitest.config.ts export default defineConfig({ test: { retry: 3, }, })设置retry: 3意味着首次执行 失败后最多重试 3 次即单个测试最多执行 4 次只要其中任何一次通过该测试即被判定为通过。从实现上看这一语义对应 packages/vitest/src/runtime/runner/run.ts 中的重试循环const retry getRetryCount(test.retry) for (let retryCount 0; retryCount retry; retryCount) {getRetryCount同文件 L47-L55负责把number或{ count?: number }统一归一化为次数retryCount retry的循环条件保证了首次 retry 次的总执行次数。当测试通过时循环会直接breakL741-L743后续重试不再执行。CLI 用法retry同样支持从命令行覆盖配置# 简单的重试次数 vitest --retry 3 # 使用点号dot notation表达高级选项 vitest --retry.count 3 --retry.delay 500 --retry.condition ECONNREFUSED|timeout这些 CLI 选项定义在 packages/vitest/src/node/cli/cli-config.ts。值得注意的是condition子命令的transform逻辑condition: { description: Regex pattern to match error messages that should trigger a retry. Only errors matching this pattern will cause a retry (default: retry on all errors), argument: pattern, transform: (value) { if (typeof value string) { return new RegExp(value, i) // 自动附加 i 标志不区分大小写 } return value }, },也就是说通过 CLI 传入的 pattern 字符串会被自动编译为不区分大小写的正则表达式例如上面的ECONNREFUSED|timeout等价于/ECONNREFUSED|timeout/i。高级选项对象形式Vitest 4.1.0当需要精细控制重试行为时可以传入对象// vitest.config.ts export default defineConfig({ test: { retry: { count: 3, // 重试次数 delay: 1000, // 重试之间的延迟毫秒 condition: /ECONNREFUSED|timeout/i, // 仅当错误消息匹配该正则时才重试 }, }, })count测试失败后重试的次数默认0。export default defineConfig({ test: { retry: { count: 2, }, }, })它等价于数字形式的retry: 2只是为后续扩展delay、condition预留了位置。实现上由getRetryCount归一化function getRetryCount(retry: number | { count?: number } | undefined): number { if (retry undefined) { return 0 } if (typeof retry number) { return retry } return retry.count ?? 0 }delay两次重试尝试之间的延迟毫秒默认0。官方文档强调它特别适合与限流rate-limitedAPI 交互或需要时间恢复的场景——例如第三方接口瞬时 429、数据库连接池暂时耗尽、上游服务正在滚动发布等。export default defineConfig({ test: { retry: { count: 3, delay: 500, // 每次重试前等待 500ms }, }, })重试延迟的实现位于 packages/vitest/src/runtime/runner/run.tsconst delay getRetryDelay(test.retry) if (delay 0) { await new Promise(resolve setTimeout(resolve, delay)) }getRetryDelayL57-L65同样兼容数字与对象两种形式。仓库中的 test/unit/test/retry-delay.test.ts 对这个行为有直接的验证一个断言计数必须等于 3 的测试配置retry: { count: 2, delay: 100 }首次执行必然失败、经两次 100ms 延迟后重试成功随后另一个测试校验总耗时 200ms确认 delay 真实生效it(retry with delay, { retry: { count: 2, delay: 100, }, }, () { if (delayCount 0) { delayStart Date.now() } delayCount 1 expect(delayCount).toBe(3) }) it(verify delay was applied, () { const duration Date.now() - delayStart expect(delayCount).toBe(3) // With 2 retries and 100ms delay each, should take at least 200ms expect(duration).toBeGreaterThanOrEqual(200) })conditioncondition决定是否根据错误内容触发重试。它有两种形态RegExp对错误消息error.message进行匹配函数接收错误对象并返回布尔值。警告当condition是函数时必须在测试文件中直接定义不能写在配置文件里——配置文件会被序列化传给 worker 线程函数无法跨线程序列化。正则 condition配置文件内export default defineConfig({ test: { retry: { count: 2, condition: /ECONNREFUSED|ETIMEDOUT/i, // 仅在连接/超时类错误时重试 }, }, })函数 condition测试文件内import { describe, test } from vitest describe(tests with advanced retry condition, () { test(with function condition, { retry: { count: 2, condition: error error.message.includes(Network) } }, () { // test code }) })函数 condition 的底层判断逻辑packages/vitest/src/runtime/runner/run.tsfunction passesRetryCondition(test: Test, errors: TestError[] | undefined): boolean { const condition getRetryCondition(test.retry) const error errors?.at(-1) // 取最近一次失败的错误 if (error null) { return false } if (!condition) { return true // 未配置 condition所有错误都重试 } if (condition instanceof RegExp) { return condition.test(error.message || ) // 正则匹配错误消息 } else if (typeof condition function) { return condition(error) // 函数自定义判定 } return false }三个关键语义值得注意condition缺省时所有错误都会触发重试return true正则匹配的是最近一次失败的错误消息且基于error.message函数形式接收的是完整的TestError对象而非仅字符串因此可以基于error.name如TimeoutError、error.stack等字段做更精细的判定。SerializableRetry类型注释packages/vitest/src/runtime/runner/types.ts也印证了这一点RegExp 测试错误消息函数则接收TestError返回布尔值且只能用于测试文件。为什么配置文件中不能用函数源码 packages/vitest/src/node/config/resolveConfig.ts 给出了答案if (resolved.retry typeof resolved.retry object typeof resolved.retry.condition function) { logger.warn( c.yellow(Warning: retry.condition function cannot be used inside a config file. Use a RegExp pattern instead, or define the function in your test file.), ) resolved.retry { ...resolved.retry, condition: undefined, // 函数条件被静默丢弃 } }Vitest 检测到配置中的函数 condition 时会发出警告并将其置为undefined退化为所有错误都重试。此外test.tags中定义的自定义标签若带函数 condition则直接抛出TypeError同文件 L307-L309。原因正如类型定义所述配置需要序列化后跨线程传递。测试文件内覆盖按测试 / 按套件重试retry同样可以作为测试选项在单个test()或describe()级别覆盖全局配置——这在某几个测试特别不稳定但不想全局开启重试时非常实用import { describe, test } from vitest describe(flaky tests, { retry: { count: 2, delay: 100, }, }, () { test(network request, () { // test code }) }) test(another test, { retry: { count: 3, condition: error error.message.includes(timeout), }, }, () { // test code })这里的函数 condition 位于测试文件内部不经过配置序列化因此可以放心使用。测试文件中的选项会合并到测试任务的retry字段上运行时统一由getRetryCount/getRetryDelay/getRetryCondition读取。与 repeats 的区别配置中还有一个容易混淆的相邻选项repeats见 packages/vitest/src/node/types/config.tsretry只在失败时重试而repeats是无论成败都重复执行指定次数。二者的循环在 packages/vitest/src/runtime/runner/run.ts 中嵌套实现const repeats test.repeats ?? 0 for (let repeatCount 0; repeatCount repeats; repeatCount) { const retry getRetryCount(test.retry) for (let retryCount 0; retryCount retry; retryCount) {外层是 repeats无条件下重复内层才是 retry失败后按条件重试二者可以组合使用。重试状态与报告重试过程中Vitest 会维护每个测试的retryCount并触发test-retried任务事件packages/vitest/src/runtime/runner/run.tstest.result.state run test.result.retryCount (test.result.retryCount ?? 0) 1 const delay getRetryDelay(test.retry) if (delay 0) { await new Promise(resolve setTimeout(resolve, delay)) } // update retry info updateTask(test-retried, test, runner)这意味着你可以在自定义 reporter 中监听test-retried事件来感知重试行为同时retryCount也会进入测试结果供报告器展示第几次尝试后通过。相关任务状态字段定义在 packages/vitest/src/node/reporters/reported-tasks.ts其中retry字段以SerializableRetry类型随任务一起上报。结合 Hook 与 Fixture 的语义需要留意的是重试并不仅仅是重新执行测试函数。从 packages/vitest/src/runtime/runner/run.ts 的重试循环可以看到每次重试都会重新执行beforeEach/afterEach钩子、aroundEachfixtures 及其清理逻辑并触发onBeforeTryTask/onAfterTryTask/onAfterRetryTask等 runner 回调参数中携带当前的retry: retryCount与repeats: repeatCount。因此写在beforeEach中的状态初始化每次重试都会重新执行避免上一次失败残留污染下一次尝试每次重试拥有独立的测试上下文test.context与 fixture 生命周期如果失败发生在beforeAll/afterAll这类套件级钩子中重试不会覆盖该错误——套件级钩子的失败仍会使整个套件失败。实战建议与限制基于文档与源码以下是实践中比较可靠的使用策略先定位再重试重试是治标手段不应掩盖真正的确定性 bug。建议先排查根因仅对已知偶发网络超时、限流、时序竞争的用例开启。用 condition 收窄范围默认所有错误都重试可能掩盖真实故障。配合condition: /ECONNREFUSED|ETIMEDOUT/i之类正则只对特定错误重试其余错误立即暴露。合理设置 delay对限流接口建议delay 500对本地资源竞争可保持delay: 0立即重试避免拖慢整个测试套件。仓库的 test/unit/test/retry-delay.test.ts 同时验证了delay: 100与delay: 0两种路径。优先在测试文件内做局部覆盖全局retry会影响所有测试文件包括本应快速失败的断言按测试/套件覆盖更加精准。不要在配置文件中写函数 condition会被警告并静默降级为重试所有错误。需要函数判定时把它放进测试文件的测试选项中。重试次数与报告联动retry: 3意味着单测最多执行 4 次会放大执行时间与外部调用次数注意对 CI 总时长的影响。小结retry是 Vitest 处理不稳定测试的核心配置项数字形式快速开启对象形式通过count/delay/condition实现次数 间隔 错误筛选的精细控制测试文件内覆盖则提供了按用例定制的灵活性。其底层由 packages/vitest/src/runtime/runner/run.ts 中的重试循环与条件判定统一驱动类型定义集中在 packages/vitest/src/runtime/runner/types.ts相关行为均有 test/unit/test/retry-condition.test.ts 与 test/unit/test/retry-delay.test.ts 等单元测试直接验证。合理组合这三要素就能在不掩盖真实缺陷的前提下显著提升测试套件在真实网络环境下的稳定性。【免费下载链接】vitestNext generation testing framework powered by Vite.项目地址: https://gitcode.com/GitHub_Trending/vi/vitest创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →