Test-Driven Development for Games
Ξ August 24th, 2007 | → 0 Comments | ∇ Software Development |
Jamie Fristrom has some opinions on Test-Driven Development after using it for a year, and it seems that they’re mostly negative. It’s quite interesting to compare this with his experiences with TDD after only a few weeks.
Some interesting observations:
After a few weeks:
TDD is forcing me to pay more attention to the code, and I’ve actually found bugs just from studying it.
After a year:
Frankly, I’m disappointed. I was hoping for a bigger, more obvious win.
The number of “false positives” - tests that break because my assumptions changed - greatly outnumbers the number of real bugs really caught.
Also, they say it improves your code quality. Frankly, my code quality is worse, as I frequently expose things I wouldn’t have exposed so I can get them under test more easily - and other abuses.
Quite a different story after a year, and I’m not really sure I agree with his final word. Code quality is subjective of course, but in my experience TDD often results in more clearly defined interfaces. Sure, sometimes one needs to expose methods and functions that could have been strictly internal, but they are often more well-defined and….well…functional, with a clearly defined purpose.
After a few weeks:
TDD seems very well suited to gameplay and AI programming. It’s hard to TDD graphics and sound, but in the virtual world that the graphics and sound are just a representation of it’s a good fit.
After a year:
And we still end up introducing lots of bugs into the code. Partly because we’re not covering everything (not only are we not covering cosmetic features, “is this sprite/menu item/whatever in the right place?”, we’re not covering experiments, “Will it be more fun this way?” even if those experiments end up sticking) and partly because you just can’t cover all the cases of all the functions, and partly because the tests don’t catch multithread problems.
Oooh… To me it seems like the engine/framework code was suitable for TDD, but when it comes to the artistic side things get worse. I agree completely with the latter sentiment: there’s no simple way to test these things using unit tests. I think the reason is hinted in the last part. Multithreading is a problem, but so is user input and everything that concerns subjectivity. They’re haphazard and volatile and other cool words that indicate that they’re inherently problematic because the system complexity increases in orders of magnitudes when you introduce these things.
I agree with his final decision that TDD is not for him; it might be useful for some parts of game development, but the biggest - and most critical - parts cannot be easily tested. At least I know of no simple ways to do so. However, TDD itself isn’t bad. It forces a functional structure; it captures stupid memory leaks and such; but mostly, it’s extremely useful for projects with multiple members.
Jamie mentions the fact that his understanding of the code has increased due to TDD, and this is iiiincredibly useful in some projects. “How did they intend this function to be used? I know - let’s check out the unit test!” Another benefit of TDD comes when you’re working with other people’s code, even if they have no unit tests for you to glance at: if you’re using valgrind or other tools of that crowd you’ll easily detect whenever you’re using a library incorrectly. Trust me, it’s not always obvious what exactly needs to be deallocated…but if you’re running a unit test for a specific function, you’ll know instantly when something was done incorrectly.
On a final note: have you noticed that I’m only mentioning unit tests and their benefits? There’s a simple reason for this: I like unit tests, but I hate TDD itself. Writing the tests first is cumbersome and a waste of time in my view. Maybe I’ll change my ways as I gain more experience, but my current (non-game development*) approach looks more like XP, with rapid iterations:
- Write a quick prototype
- Write unit tests for the most obvious functions
- Add more functionality and refactor if necessary
- Unit test the new functionality
- Repeat refactoring and unit tests until it’s good enough
I’m still undecided whether this means that I’m too unstructured and inexperienced, or if it’s actually a benefit to let code grow like this. Either way, I definitely think that unit tests have benefited in my work.
* The game development iterations look more like this:
- Write a quick prototype
- Sigh and re-write the prototype
- Add some graphics
- Re-design the game and re-write the prototype
- Sigh and re-design the game because the gameplay is dull
- Go back to the original design, modify it, and re-write the prototype
- Repeat for a while until all of the sudden I realize that the prototype is starting to look like a proper game
- Add music and graphics and make a final version
- Test the game
- Scream in frustration when I realize that the game sucks
- Re-design the game, add features, re-write parts, and repeat ad nauseum
