I’ve written before about automated tests, why we use them at Rare, and how to use them effectively. In the time between then and now I’ve learned a lot about our test systems and tests in general, so I thought it was time for a more in depth look at testing, especially the much maligned unit test.
Unit tests are often considered unnecessary guff that’s just more code to write and maintain- but often they are not given a fair chance. Like any other new technique, we start by writing bad tests on code written without testing in mind, have a miserable time, and write off testing entirely as a bad job. But, writing great tests on great code can be easy peasy. I’m going to cover some specific techniques on how you can write great tests on great code, and get a lot more out of it in the process.
My colleagues sometimes get into heated debates about the difference between a programmer and an engineer. The latter is more common in the US than the UK, and often people in the UK don’t really understand what is meant by it. My usual answer to this question is that words are made up and don’t mean anything, but my helpful answer to this question is that to me, programmer means “programs things”, engineer means “works with technical systems”, and the former is a subset of the latter.
Before I got hired as a software engineer by Rare, I studied mechanical engineering at university, which means designing mechanisms and structures. I learned lots of interesting stuff about different kinds of steel, but I also learned about the engineering principles behind designing any functional system. I use that knowledge at work daily, and even though I picked it up in the context of clevis pins and composite materials it applies very nicely to templated functions and explicit constructors. So here’s the end to end process I use for designing technical systems of any kind.
It is a truth universally acknowledged, that a readable codebase in possession of a good engineer, must be in want of comments. As manipulators of logic, we programmers (and sith, I guess) tend to have a habit of dealing in absolutes. You’ll often see “well commented” as a metric of readability on university marksheets and interview homework checklists. In the spirit of breaking that habit, I’m not about to argue against comments as a whole. Instead, I’ll show you how thinking of them as a last resort it can force you to deal with the root cause of confusing code- instead of just treating the symptoms.
If you’ve ever played Sea of Thieves, the game I’m working on as an engineer at Rare Ltd, you may have run afoul of skeletons firing cannons at you while you sail past an island, minding your own business. One of the first pieces of work I did here at Rare was programming the algorithm the skellies use to aim the cannon and provide a sense of emergent danger for players. In this article I’ll give you a peek behind the curtain at how we’d build up the moving parts of a gameplay feature. We’ll go step by step through how I designed the first version of this cannon aiming algorithm, which was released around E3 2017 to our Technical Alpha players. The code may have changed quite a bit in the year since we released it, so it may not exactly match the behaviour you can see in game now.
When you work on a large, constantly changing codebase with lots of other engineers, there’s a lot you don’t know about the person who will next change your code. They could be a new hire who isn’t yet familiar with the code standards. They could be an experienced programmer with a different opinion about code design. They could just be your future self, having a long day and prone to making errors. At their lowest common denominator, each of these people is a very fallible human being with a base level of programming understanding and a heap of cognitive biases- I suggest that you write your code for this person.
If you think of each class or set of functions you write as an API, you can anticipate what ways it could be misused or misunderstood and take preventative measures. This is so much quicker than it is for someone else to dig through the source files trying to understand why your function call isn’t returning what they expected. Imagine that the hazards in your code like a dereferenced pointer or a non intuitive return type are like physical hazards in the real world. If you owned public property with a big hole in the middle, you’d put up warning signs and maybe a fence to prevent people from falling in. Code has the additional challenge that people then have the authority (and maybe good reason) to come along and knock your fence down or fill the hole with sulphuric acid, but at least your fence says “people probably shouldn’t be allowed to fall in here”.
At Rare we tend to avoid using comments in the codebase, and very few files have any. This may be a matter of taste, but it definitely has the effect of encouraging you to learn to use all the tools at your disposal to make your code speak for itself. Here I’ll break down different types of “code hazard”, and how we can guard or eliminate them without relying on code comments or documentation.
It’s not exactly traditional to use automated testing in the game development process, but at Rare we’ve been developing our title Sea of Thieves with a continuous delivery method. This means we try and have a stable production-ready build at all times, and this relies on the automated tests running round the clock. Every time we check in a change to the build, it has to run against the current build and pass all the tests. If you want to learn more about how our games-as-a-service based development process, there’s a really interesting series of blog posts about it here , written by my colleague Jafar Soltani from the infrastructure team.
So what’s it like working at the pointy end of this continuous delivery model? It can feel like it’s all fun and games until you have to write the tests, or at least it did when I joined. One year, and several silly mistakes caught by simple unit tests later, I’m decidedly pro-testing. When your unit tests are part of the implementation and debugging process, it becomes a tool rather than an annoyance.