bevy-testing について
bevy-testing は、ECS の動作、時間、非同期タスク、描画結果を検証する Bevy 0.19 向けの決定論的テストスキルです。テスト内で時刻と入力を制御し、状態やキャプチャ画像で結果を確認します。
主な機能
- 01最小限の App テスト環境を構築
- 02時刻を制御して固定スケジュールを実行
- 03ワールド状態、メッセージ、オブザーバーの結果を確認
- 04スリープなしで非同期処理をテスト
- 05描画結果をキャプチャしてビジュアル回帰を確認
活用シーン
- 01固定スケジュールやタイマーの検証
- 02メッセージ、オブザーバー、遅延コマンドのテスト
- 03非同期タスクの完了を決定論的に確認
- 04レンダー画像とベースラインの比較
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ chrisgliddon/ bevy-skills --skill bevy-testingインストール後、Claude Code で/bevy-testingを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/app-and-time.mdmessages-observers-and-async.mdvisual-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、複数の場合をカバーします。
- メッセージカーソルは、書き込みの前後どちらに作成するかを意図的に決めます。
- 非同期テストでは、決定論的なフェイクを使うか、有用な失敗情報を伴う回数制限付きの進行ループを使用します。
- ビジュアルテストの失敗時には、実際の画像、期待される画像、差分画像、および実行メタデータを保持します。
- ベースラインは、明示的にレビュー済みの操作を通じてのみ更新します。
関連項目
bevy-input-actions— フレーム入力から固定入力への不変条件。bevy-physics— Rapier のヘッドレス実行と決定性の境界。bevy-diagnostics-profiling— 受け入れテストのリプレイとパフォーマンスの根拠。- Bevy
TimeUpdateStrategy - Bevy
Task