I wrote a little helper library to help you write unit tests for the logic in your Godot game's scripts.
First things first: What is unit testing and why would you want it? A unit test is just some code that checks other code to make sure it does what it claims to do. For example, if you implemented a path finding algorithm, a unit test would be code that checks that it does indeed find the correct path for a few different test scenarios. For traditional software development, "regression tests" are a non-negotiable must-have. While it may seem like a bother or that it will slow down development, writing good tests will actually speed up refactoring and optimization of code. This can speed up development in general.
In short... more tests for games = less buggy games! And there are simply too many buggy games out there, so let's try not write any more :)
Recently, I've been working on a tanagrams puzzle game called Sawdust. Core functionality of that game involves "filling up" a board by fitting pieces. It's difficult to test piece combinations by hand, I don't want any nasty bugs to creep in, so I wrote unit tests for it. I don't know if they'd be of any use to anyone, but if you like full examples, click here to see the unit tests I wrote for Sawdust!
Unit testing libraries
At the time of writing this article, I know of only two options: mine, and one other. The other option is GUT which seems to be pretty feature filled. But... this wasn't around when I needed it, so I ended up writing my own very simple one.
So, use GUT if you want something very full-featured. If you just want a simple layout and assertions with minimal set-up (a single file), then you might like my quick-and-dirty solution included below. :)
Download unittest.gd to some spot in your Godot project, such as
2. Create a test
Now you gotta write your actual tests. Create a GDScript file that extends
unittest.gd, and has a
func test() method that has a bunch of assertions. Here are some examples from the game Sawdust:
What's is it doing? If you aren't familiar with unit tests, you might not be sure what my example test is doing. I'll break it down:
"testcase" is simply a human readable way of separating what concept we are testing. In this, we test a few methods of the Board class.
assert_xstatements check for a certain condition, for example, that two things are equal, or that something is true. For a full list of what you can assert, look at the code of
If any checks fail, it will alert you when you run this test. This allows you to zero in on "regressions" or those little bugs that creep in as you work on something.
extends "res://scripts/unittest.gd" func tests(): testcase("board can place pieces and check for collisions") var board = Board.new() board.set_dimensions(13, 13) board.add_block(0, 0) # adds large block assert_false(board.check_placement(0, 0), 'near top left corner') assert_true(board.check_placement(2, 0), 'top left corner') endcase() testcase("rotating a piece 4x results in the original piece for all pieces") for i in range(10): var original_piece = board.piece_constants.PIECES[i] var piece = board.rotate_piece(original_piece, 4) assert_dict_equal(piece, original_piece, '4x rotation piece ' + str(i)) endcase()
3. Create a script to run your tests
Now, you'll want to be able to run all your tests at once.
unittest.gd has a helper to do this. Create a script like the following, add the unit test you just wrote to the list, name it
runtests.gd (or whatever you want) and plop it into your scripts directory:
extends SceneTree func _init(): load('res://scripts/unittest.gd').run([ 'res://scripts/example_unit_test.gd', ]) quit()
To run all your tests, run the following command in a terminal (where
godot is your Godot binary, wherever you have it):
$ godot -s ./scripts/runtests.gd - [OK] 1/1 - example_unit_test - [SUCCESS] 1/1
That's it! Happy testing :)
PS: All this code is free (MIT licensed like Godot itself)