mcpField
C
condition-based-waiting

condition-based-waiting

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

condition-based-waiting について

condition-based-waiting は、テストのタイミングに起因する不安定さを減らすためのスキルです。任意の遅延ではなく条件成立を待つ方法を示し、ポーリング、タイムアウト、固定待機が妥当な場合を解説します。

主な機能

  1. 01状態やイベントの条件成立を待機
  2. 02一般的な条件ポーリングのパターン
  3. 03ポーリングのタイムアウトと明確なエラー設定
  4. 04固定遅延が適切な状況の判断

活用シーン

  1. 01テスト内の任意の sleep を置き換え
  2. 02非同期処理やファイル作成を待機
  3. 03並列実行時に不安定なテストを調査
  4. 04既知の間隔を持つ時間動作の検証

インストール

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

$npx skills add https://github.com/jackspace/claudeskillz --skill condition-based-waiting

インストール後、Claude Code で/condition-based-waitingを入力して読み込まれたか確認します。

同梱ファイルexample.tsSKILL.json

詳細

条件ベースの待機

概要

不安定なテストでは、根拠のない待機時間を設定してタイミングを推測しがちです。これにより、処理の速いマシンではテストが成功する一方、負荷がかかっている環境や 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% 短縮
  • 競合状態が発生しなくなった

コメント