Replies: 6 comments 7 replies
-
Hi when i correct understand than you have to use a spy to verify a certain function is called with a expected set of arguments. The missing pice here is a Test timeouts can be set right now by using the arguemt The test could be look lke
And for signal should be duable aktually by
An example can be find here https://github.com/MikeSchulze/gdUnit3-examples/blob/59bc2b8785526ed83b4b03a8a6c61e364984b9e2/SignalsTestExamples/test/ExampleSpyWithSignalTest.gd The examples can be installed by open the tools from the inspector and pess the "Install Examples" button. |
Beta Was this translation helpful? Give feedback.
-
Thank you for writing up a suggestion. Using a spy will not work for this. Let me quickly share the implementation of the new game dialog driver: class_name NewGameDialogDriver
extends DriverBase
func is_present() -> bool:
var root = _root()
return root != null && root.name == "NewGameDialog" So this driver basically inspects the node hierarchy and checks if a certain node is there. So there is no setter for func test_i_can_start_a_new_game():
# when
_game_driver.main_menu.press_new_game()
# then
# wait 3 seconds
yield(get_tree().create_timer(3000), "timeout")
# check that the dialog is visible
assert_that( _game_driver.new_game_dialog.is_present() ).is_true() That would work, however it is inefficient, because I wait the full 3 seconds even if the menu switch only takes 100ms. So what I want would be more like: func test_i_can_start_a_new_game():
# when
_game_driver.main_menu.press_new_game()
# then
# make me a timer
var timer = auto_free(Timer.new())
# start it
timer.start(3000)
# within the next 3 seconds check if the dialog appears, every frame
while timer.time_left > 0:
# is the dialog visible?
if _game.driver.new_game_dialog.is_present():
break
# else wait a frame
yield(get_tree(), "idle_frame")
# check that we didn't run in a timeout and that the dialog is now visible
assert_that( timer.time_left > 0 and _game_driver.new_game_dialog.is_present()).is_true() This would make the test only run for as long as the dialog actually needs to materialize but it is extremely verbose and makes the test both hard to write and hard to read. Hence my idea of having an assert that basically does this loop: func test_i_can_start_a_new_game():
# when
_game_driver.main_menu.press_new_game()
# then
# this will run the loop and either terminates after 3 seconds or when is_present returns true
yield( assert_that( _game_driver.new_game_dialog ) \
.within_millis(3000).result_of_call("is_present").is_true(), "completed" ) |
Beta Was this translation helpful? Give feedback.
-
I got a first version of this running locally and made it even more readable by adding an func test_i_can_start_a_new_game():
# when
_game_driver.main_menu.press_new_game()
# then
yield( assert_within_millis(3000).return_value_of( _game_driver.new_game_dialog, "is_present" ).is_true(), "completed") Checking a signal is equally readable and doesn't require an extra line of code to trigger the runner or create a spy: func test_scene_changes_are_announced():
# when
_game_driver.main_menu.press_new_game()
# then
yield(assert_within_millis(3000).that_object(Global).sent_signal("scene_changed"), "completed") |
Beta Was this translation helpful? Give feedback.
-
@derkork feel free to play around #159 |
Beta Was this translation helpful? Give feedback.
-
@derkork can you give me please some feedback? Best Regards ps. Ich sehe gerade du kommst ja auch aus Leipzig ;) |
Beta Was this translation helpful? Give feedback.
-
I'm currently writing up some tests for my game, which is mostly an integration test thing. So basically i have a driver class that provides an abstraction of the game for the tests, so I can write tests like this:
These tests are very resilient to code and UI changes as they are on a high level, and they immensely help verifying that the game still works. However you can immediately see the problem with these kinds of tests. Most actions do not have an immediate effect. There are animations played which take time, there is stuff loaded which takes time, so I have to litter waits everywhere. On top of that, those waits actually need to know what is going on below the hood. E.g in the example above, when I press "New Game" in the main menu, actually a scene switch happens. So the test needs to know that and it needs to wait for signalling that this scene switch has happened.
However this is not what I want to test. On a high level I want to test:
I don't actually care if a scene needs to be loaded or if some animation plays. So I wondered if we could maybe have timed assertions. Basically the
assert_that
function that takes an object will get the option to switch it to a timed assert by calling thewithin_millis
function. A timed assert allows you to repeatedly check for a condition until either the condition is satisfied or a timeout occurs. If the condition is not satisfied within the timeout, the assertion fails. Finally you can yield on such assertions. With this, I could rewrite above code like this:This would make integration test code much easier to write (and read) and it would also help decouple the test code from the actual implementation. This could potentially even eliminate the need for having the
simulate_until_xxx
methods in the scene runner, e.g. a timed assertion could also check for signals being emitted:So basically the assertions API would be extended like this:
I would volunteer to write a proof of concept implementation of this and submit it as a pull request, but since this is a bit more work, I would first like to present the idea to you and flesh it out before spending a lot of time on coding this, only to have it rejected. What do you think?
Beta Was this translation helpful? Give feedback.
All reactions