name: 基于条件的等待 description: 用条件轮询替换随机超时,以实现可靠的异步测试 when_to_use: 当测试存在竞争条件、时序依赖或通过/失败行为不一致时 version: 1.1.0 languages: 所有 progressive_disclosure: entry_point: summary: “用条件轮询替换随机超时,以实现可靠的异步测试” when_to_use: “使用setTimeout/sleep、不稳定测试或依赖时序的异步操作的测试” quick_start: | 1. 识别测试中的随机延迟(setTimeout、sleep、time.sleep()) 2. 替换为基于条件的等待(waitFor模式) 3. 针对常见场景使用领域特定助手 请参阅@example.ts获取完整的工作实现 core_pattern: | // ❌ 猜测时序 await new Promise(r => setTimeout(r, 50));
// ✅ 等待条件
await waitFor(() => getResult() !== undefined);
references: - path: references/patterns-and-implementation.md purpose: 详细的等待模式、实现指南和常见错误 when_to_read: 实现waitFor或调试时序问题时
基于条件的等待
概述
不稳定的测试通常通过随机延迟来猜测时序。这会创建竞争条件,导致测试在快速机器上通过,但在负载下或CI中失败。
核心原则: 等待你真正关心的实际条件,而不是猜测需要多长时间。
何时使用
digraph when_to_use {
"测试使用setTimeout/sleep吗?" [shape=diamond];
"测试时序行为吗?" [shape=diamond];
"记录为什么需要超时" [shape=box];
"使用基于条件的等待" [shape=box];
"测试使用setTimeout/sleep吗?" -> "测试时序行为吗?" [label="是"];
"测试时序行为吗?" -> "记录为什么需要超时" [label="是"];
"测试时序行为吗?" -> "使用基于条件的等待" [label="否"];
}
使用时机:
- 测试中存在随机延迟(
setTimeout、sleep、time.sleep()) - 测试不稳定(有时通过,负载下失败)
- 并行运行时测试超时
- 等待异步操作完成
不使用时:
- 测试实际时序行为(防抖、节流间隔)
- 如果使用随机超时,请始终记录原因
核心模式
// ❌ 之前:猜测时序
await new Promise(r => setTimeout(r, 50));
const result = getResult();
expect(result).toBeDefined();
// ✅ 之后:等待条件
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)); // 每10毫秒轮询一次
}
}
请参阅@example.ts获取包含领域特定助手(waitForEvent、waitForEventCount、waitForEventMatch)的完整实现。
有关详细模式、实现指南和常见错误,请参阅@references/patterns-and-implementation.md
实际影响
从调试会话(2025-10-03):
- 修复了3个文件中的15个不稳定测试
- 通过率:60% → 100%
- 执行时间:快40%
- 不再有竞争条件