(Image credit: Godot blog)
Godot affords the possibility of unit and integration testing gdscript
code via the GUT extension. If you're getting started with unit/integrated testing (collectively "automated testing") or Godot, and interested in what automated testing can do for you, read on.
Your game contains lots of logic. When you make changes, you need to re-test that everything that worked before, still works (nothing broke). As you find and fix bugs, and get into corner-cases of your game (eg. testing the entire play-through), manually re-testing everything becomes more and more time-consuming.
Automated testing provides a solution to these problems: what if you could write code that would test your game code? What if that code could run through all the necessary scenarios of your game in a matter of minutes, or seconds? What if you could run the tests after every single change, and be sure that you never broke anything?
Automated testing provides exactly this benefit. You write test-code that tests your "production" code. Unit tests, which test very small levels of functionality, run in in a matter of milliseconds; integration tests, which typically test workflows (fight monster, get experience, level up, spend skill points) take longer, but allow you to automate workflows.
Finally, any experienced developer with automated testing will agree: the mindset change you get from automated testing is unparalleled. Not only can you test every single line of code you change, making sure it works, but you can also make bigger, riskier changes, because of your safety-net of unit tests. You can actually go faster, and make bigger changes, with your safety net of automated tests.
Like many things, automated testing is a trade-off. Unit testing is the de-facto standard now in the software industry, so we happily accept the downsides in exchange for the wealth of benefits we receive. Regardless, here they are:
Overall, the benefits heavily outweigh the costs. There's really no reason not to write automated tests!
We touched earlier on the difference between unit and integration tests. Typically, unit tests test a single method or class. The hallmark of unit tests, is that they don't depend on anything external - they don't touch the database, the file-system, they don't make web-service calls, etc. Because of this, unit tests tend to be extremely stable and robust, and run in sub-millisecond time. A large suite of thousands of unit tests may run in just a few seconds.
However, unit tests, by nature, test only a very limited set of functionality. Integration tests, in contrast, typically test more complex scenarios, and/or workflows.
Imagine we wrote an RPG-style game. The user can battle monsters; on victory, he receives experience points, and can use those to invest in learning new skills. If we structured this as typical domain-driven classes (such as Player
, Monster
, etc.) we might write unit tests such as:
Player.attack(target)
damages the targetPlayer.hurt(damage)
decreases healthPlayer.hurt(damage)
throws an error if damage is less than zero (programmer bug)These are all class/object/method-level tests. In contrast, we might write integration tests such as:
These tests, by nature, work with a larger set of code - so you can test more things. But, they can also be more fragile - if something breaks in the workflow (damage calculation broken? Level-up calculation broken? Skill-points UI broken?), the test can fail.
Godot provides two options for unit testing, depending on whether you prefer C# or GDscript.
If you prefer C#, create your project as a Godot + Mono project, and write as much logic as possible in the C# code. This way, you can use the "usual" set of C# testing tools (NUnit, Moq, etc.) and everything Just Works.
If you can't, won't, or prefer GDscript, you can use the GUT extension. (GUT stands for Godot Unit Test.)
The GUT project page on GitHub provides a detailed explanation of getting started, which I won't repeat here. Install GUT in your project, create a new scene with the Gut
object, and you're ready to go.
Instead, I'll focus on getting started testing: what do you test? Assuming you have an existing project with an extensive code-base and zero unit tests, what do you do?
Fundamentally, you need to start doing two things:
1) Refactor your code to make it more testable 2) Write tests for any new code
We didn't really touch on testable code in any depth. The short version is: write simple classes with simple methods, and avoid dependencies on any external "things." For example, if your Player.damage
class depends on a Globals.battle_difficulty
multiplier, accept it as a passed-in value instead of referencing the Globals
script. Change this:
// player.gd
class damage(target):
var total_defense = target.defense * (1 + Globals.battle_difficulty)
target.current_health -= self.strength - total_defense
// Elsewhere
player.damage(selected_monster)
To this:
// player.gd
class damage(target, battle_difficulty):
var total_defense = target.defense * (1 + battle_difficulty)
target.current_health -= self.strength - total_defense
// Elsewhere
player.damage(selected_monster, Globals.battle_difficulty)
While the code is functionally equivalent, the latter is far more testable: you can pass in whatever value of battle_difficutly
you want in your tests, even invalid values like -3! In fact, this is the kind of functionality you want to unit-test: in addition to making sure it works with a correct difficulty, you can test that it fails appropriately (eg. throws an error or returns 0) with an invalid value - something you can't easily test through the actual game.
We covered a lot of ground in this article. In summary:
I hope this post benefited you, and convinced you to at least try unit testing! Please let me know your feedback on Twitter - whether you decide to try testing, what you didn't understand, and what kind of topics you would like me to cover in future articles on Godot and testing.
Liked our updates? Subscribe to our newsletter to get access to our game demos and exclusive insider updates!