Test Driven Debugging
Development on “new features” this month has honestly been a bit slower than I would have liked. It turns out that the maths underlying a lot of orbital mechanics is really quite complicated! However, something that’s really helped has been having those “automated tests” set up which I mentioned last month…
I know Test Driven Development isn’t exactly anything new per se, but it doesn’t seem to be quite as common in game development as in other fields. It’s always been one of those things that I’ve felt I should get into the habit of doing but never quite found the time to prioritise and has also felt a little intimidating. In addition, the few times I’ve tried to do TDD in the past, it’s been a very good way to chew up time and make things more complicated, rather than less. That’s likely mostly a result of my own inexperience, of course…
This month, though, I’ve hit upon a technique that’s worked quite well for me, which I’m dubbing “Test Driven Debugging”: Instead of trying to sit down and write a bunch of tests before doing any coding, I write a bunch of code first (whiteboarding any complex parts as necessary), then run the game to see if that produces the expected output. If not then, I go ahead and create a new test function & use it a bit like I would a REPL environment. GUT makes it really quick & easy to run a specific test function, so it’s super quick to iterate while slowly stepping through the logic of some system and using asserts (or sometimes just good ol’ fashioned console output…) to confirm that intermediate results are as expected. As soon as I catch something that’s not coming out the way I’m expecting it, then I can go back to the source code for that thing & try to fix it (or my understanding of it!), then re-run the test(s) to check that the fix worked.
To give a more concrete example, I have a GenericOrbit
class which is supposed to be able to handle orbits with any eccentricity, however until recently the game only ever really included circular & elliptic orbits (i.e. eccentricity < 1). So when I started getting super-weird results from hyperbolic orbits, I went through the following process:
- Add a
test_hyperbolic_orbits()
test function, within which, I: - Created & initialised the necessary objects
- Started querying the relevant orbital parameters with different initialisation values. These were mostly fine
- Started querying the positions & velocities returned at different timestamps
- Noticed that I was getting
nan
values far more quickly than I would have expected - Went back to the code for the
GenericOrbit
class’s various functions, comparing them to the relevant math from the Fundamentals of Astrodynamics book I’ve been primarily using as reference. - Adding print statements inside those functions being executed to try & establish the point at which those
nan
values might be originating. - Found & fixed the offending lines in the code for the Stampff functions which were trying to square root a value which would often be negative for hyperbolic orbits.
- Re-ran the tests, updated some of the assert checks where necessary, added a few more, removed some of those print functions
- Ran the full set of tests to ensure I’d not broken anything else & then committed the change to source control
So it’s not that different to how I might normally debug my code, but with the difference that without really spending any extra time writing tests, I’ve got:
- A nice, convenient place to do it rather than an ephemeral REPL session.
- A signal to tell me if I break that thing in the future during a refactor or something and
- A sort of accompanying documentation I can re-read in the future to remember some of the quirks in certain systems etc.
I’m not sure if others use “TDD” this way and I’m sure that I’d horrify a lot of more experienced devs with my tests, but it’s been pretty helpful so far, especially when trying to get to the bottom of bugs resulting in subtle maths-y errors like this one! Lord knows there are a lot of opportunities for those in a project like this!