godot-testing-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTesting Patterns
测试模式
GUT framework, assertion patterns, mocking, and async testing define automated validation.
GUT框架、断言模式、模拟和异步测试构成了自动化验证体系。
Available Scripts
可用脚本
integration_test_base.gd
integration_test_base.gd
Base class for GUT integration tests with auto-cleanup and scene helpers.
GUT集成测试的基类,包含自动清理和场景辅助功能。
headless_test_runner.gd
headless_test_runner.gd
Expert headless test runner for CI/CD with JUnit XML output and exit code handling.
适用于CI/CD的专业无头测试运行器,支持JUnit XML输出和退出码处理。
NEVER Do in Testing
测试中的禁忌
- NEVER test implementation details — ? Private variables = brittle tests. Test PUBLIC behavior, not internals.
assert_eq(player._internal_state, 5) - NEVER share state between tests — Test 1 modifies global variable, test 2 assumes clean state? Flaky tests. Use for fresh setup.
before_each() - NEVER use sleep() for timing — in tests? Slow + unreliable. Use GUT's
await get_tree().create_timer(1.0).timeoutOR manual frame stepping.wait_seconds() - NEVER skip cleanup in after_each() — Test spawns 100 nodes, doesn't free? Memory leak + slow test suite. ALWAYS free nodes in .
after_each() - NEVER test randomness without seeding — in test = non-deterministic failure. Use
randi()for repeatable tests.seed(12345) - NEVER forget to watch signals — without
assert_signal_emitted(obj, "died")? Fails silently. MUST callwatch_signalsfirst.watch_signals(obj)
- 绝对不要测试实现细节 — 比如?私有变量会导致测试脆弱。应测试公开行为,而非内部实现。
assert_eq(player._internal_state, 5) - 绝对不要在测试间共享状态 — 测试1修改了全局变量,测试2却假设状态是干净的?这会导致测试不稳定。使用来实现每次测试的全新初始化。
before_each() - 绝对不要用sleep()来处理时序 — 在测试中使用?既缓慢又不可靠。使用GUT的
await get_tree().create_timer(1.0).timeout或手动帧步进。wait_seconds() - 绝对不要在after_each()中跳过清理步骤 — 测试生成了100个节点却不释放?会造成内存泄漏并拖慢测试套件。务必在中释放节点。
after_each() - 绝对不要在不设置种子的情况下测试随机性 — 测试中使用会导致非确定性失败。使用
randi()来保证测试可重复。seed(12345) - 绝对不要忘记监听信号 — 未调用就使用
watch_signals?会静默失败。必须先调用assert_signal_emitted(obj, "died")。watch_signals(obj)
Installation
安装步骤
- Download from AssetLib: "GUT - Godot Unit Test"
- Enable in Project Settings → Plugins
- Create directory
res://test/
- 从AssetLib下载“GUT - Godot Unit Test”
- 在项目设置→插件中启用
- 创建目录
res://test/
Basic Test
基础测试
gdscript
undefinedgdscript
undefinedtest/test_player.gd
test/test_player.gd
extends GutTest
var player: CharacterBody2D
func before_each() -> void:
player = preload("res://entities/player/player.tscn").instantiate()
add_child(player)
func after_each() -> void:
player.queue_free()
func test_initial_health() -> void:
assert_eq(player.health, 100, "Player should start with 100 health")
func test_take_damage() -> void:
player.take_damage(25)
assert_eq(player.health, 75, "Health should be 75 after 25 damage")
func test_cannot_have_negative_health() -> void:
player.take_damage(200)
assert_gte(player.health, 0, "Health should not go below 0")
undefinedextends GutTest
var player: CharacterBody2D
func before_each() -> void:
player = preload("res://entities/player/player.tscn").instantiate()
add_child(player)
func after_each() -> void:
player.queue_free()
func test_initial_health() -> void:
assert_eq(player.health, 100, "Player should start with 100 health")
func test_take_damage() -> void:
player.take_damage(25)
assert_eq(player.health, 75, "Health should be 75 after 25 damage")
func test_cannot_have_negative_health() -> void:
player.take_damage(200)
assert_gte(player.health, 0, "Health should not go below 0")
undefinedRunning Tests
运行测试
gdscript
undefinedgdscript
undefinedVia GUT panel in editor
Via GUT panel in editor
Or command line:
Or command line:
godot --headless -s addons/gut/gut_cmdln.gd
godot --headless -s addons/gut/gut_cmdln.gd
undefinedundefinedAssertion Patterns
断言模式
gdscript
undefinedgdscript
undefinedEquality
Equality
assert_eq(actual, expected, "message")
assert_ne(actual, not_expected, "message")
assert_eq(actual, expected, "message")
assert_ne(actual, not_expected, "message")
Comparison
Comparison
assert_gt(value, min_value, "should be greater")
assert_lt(value, max_value, "should be less")
assert_gte(value, min_value, "should be >= min")
assert_lte(value, max_value, "should be <= max")
assert_gt(value, min_value, "should be greater")
assert_lt(value, max_value, "should be less")
assert_gte(value, min_value, "should be >= min")
assert_lte(value, max_value, "should be <= max")
Boolean
Boolean
assert_true(condition, "should be true")
assert_false(condition, "should be false")
assert_true(condition, "should be true")
assert_false(condition, "should be false")
Null
Null
assert_not_null(object, "should exist")
assert_null(object, "should be null")
assert_not_null(object, "should exist")
assert_null(object, "should be null")
Arrays
Arrays
assert_has(array, element, "should contain element")
assert_does_not_have(array, element, "should not contain")
assert_has(array, element, "should contain element")
assert_does_not_have(array, element, "should not contain")
Signals
Signals
watch_signals(object)
assert_signal_emitted(object, "signal_name")
undefinedwatch_signals(object)
assert_signal_emitted(object, "signal_name")
undefinedTesting Signals
信号测试
gdscript
func test_death_signal() -> void:
watch_signals(player)
player.take_damage(100)
assert_signal_emitted(player, "died")
assert_signal_emitted_with_parameters(player, "died", [player])gdscript
func test_death_signal() -> void:
watch_signals(player)
player.take_damage(100)
assert_signal_emitted(player, "died")
assert_signal_emitted_with_parameters(player, "died", [player])Testing Async
异步测试
gdscript
func test_delayed_action() -> void:
player.start_ability()
# Wait for timer
await wait_seconds(1.0)
assert_true(player.ability_active, "Ability should be active after delay")gdscript
func test_delayed_action() -> void:
player.start_ability()
# Wait for timer
await wait_seconds(1.0)
assert_true(player.ability_active, "Ability should be active after delay")Mock/Stub Patterns
模拟/存根模式
gdscript
undefinedgdscript
undefinedDouble (mock) pattern
Double (mock) pattern
func test_with_mock() -> void:
var mock_enemy := double(Enemy).new()
stub(mock_enemy, "get_damage").to_return(50)
player.collide_with(mock_enemy)
assert_eq(player.health, 50, "Should take mocked damage")undefinedfunc test_with_mock() -> void:
var mock_enemy := double(Enemy).new()
stub(mock_enemy, "get_damage").to_return(50)
player.collide_with(mock_enemy)
assert_eq(player.health, 50, "Should take mocked damage")undefinedIntegration Testing
集成测试
gdscript
undefinedgdscript
undefinedtest/test_combat_system.gd
test/test_combat_system.gd
extends GutTest
func test_player_kills_enemy() -> void:
var level := preload("res://levels/test_arena.tscn").instantiate()
add_child(level)
var player := level.get_node("Player")
var enemy := level.get_node("Enemy")
# Simulate combat
for i in range(5):
player.attack(enemy)
await wait_frames(1)
assert_true(enemy.is_dead, "Enemy should be dead")
assert_gt(player.score, 0, "Player should have score")
level.queue_free()undefinedextends GutTest
func test_player_kills_enemy() -> void:
var level := preload("res://levels/test_arena.tscn").instantiate()
add_child(level)
var player := level.get_node("Player")
var enemy := level.get_node("Enemy")
# Simulate combat
for i in range(5):
player.attack(enemy)
await wait_frames(1)
assert_true(enemy.is_dead, "Enemy should be dead")
assert_gt(player.score, 0, "Player should have score")
level.queue_free()undefinedManual Testing Checklist
手动测试清单
markdown
undefinedmarkdown
undefinedGameplay
Gameplay
- Player can move in all directions
- Jump height feels right
- Enemies respond to player
- Damage numbers are correct
- Player can move in all directions
- Jump height feels right
- Enemies respond to player
- Damage numbers are correct
UI
UI
- All buttons work
- Text is readable
- Responsive on different resolutions
- All buttons work
- Text is readable
- Responsive on different resolutions
Audio
Audio
- Music plays
- SFX trigger correctly
- Volume levels balanced
- Music plays
- SFX trigger correctly
- Volume levels balanced
Performance
Performance
- Maintains 60 FPS
- No stuttering
- Memory stable
undefined- Maintains 60 FPS
- No stuttering
- Memory stable
undefinedValidation Helpers
验证辅助工具
gdscript
undefinedgdscript
undefinedvalidation.gd (for runtime checks)
validation.gd (for runtime checks)
class_name Validation
static func assert_valid_health(health: int) -> void:
assert(health >= 0 and health <= 100, "Invalid health: %d" % health)
static func assert_valid_position(pos: Vector2, bounds: Rect2) -> void:
assert(bounds.has_point(pos), "Position out of bounds: %s" % pos)
undefinedclass_name Validation
static func assert_valid_health(health: int) -> void:
assert(health >= 0 and health <= 100, "Invalid health: %d" % health)
static func assert_valid_position(pos: Vector2, bounds: Rect2) -> void:
assert(bounds.has_point(pos), "Position out of bounds: %s" % pos)
undefinedTest Organization
测试组织结构
test/
├── unit/
│ ├── test_player.gd
│ ├── test_enemy.gd
│ └── test_inventory.gd
├── integration/
│ ├── test_combat.gd
│ └── test_save_load.gd
└── fixtures/
├── test_level.tscn
└── mock_data.trestest/
├── unit/
│ ├── test_player.gd
│ ├── test_enemy.gd
│ └── test_inventory.gd
├── integration/
│ ├── test_combat.gd
│ └── test_save_load.gd
└── fixtures/
├── test_level.tscn
└── mock_data.tresBest Practices
最佳实践
1. Test Edge Cases
1. 测试边缘情况
gdscript
func test_edge_cases() -> void:
player.take_damage(0) # Zero damage
assert_eq(player.health, 100)
player.take_damage(-10) # Negative (heal?)
assert_eq(player.health, 100) # Should not changegdscript
func test_edge_cases() -> void:
player.take_damage(0) # Zero damage
assert_eq(player.health, 100)
player.take_damage(-10) # Negative (heal?)
assert_eq(player.health, 100) # Should not change2. Isolate Tests
2. 隔离测试
gdscript
undefinedgdscript
undefinedEach test should be independent
Each test should be independent
func before_each() -> void:
# Fresh setup for each test
player = create_fresh_player()
undefinedfunc before_each() -> void:
# Fresh setup for each test
player = create_fresh_player()
undefined3. Test Critical Paths First
3. 优先测试关键路径
Priority:
1. Core gameplay (movement, combat)
2. Save/load system
3. Level transitions
4. UI interactionsPriority:
1. Core gameplay (movement, combat)
2. Save/load system
3. Level transitions
4. UI interactionsReference
参考资料
Related
相关内容
- Master Skill: godot-master
- Master Skill: godot-master