mcpField
C
condition-based-waiting

condition-based-waiting

github.com/zenobi-us测试与质量6844 次安装今天更新
代码仓库

关于 condition-based-waiting

这项测试技能主张等待被测条件成立,而不是猜测延迟时长,适用于处理异步测试不稳定的开发者。内容涵盖轮询、超时处理,以及何时可以使用有依据的固定延迟。

主要功能

  1. 01等待条件成立而非猜测延迟
  2. 02事件、状态、数量和文件等待模式
  3. 03轮询与超时设置指导
  4. 04处理确需合理延迟的场景

适用场景

  1. 01稳定包含异步操作的测试
  2. 02排查负载或并行运行时失败的测试
  3. 03事件发生后验证定时行为

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/zenobi-us/dotfiles --skill condition-based-waiting

安装后在 Claude Code 中输入/condition-based-waiting确认已加载。

附带文件example.ts

详细介绍

基于条件的等待

概述

不稳定的测试常会用任意延迟来猜测等待时间。这会造成竞态条件:测试在速度较快的机器上通过,却在负载较高时或 CI 中失败。

核心原则: 等待你真正关心的条件,而不是猜测它需要多长时间。

适用场景

digraph when_to_use {
    "Test uses setTimeout/sleep?" [shape=diamond];
    "Testing timing behavior?" [shape=diamond];
    "Document WHY timeout needed" [shape=box];
    "Use condition-based waiting" [shape=box];

    "Test uses setTimeout/sleep?" -> "Testing timing behavior?" [label="yes"];
    "Testing timing behavior?" -> "Document WHY timeout needed" [label="yes"];
    "Testing timing behavior?" -> "Use condition-based waiting" [label="no"];
}

适用于:

  • 测试中有任意延迟(setTimeout、sleep、time.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
✅ 修复: 每 10 ms 轮询一次

❌ 没有超时: 如果条件始终不满足,循环将永远持续
✅ 修复: 始终设置超时并提供清晰的错误信息

❌ 数据过期: 在循环开始前缓存状态
✅ 修复: 在循环中调用 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

要求:

  1. 首先等待触发条件
  2. 基于已知的时间安排(而非猜测)
  3. 添加注释说明原因

实际效果

来自调试会话(2025-10-03):

  • 修复了 3 个文件中的 15 个不稳定测试
  • 通过率:60% → 100%
  • 执行时间:缩短 40%
  • 不再出现竞态条件

评论

支持 Markdown