Summary
I'm trying write a unit test using Gut for my player
in gdscript but calling get_position()
on the player
(a KinematicBody2D) is causing the following error:
ERROR: get_global_transform: Condition "!is_inside_tree()" is true. Returned: get_transform()
At: scene/2d/canvas_item.cpp:467.
ERROR: body_test_motion: Condition "!body->get_space()" is true. Returned: false
At: servers/physics_2d/physics_2d_server_sw.cpp:1046.
ERROR: get_global_transform: Condition "!is_inside_tree()" is true. Returned: get_transform()
At: scene/2d/canvas_item.cpp:467.
ERROR: body_test_ray_separation: Condition "!body->get_space()" is true. Returned: false
At: servers/physics_2d/physics_2d_server_sw.cpp:1058.
Expectation
I was expecting that after _physics_process()
I would be able to get the updated position
of my player
but it returns Vector2(0, 0)
.
Resources
This is my first time using GDScript so I have been using a mixture of the following resources in an attempt to write this first test:
Failed solutions
I think I may need to make the player
a child of something else but I'm unsure how to do this within my test.
I tried searching the Gut issues on how to use add_child()
within a test but I couldn't find an answer.
I also tried making the player
a child of an Area2D
but that didn't seem to work either:
var player := Player.new(input)
var area := Area2D.new()
area.add_child(player)
Code
This is the code that is causing the error when calling gut
from the command line:
test/unit/actors/test_player.gd
extends "res://addons/gut/test.gd"
func test_can_create_player() -> void:
var input = MockInput.new()
var player := Player.new(input)
assert_not_null(player)
func test_can_move_player_up() -> void:
var input = MockInput.new()
input.press("ui_up")
var player := Player.new(input)
simulate(player, 1, .1)
assert_eq(player.get_position(), Vector2(200, 0))
test/mock_input.gd
class_name MockInput
var _pressed: Dictionary = {}
func press(key: String) -> void:
_pressed[key] = true
func release(key: String) -> void:
_pressed[key] = false
func is_action_pressed(key: String) -> bool:
if _pressed.has(key):
return _pressed.get(key)
return false
entities/actors/player.gd
extends KinematicBody2D
class_name Player
export var speed: int = 200
var _input: Object = Input
var _velocity: Vector2 = Vector2()
func _init(input: Object = Input):
_input = input
func _physics_process(_delta: float) -> void:
_velocity = Vector2()
if _input.is_action_pressed("ui_right"):
_velocity.x = 1
if _input.is_action_pressed("ui_left"):
_velocity.x -= 1
if _input.is_action_pressed("ui_down"):
_velocity.y = 1
if _input.is_action_pressed("ui_up"):
_velocity.y -= 1
_velocity = _velocity.normalized() * speed
_velocity = move_and_slide(_velocity)
CodePudding user response:
The error is telling you !is_inside_tree()
. The problem is not -only- that the node does not have a parent. The problem is that it is not inside a SceneTree
.
You can create your own instance of SceneTree
. You can even extend it. For example, this would work:
var node := KinematicBody2D.new()
var tree := SceneTree.new()
tree.get_root().add_child(node)
yield(tree, "idle_frame")
print(node.get_global_transform())
Now, about Gut… Your tests will be running inside a SceneTree
. Thus, you can add the nodes you want to test as children. Just remember to do proper teardown (remove the nodes). Because Gut will not do it for you, and if children nodes linger from one test to another, then they aren't units anymore.
Addendum: You can use add_child_autofree
to add a child, and also queue it for freeing right after the test ends.
The rest of the answer is me going over how Gut runs tests to confirm they run inside a SceneTree.
The file gut_cmdln.gd
is actually an extended SceneTree
. It will load gut.gd
(here) and add it as a node to itself (here).
In turn, gut.gd
is in charge of running the tests, which is handled by _test_the_scripts
. What I want you to look is this:
var test_script = the_script.get_new()
var script_result = null
_setup_script(test_script)
There the_script
is a TestScript
object. The get_new
call will return an instance. And _setup_script
…
func _setup_script(test_script):
test_script.gut = self
test_script.set_logger(_lgr)
add_child(test_script)
_test_script_objects.append(test_script)
Will add it as child (of gud.gd
).
Afterwards _test_the_scripts
will go into a loop (here) and call _run_test
or _run_parameterized_test
(which calls _run_test
) to actually run the test. And _run_test
will use call
on the instance to run the test method (with extra steps).
Huzza! Your tests will be running in a custom SceneTree
.
CodePudding user response:
I found something that worked for my purposes (I'm a little bit annoyed that I missed this the first time).
As I suspected, the player
needs to be a child of another Node
.
In the test/unit/actors/test_player.gd
after the player
variable has been defined it can be added as a child of the test itself using:
self.add_child_autofree(player)
With the complete unit test as the following:
func test_can_move_player_up() -> void:
var input = MockInput.new()
input.press("ui_up")
var player := Player.new(input)
self.add_child_autofree(player)
simulate(player, 1, 0)
assert_eq(player.get_position().round(), Vector2(0, -30))