Debugging, Profiling & Unit Testing
Also: OS.get_static_memory_usage(), orphan node detection with print_orphan_nodes() (debug builds), --verbose CLI flag, and file logging (*Debug → File Logging*).
36.1 Printing and assertions
print_debug("Value: ", x) # includes file and line
push_error("Invalid state: %s" % state) # red entry with stack trace in the Debugger
push_warning("Deprecated call")
assert(health >= 0, "Health went negative") # only in debug builds; stripped in release
print(get_stack()) # current call stack (debug builds)
breakpoint # keyword: pause the debugger here36.2 The debugger
Click the gutter to set breakpoints; use *Step Into* (F11), *Step Over* (F10), *Continue* (F12).
Inspect locals/members in the Stack Variables panel; hover variables in the editor while paused.
Remote scene tree (Scene dock → Remote) shows the live game tree; edit properties at runtime.
*Debug* menu: Visible Collision Shapes, Visible Navigation, Visible Paths, Synchronize Scene/Script Changes (live editing), Deploy with Remote Debug.
Errors tab collects runtime errors and warnings with stack traces.
Godot 4.5 can include script backtraces in release builds (*Debug → Settings → GDScript*), useful for crash reports.
36.3 Profilers and monitors
Time per function (script and engine), frame time Visual Profiler CPU/GPU time per rendering stage
FPS, memory, object/node counts, draw calls, physics stats Network Profiler RPC and synchronization bandwidth Texture/mesh memory usage
Also: OS.get_static_memory_usage(), orphan node detection with print_orphan_nodes() (debug builds), --verbose CLI flag, and file logging (*Debug → File Logging*).
func _ready() -> void:
Performance.add_custom_monitor("game/enemies", func(): return get_tree().get_node_count_in_group("enemies"))
func _process(_delta: float) -> void:
$DebugLabel.text = "FPS: %d Nodes: %d Draw calls: %d" % [
Engine.get_frames_per_second(),
Performance.get_monitor(Performance.OBJECT_NODE_COUNT),
Performance.get_monitor(Performance.RENDER_TOTAL_DRAW_CALLS_IN_FRAME),
]
func measure() -> void:
var start := Time.get_ticks_usec()
expensive_function()
print("took %d µs" % (Time.get_ticks_usec() - start))36.4 Unit testing with GUT
GUT (Godot Unit Test) is a popular testing addon (install from the Asset Library; use GUT 9.x for Godot 4).
Run from the GUT panel, or headless in CI:
GUT also offers doubles/stubs (double(), stub()), parameterized tests, and await wait_seconds(1) / await wait_frames(5) for async tests.
# res://test/unit/test_health_component.gd
extends GutTest
var health: HealthComponent
func before_each() -> void:
health = HealthComponent.new()
health.max_health = 10
add_child_autofree(health) # freed automatically after the test
func test_starts_full() -> void:
assert_eq(health.current, 10)
func test_damage_reduces_health() -> void:
health.damage(3)
assert_eq(health.current, 7, "Health should drop by damage amount")
func test_died_signal_emitted_at_zero() -> void:
watch_signals(health)
health.damage(100)
assert_signal_emitted(health, "died")
assert_eq(health.current, 0)
func test_damage_never_negative() -> void:
for i in 5: health.damage(5)
assert_true(health.current >= 0)godot --headless -s addons/gut/gut_cmdln.gd -gdir=res://test -ginclude_subdirs -gexit36.5 Unit testing with gdUnit4
gdUnit4 provides fluent assertions, mocking, scene runners, and an editor inspector.
Run from the gdUnit4 panel or via its command-line runner (addons/gdUnit4/runtest.sh / runtest.cmd -a res://test). Both frameworks integrate with GitHub Actions using headless Godot.
# res://test/player_test.gd
extends GdUnitTestSuite
func test_player_moves_right() -> void:
var runner := scene_runner("res://scenes/player.tscn")
runner.simulate_action_press("move_right")
await runner.simulate_frames(30)
runner.simulate_action_release("move_right")
assert_float(runner.get_property("position").x).is_greater(0.0)
func test_inventory_add() -> void:
var inv: Inventory = auto_free(Inventory.new())
inv.add("potion", 3)
assert_int(inv.count("potion")).is_equal(3)
assert_array(inv.items()).contains(["potion"])36.6 Testable code
Put logic in plain classes (RefCounted) or small components that don't depend on the whole scene.
Inject dependencies (exported references, constructor args) instead of hard-coded paths.
Keep randomness seedable (RandomNumberGenerator.seed).