Unit Testing
So you might have heard of this development trick called Unit Testing – most likely in the context of some business application or software engineering talk, but probably not related to games. It’s something that large, bulky projects have, with a ton of different programmers working on the same stuff, but it doesn’t make sense to do something like this yourself, right?
Wrong!
I didn’t believe it myself for quite a while – but at the same time, testing Duality was starting to become a more and more tedious work. It took two years of expanding the project and gathering users, until the overall system complexity and the fear of breaking something for everyone had outweighed my ignorance: At one point I had to make changes to core behavior that affected nearly everything, and had the potential to break a lot of peoples projects when handled the wrong way. It was of no question that I had to test this thoroughly, yet I started to get annoyed by repeatedly testing the same things over and over again.
Run the editor, load this Scene, click here, verify the debug outputs. Repeat.
After a while, my programmer instinct kicked in and demanded to find a way to automate the whole process. Well, how could that be done? Unit Testing just sat there, grinning complacently.
Do’s and Don’ts
I’ll just assume that you’re not a huge team of programmers, or developing something that will probably kill people when crashing. Just minding your own code, and maybe releasing some of it to the public for others to use. There are different approaches to Unit Testing, and you should pick one that fits your situation. If you’re thinking of Unit Testing as in »I’m gonna test all methods of all classes!!«, then no, Unit Testing will likely be not a good choice for your one-man hobby project, and may end up introducing more limitations than benefits to your development cycle.
Instead, be reasonable about what to test and how to test it. Keep in mind that writing and moreover maintaining Unit Tests costs time and consideration, and if that cost is higher than the gain through automated testing, then you are better off testing manually. Writing tests just for the sake of having covered it all will keep you busy for quite a while and require a lot of maintenance, and this is usually not what you want to achieve.
If you are pondering about whether or not to introduce Unit Testing to a large, existing project of yours, then you are sitting in the same boat with my past self. Here is what I’ve done when I finally decided to give it a go:
Don’t test trivial things.
It’s a no-brainer actually, but I still find myself running right into breaking this rule. In the same way, you wouldn’t comment a line that says »list.Add(element);« with »Add element to the list«, you shouldn’t write a test that makes sure it does so. At least not if you want to spend some time with actual coding as well, rather than writing tests.
I’ve found it to be enough to tests actual use cases or complex behavior. If trivial things fail, the complex ones depending on them will fail too, so you’ll still get a hold on those errors.
When writing a new game component or engine feature you’re still experimenting with: Don’t Unit-Test that – yet.
When writing Unit Tests, you are making an assumption on how something behaves and how it is structured. But can you really do that for that thing you just wanted to try out quickly?
Usually not. Instead of getting lost in specifying behavior and public API, just code it already and see where it leads you. Unit Testing can help you to make sure that something retains a certain state, but prototyping and fast iteration require the opposite.
You can still write some tests, after you’ve settled with a certain concept and API.
When writing a new, self-contained class that has a well-defined, constant behavior: Unit-Test the hell out of that one.
Specialized containers, distinct algorithms, generic helper classes and the like are merely basic tools that you and others will use throughout the whole project.
You don’t expect a screwdriver to suddenly behave differently, and you wouldn’t try to change its overall concept. It’s just a screwdriver. Unit Tests make sure, that it remains a functional one, despite all the internal changes that you may introduce throughout development.
When trying to fix a bug, write a Unit Test that reproduces it.
If you see the test result turn red, you know that you’ve successfully reproduced the bug. After you’ve managed to turn it green again, the test will make sure your bug never comes back without warning.
Of course, this doesn’t apply to all bugs, because you can’t test everything equally efficient using an automated system. User Interfaces are hard to test, Graphics and Audio are even harder. Serialization is a candidate that I encountered to be excellently testable, to name one positive example as well. If you can’t think of a simple way to implement an automated test case within ten minutes, it’s probably not worth it for a quick fix.
When changing something you can’t ever afford to break: Write some focused Unit Tests before you start.
There is no way to make sure something is still working, except to test it. And you’ll probably have to do this a lot, especially when the subject at hand is a key player in your software.
Once while writing those tests, I even discovered that a certain subsystem has actually never worked as intended and just happened to not cause any problems yet. Now it’s one thing less to worry about.
And that’s basically it. I am aware that the above guidelines may contradict best practices of at least some Unit Test paradigm out there, but they are what I personally have found to be a useful tradeoff between maintenance and benefit. A defensive flavor of automated testing that tries not to bother you too much, but help out where it can. Read it with a grain of salt, though: I wouldn’t consider myself a Unit Testing pro by all means. There’s still a lot to learn.