Godot 4でゲームを作っていると、コードが大きくなるにつれて「この関数、本当に正しく動いてる?」という不安が出てきます。Web開発ではテストを書くのが当たり前ですが、ゲーム開発では後回しになりがちです。
この記事では、Godot 4で最も成熟したテストフレームワーク GdUnit4 を使ったユニットテストの書き方を、実際に動くコード付きで解説します。
なぜゲームコードにテストが必要なのか
ゲームのバグは「動かない」より「変な動きをする」が多いです。プレイヤーHPが負の値になる、特定の順序でアイテムを取ると進行不能になる、敵が壁にめり込む。こうしたバグは目で見ても気付きにくく、リリース後にレビューで報告されてから初めて分かることが多いです。
ユニットテストは「特定のロジックが期待通りに動くか」を自動で検証してくれます。HP管理、インベントリ計算、ダメージ計算など、ロジックが独立している部分には特に効果的です。
GdUnit4のインストール
GdUnit4はAssetLibから直接インストールできます。
- Godotエディタで
AssetLibタブを開く -
GdUnit4を検索 - ダウンロードしてプロジェクトに追加
- プラグインメニューから
GdUnit4を有効化
有効化すると、エディタの下部に GdUnit4 パネルが追加されます。
最初のテストを書く
プロジェクトのルートに tests/ フォルダを作って、その中にテストファイルを置きます。例として、シンプルなプレイヤーステータスクラスをテストします。
# scripts/player_stats.gd
class_name PlayerStats
extends Resource
@export var hp: int = 100
@export var max_hp: int = 100
func take_damage(amount: int) -> void:
hp = max(0, hp - amount)
func heal(amount: int) -> void:
hp = min(max_hp, hp + amount)
func is_dead() -> bool:
return hp <= 0
対応するテストファイル:
# tests/test_player_stats.gd
class_name TestPlayerStats
extends GdUnitTestSuite
var stats: PlayerStats
func before_test() -> void:
stats = PlayerStats.new()
func test_take_damage_reduces_hp() -> void:
stats.take_damage(30)
assert_int(stats.hp).is_equal(70)
func test_take_damage_cannot_go_below_zero() -> void:
stats.take_damage(200)
assert_int(stats.hp).is_equal(0)
func test_heal_does_not_exceed_max_hp() -> void:
stats.take_damage(30)
stats.heal(100)
assert_int(stats.hp).is_equal(100)
func test_is_dead_when_hp_is_zero() -> void:
stats.take_damage(100)
assert_bool(stats.is_dead()).is_true()
ポイント:
-
extends GdUnitTestSuiteを継承する -
before_test()でテストごとに状態をリセット - 各テスト関数は
test_で始める -
assert_int(),assert_bool(),assert_str()などの fluent API を使う
テストを実行する
GdUnit4パネルでテストファイルを右クリック → Run Test で実行できます。コマンドラインからも実行可能です:
godot --headless -s addons/gdUnit4/bin/GdUnitCmdTool.gd -a tests/
CI/CDに組み込む場合はこのコマンドラインバージョンが必須です。
シグナルのテスト
Godotのシグナルは少し特殊ですが、GdUnit4にはシグナル用のアサーションも用意されています。
func test_signal_emitted_on_death() -> void:
var monitor = monitor_signals(stats)
stats.take_damage(100)
await assert_signal(stats).is_emitted("died")
monitor_signals() でシグナルの発火を監視し、assert_signal().is_emitted() で検証します。
モックとスタブ
GdUnit4は mock() 関数でクラスのモックを作れます。
func test_save_manager_calls_resource_saver() -> void:
var save_manager = SaveManager.new()
var mock_saver = mock(ResourceSaver)
save_manager.saver = mock_saver
save_manager.save_game()
verify(mock_saver, 1).save(any(), any())
これでファイルシステムに実際に書き込まずにテストできます。
AIにテストコードを書かせる時の落とし穴
ChatGPT や Claude にGodot用のテストコードを書いてもらうと、よく extends Node を使ったり、Godot 3のtest patternを使ったりします。Godot 4 + GdUnit4 では extends GdUnitTestSuite が正解です。
実際にプロジェクトの依存関係を見られないAIには、こうしたバージョン違いの判別が難しいのです。Godotエディタの中で動作するAIエージェントの Ziva は、プロジェクトのGodotバージョンとインストール済みプラグインを実際に確認した上でテストコードを生成するので、こうしたミスを起こしません。
まとめ
ゲーム開発でもテストは書くべきですし、GdUnit4があれば書ける環境は整っています。最初は HP/インベントリのようなロジック中心の部分から始め、徐々にカバレッジを増やしていくのが現実的です。
Godot公式のテストフレームワーク統合はまだ無いので、GdUnit4は事実上の標準になっています。AssetLibから5分でセットアップできるので、まずは小さいテストから書いてみましょう。