用途
条件等待用于解决异步测试中的不稳定问题。许多测试用任意延迟来「猜」异步操作何时完成,这会产生竞态条件:在快机器上通过,在负载高或 CI 环境中失败。其核心原则是:等待你真正关心的那个条件,而不是猜测它需要多长时间。
何时使用
适用场景:
- 测试中存在任意延迟(setTimeout、sleep、time.sleep())
- 测试不稳定(有时通过,负载下失败)
- 测试并行运行时超时
- 需要等待异步操作完成
不适用场景:
- 测试真实的时序行为(如 debounce、throttle 间隔)
- 如果确实要使用任意超时,必须注释说明原因(WHY)
资料中的决策流程为:测试是否使用了 setTimeout/sleep?若是,再判断是否在测试时序行为;若是时序行为,则记录为什么需要超时;若不是,则使用条件等待。
核心模式
用等待条件替代猜测时间:
// ❌ BEFORE: Guessing at timing
await new Promise(r => setTimeout(r, 50));
const result = getResult();
expect(result).toBeDefined();
// ✅ AFTER: Waiting for condition
await waitFor(() => getResult() !== undefined);
const result = getResult();
expect(result).toBeDefined();
常见等待模式
- 等待事件:
waitFor(() => events.find(e => e.type === 'DONE')) - 等待状态:
waitFor(() => machine.state === 'ready') - 等待数量:
waitFor(() => items.length >= 5) - 等待文件:
waitFor(() => fs.existsSync(path)) - 复杂条件:
waitFor(() => obj.ready && obj.value > 10)
实现方式
通用轮询函数:
async function waitFor<T>(
condition: () => T | undefined | null | false,
description: string,
timeoutMs = 5000
): Promise<T> {
const startTime = Date.now();
while (true) {
const result = condition();
if (result) return result;
if (Date.now() - startTime > timeoutMs) {
throw new Error(`Timeout waiting for ${description} after ${timeoutMs}ms`);
}
await new Promise(r => setTimeout(r, 10)); // Poll every 10ms
}
}
资料提到可参考 @example.ts 获取完整实现,其中包含来自真实调试会话的领域专用辅助函数(waitForEvent、waitForEventCount、waitForEventMatch)。
常见错误
- 轮询过快:
setTimeout(check, 1)会浪费 CPU;修复方式是每 10ms 轮询一次。 - 没有超时:条件永不满足时会无限循环;修复方式是始终包含超时并给出清晰错误。
- 数据陈旧:在循环前缓存状态;修复方式是在循环内部调用 getter 以获取最新数据。
何时任意超时是正确的
// Tool ticks every 100ms - need 2 ticks to verify partial output
await waitForEvent(manager, 'TOOL_STARTED'); // First: wait for condition
await new Promise(r => setTimeout(r, 200)); // Then: wait for timed behavior
// 200ms = 2 ticks at 100ms intervals - documented and justified
要求:先等待触发条件;基于已知时序(而非猜测);用注释解释原因。
实际效果
来自 2025-10-03 的调试会话:修复了 3 个文件中的 15 个不稳定测试;通过率从 60% 提升到 100%;执行时间加快 40%;不再有竞态条件。
限制
资料未提供该技能的具体安装命令、依赖要求、支持的语言或框架范围、版本信息及收费方式,这些内容暂未提供。