mcpField
C
Condition-Based Waiting

Condition-Based Waiting

github.com/obraテスト・品質756510 回インストール54 週間前に更新
リポジトリ

Condition-Based Waiting について

Condition-Based Waitingは、推測した待ち時間を観測可能な条件の待機に置き換えるテスト用スキルです。非同期テストの不安定さを減らしつつ、実際のタイミングを検証する場合は根拠のあるタイムアウトを使います。

主な機能

  1. 01観測可能な条件を待つ
  2. 02イベント・状態・ファイルの待機パターン
  3. 03明確なエラーを伴うタイムアウト
  4. 04過剰なポーリングと古いデータを回避
  5. 05妥当な遅延の使い方

活用シーン

  1. 01断続的に失敗するテストの安定化
  2. 02非同期処理の完了待ち
  3. 03ファイルやイベントの出現確認
  4. 04根拠を示した遅延でタイミングを検証

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/obra/superpowers-skills --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
  }
}

実際のデバッグセッションで使用した、ドメイン固有のヘルパー(waitForEvent、waitForEventCount、waitForEventMatch)を含む完全な実装については、@example.ts を参照してください。

よくある間違い

❌ ポーリング間隔が短すぎる: setTimeout(check, 1) - CPU を浪費します ✅ 修正: 10ms 間隔でポーリングします

❌ タイムアウトがない: 条件が満たされないと無限ループになります ✅ 修正: 明確なエラーを含むタイムアウトを必ず設定します

❌ 古いデータ: ループの前に状態をキャッシュします ✅ 修正: ループ内でゲッターを呼び出して最新のデータを取得します

根拠のないタイムアウトが正しい場合

// 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% 短縮
  • 競合状態が発生しなくなった

コメント