mcpField
B
bevy-testing

bevy-testing

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

bevy-testing について

bevy-testing は、ECS の動作、時間、非同期タスク、描画結果を検証する Bevy 0.19 向けの決定論的テストスキルです。テスト内で時刻と入力を制御し、状態やキャプチャ画像で結果を確認します。

主な機能

  1. 01最小限の App テスト環境を構築
  2. 02時刻を制御して固定スケジュールを実行
  3. 03ワールド状態、メッセージ、オブザーバーの結果を確認
  4. 04スリープなしで非同期処理をテスト
  5. 05描画結果をキャプチャしてビジュアル回帰を確認

活用シーン

  1. 01固定スケジュールやタイマーの検証
  2. 02メッセージ、オブザーバー、遅延コマンドのテスト
  3. 03非同期タスクの完了を決定論的に確認
  4. 04レンダー画像とベースラインの比較

インストール

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

$npx skills add https://github.com/chrisgliddon/bevy-skills --skill bevy-testing

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

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
app-and-time.md
messages-observers-and-async.md
visual-regression.md

詳細

Bevy 0.19 — 決定論的なテスト

このスキルを使う場面

  • システム/プラグインで、最小限の App ハーネスとワールドへの直接アサーションが必要な場合。
  • 固定スケジュール、タイマー、メッセージ、オブザーバーを決定論的にステップ実行する必要がある場合。
  • 任意の時間待機を使わずに、テスト内で非同期処理を完了させる必要がある場合。
  • レンダリング出力をキャプチャして、ビジュアルリグレッションテストを行う必要がある場合。

テスト対象の動作を持つ最小限の App を構築し、クロックと入力を制御して明示的にステップ実行し、その後ワールドの状態または出力された内容を確認します。通常のユニットテストや統合テストで App::run() を呼び出さないでください。

基本パターン

use bevy::{prelude::*, time::{TimePlugin, TimeUpdateStrategy}};

#[derive(Resource, Default)]
struct TickCount(u32);

fn count_tick(mut count: ResMut<TickCount>) {
    count.0 += 1;
}

#[test]
fn fixed_system_runs_exactly_three_times() {
    let mut app = App::new();
    app.add_plugins(TimePlugin)
        .insert_resource(Time::<Fixed>::from_hz(60.0))
        .insert_resource(TimeUpdateStrategy::FixedTimesteps(1))
        .init_resource::<TickCount>()
        .add_systems(FixedUpdate, count_tick);

    // The first update initializes Bevy's real-time clock; it has zero delta.
    app.update();

    for _ in 0..3 {
        app.update();
    }

    assert_eq!(app.world().resource::<TickCount>().0, 3);
}

最初のクロック初期化用のアップデートの後、FixedTimesteps(n) を使うと、各 app.update() は固定タイムステップの n 倍だけ進み、固定ループを正確に n 回実行します。テストでゼロティックのフレーム、時間の蓄積、または追い付き処理を 検証する必要がある場合は、ManualDuration を使います。正確な絶対時刻が重要な場合は、 ManualInstant を使い、各アップデートの前にそのリソースを進めます。

注意点

  • 最初の app.update() は実時間をデルタゼロで初期化します。FixedTimesteps のティック数を数える前にウォームアップするか、ゼロステップのフレームを意図的にアサートしてください。
  • FixedUpdate を直接実行すると、固定メインのクロック/メッセージのセマンティクスが迂回されます。
  • 遅延された Commands は、同期ポイントに到達するまで確認できません。
  • 非同期処理の完了には、注入したフェイクか、上限付きの条件ループを使ってください。スリープは使わないでください。
  • DefaultPlugins は、ほとんどのワールドテストで不要なプラットフォーム/レンダリング/オーディオの状態を追加します。

観測可能な契約を通じてテストする

  • リソース/コンポーネントを挿入し、スケジュールされた動作には app.update() を呼び出します。
  • アルゴリズムや状態機械には、純粋なヘルパー関数を直接呼び出します。
  • ワールドの状態、テスト側で所有する MessageCursor を使ったメッセージ、またはオブザーバーの効果を読み取ります。
  • 遅延コマンドの境界を明確にします。コマンドは、スケジュールが同期ポイントに到達するまで適用されません。
  • 任意のクエリ/エンティティの順序や、同一イベントに対するオブザーバーの順序ではなく、不変条件と外部から意味のある順序をアサートします。

スリープを使わない非同期処理

本番システムでは、bevy::tasks::futures::check_ready を使って Task<T> をポーリングするか、結果チャネルをブロックせずに排出します。テストでは、 可能であれば決定論的なインライン/フェイクのエグゼキューターを注入します。それ以外の場合は、 厳格な最大ステップ数/期限を設け、観測可能な完了条件が満たされるまでアプリをステップ実行し、 キュー/タスクの状態を示して失敗させます。「100 ms スリープしてタスクが完了することを期待する」方法は決して使わないでください。

Bevy の Task は、デタッチされていない限り、破棄するとキャンセルされます。 タスクの所有権を明確にし、テストで完了、キャンセル、失敗、古い結果の各経路を網羅してください。

ヘッドレステストとレンダリングテスト

ECS/ゲームプレイロジックには、App::new() と必要なプラグインのみ(TimePlugin、 AssetPlugin など)を使います。契約にウィンドウ、レンダラー、オーディオデバイス、または ファイルシステムが含まれない場合は、DefaultPlugins を避けます。レンダリングの受け入れテストでは、実際のレンダリングアプリを起動し、 Screenshot をキャプチャして、ScreenshotCaptured を待ち、保存した成果物を、 許容差を文書化したバージョン管理済みのベースラインと比較します。

関連する詳細解説を選ぶ

問題読む
最小限のアプリ、手動時間、固定ループ、スケジュールのステップ実行App と時間
メッセージ、オブザーバー、遅延効果、タスク、スリープを使わない排出処理メッセージ、オブザーバー、非同期処理
スクリーンショットのキャプチャ、決定論的なシーン、差分しきい値、成果物ビジュアルリグレッション

レビューチェックリスト

  • テストが時間を管理し、実時間の速度によって期待される結果が変わることはありません。
  • 各テストでは、対象が必要とするプラグインとリソースのみを設定します。
  • 入力と乱数シードを明示し、永続状態とグローバル状態を分離します。
  • 固定入力のテストでは、更新ごとの固定ティック数が0、1、複数の場合をカバーします。
  • メッセージカーソルは、書き込みの前後どちらに作成するかを意図的に決めます。
  • 非同期テストでは、決定論的なフェイクを使うか、有用な失敗情報を伴う回数制限付きの進行ループを使用します。
  • ビジュアルテストの失敗時には、実際の画像、期待される画像、差分画像、および実行メタデータを保持します。
  • ベースラインは、明示的にレビュー済みの操作を通じてのみ更新します。

関連項目

コメント