Unit testing GDScript with GUT
Who says you can’t unit test your game?
I’ve been tinkering with Godot for a while and I’m starting to get more comfortable with GDScript. I recently found myself writing and rewriting some logic that’s quite decoupled from the game engine and thought, “It would be really cool to write some automated tests for this.”
I discovered the Godot Unit Testing (GUT) project and decided to give it a shot. So far it’s proven easy to use and it has allowed me to bring the power of unit testing to my game dev hobby (without having to write my own framework for it). Below is a quick guide I wrote for myself as I first set it up.
How to set up GUT
Installing GUT
Detailed instructions for installing GUT can be found here. GUT is installed on a per-project basis via the Godot asset library. Start by clicking the AssetLib button.
In the asset library, search for and install the asset named “GUT - Godot Unit Testing (Godot 4)”.
By default, this installs GUT in the “addons” directory of your current project.
Once installed, GUT needs to be activated. You can do this on the “Plugins” tab of the “Project Settings” dialogue.
Tick the “Enable” checkbox for the plugin named “Gut”. A new panel should appear at the bottom of the editor.
Checking that it works
If you click the “Run All” button immediately, you get an error message like the following:
Cannot run tests, you have a configuration error:
* You do not have any directories set.
Check your settings ----->
Create one or more directories for saving your tests. Then, in the GUT panel, scroll down to the “Test Directories” section. Here, enter each of the directories you want GUT to load tests from, e.g. if you have one directory named test then simply enter “test” in the 0th row (the toggle switch flips automatically).
You can test that your configuration works by clicking the “Run All” button again, which should greet you with a report such as the following.
Of course, this is not very useful until we’ve written some tests.
How to write tests
Test scripts naming
By default, test scripts are expected to begin with the prefix test_
, e.g. test_foo.gd. You can change this along with the expected suffix under the “Misc” section in the GUT settings.
For example, if you want to name your tests in the style FooTests.gd instead of the default test_foo.gd, simply remove the prefix and change the suffix from .gd to Tests.gd.
Test script conventions
All test scripts need to extend the base type GutTest
.
Furthermore, each test method name has to begin with the prefix test_
, e.g. test_method_works()
. Unlike the file name prefix, this does not appear to be configurable. Test methods also do not take any parameters (unless you’re writing a parameterized test, in which case it has a specific signature).
For example, given the following script:
class_name Foo
func bar(number: int) -> String:
match(number):
1:
return "Baz"
2:
return "Qux"
return ""
A test script might look like this:
extends GutTest
func test_foo_bar_1_returns_baz():
var sut = Foo.new()
assert_eq(sut.bar(1), "Baz")
func test_foo_bar_2_returns_qux():
var sut = Foo.new()
assert_eq(sut.bar(2), "Qux")
func test_foo_bar_other_returns_blank():
var sut = Foo.new()
assert_eq(sut.bar(0), "")
Running all tests gives the following feedback:
If you declare inner classes that start with the prefix Test
e.g. TestBar
then GUT will use those to group test methods in the results. For example, let’s say our earlier Foo
class has another method Hello
which needs testing. We could structure our test script this way:
extends GutTest
class TestBar:
extends GutTest
func test_foo_bar_1_returns_baz():
var sut = Foo.new()
assert_eq(sut.bar(1), "Baz")
func test_foo_bar_2_returns_qux():
var sut = Foo.new()
assert_eq(sut.bar(2), "Qux")
func test_foo_bar_other_returns_blank():
var sut = Foo.new()
assert_eq(sut.bar(0), "")
class TestHello:
extends GutTest
func test_hello_world():
var sut = Foo.new()
assert_eq(sut.hello("World"), "Hello World")
Now, the test methods for each inner class appear under a different heading in the test results, e.g.
Next steps
GUT has some good documentation. There are a lot of assertion methods and it’s worth familiarizing yourself with them. The library also supports doubles, spies, etc. for more advanced scenarios.
At the moment, I’m more focused on testing logic that’s isolated from the main game loop, but I’ve noted that GUT has support for simulating calls to engine methods like _physics_process
. If I decide to extend automated testing to my game objects, I’ll be sure to look into these.
Used for this article
- Godot Engine v4.2.1
- GUT - Godot Unit Testing (Godot 4) v9.2.1
- Windows 10 Pro