|
High
Moon Studio developers Noel Llopis and Sean Houghton have been working
with a unique build method called test-driven development (TDD). In an
intermediate level lecture at GDC 2006, the programming duo discussed
what TDD is, how others can use it in their development cycle, and the
benefits of testing often--a primary focus of the methodology.
TDD
is a cyclical, three-point procedure that moves as such: write test,
write code, refactor, and back to write test. Llopis and Houghton are
using TDD with C++ as well as Unreal code base.
TDD
is simple in both concept and implementation. “It comes down to one
very simple cycle. You think of a very, very small feature and ... you
write a very small test for that feature. We're talking about something
very small,” said Llopis. Then, the programmer tests the piece of code.
If it fails, he says, “you write some code to make that test pass. So
now you have some passing tests. The next step is the crucial one for
TDD: you refactor things,” changing the code in whatever necessary ways
without changing its functionality.
The
whole cycle should take less than a minute, according to Llopis,
stressing the importance of quick, small improvements. Although the
process could take as much as three or four minutes, “if it takes ten
minutes, it's not a good sign,” according to Llopis.
Typical
problems programmers face, such as bugs, unstable builds, risk (when
implementing changes), and early freezes, are all addressed in TDD.
Benefits of the TDD system include simplicity, modularity, and instant feedback.
“The
main root of the problem [is that] code gets out of hand too quickly,”
says Llopis. With TDD, the code is self-contained and is tested in
isolation. “What is the first thing that you do with TDD? You write a
test. You're writing your code from the point of view of the user of
your code. You are your own first user,” says Llopis. “And you end up
with really much more usable code.”
TDD
also provides a safety net, freeing programmers to make the kinds of
changes they actually want to make, rather than working around a
complex in-place codebase. “Otherwise, how do you do major changes? You
run the game, you run a couple of levels?”
TDD
also improves feedback. Programmers not using TDD only receive feedback
at often wide-spread intervals, such as milestones, which might come
once every two months, or possibly at interation points (every two to
four weeks). And though nightly builds or automated builds, which can
give feedback once a day or once an hour, TDD, by its nature, provides
even more frequent feedback: “every 30 seconds to three or four
minutes,” according to Llopis.
“In
the worst case, if something horrible happens, you only lost three
minutes. It also tells you that every three minutes, you're making
progress,” he says, which can boost a coder's confidence and perceived
value. “You're not just more confident, but it changes the feeling you
get as a programmer working on your code,” he adds, commenting on the
“joy of programming again with instant feedback.”
Houghton
provided examples of how TDD can be used in a game. Most of the TDD
sample code was less than 15 lines total, reiterating the importance of
keeping the unit tests small.
“It's
the most simple test you can run using TDD: testing return values,”
says Houghton, whose example involved a character with a shield that's
sustaining damage. He used CHECK_EQUAL and GetLevel to test his code.
The tests, he says, can be put in four places: testGame.exe (links with Game.lib); #ifdef UNIT_TESTS (used on Unreal); GameTests.DLL; or GameTests.upk (for Unreal
script tests). “You can put them anywhere you want depending on how
your build system is set up. Every time you build, you want to run your
unit tests,” he adds.
“You want your unit test to run as part of the build. A failed unit test is considered a failed compilation.”
Llopis
showed more examples of running a couple of tests on the shield
component, interacting with the code on the screen to give the
programmer audience a strong understanding of exactly how the scenario
would play out for the coder. Llopis repeated the TDD cycle a number of
times until his code compiled properly.
“We
compile. The build fails. [The program] highlights the test that failed
for you. This is as important as a syntax error. Let's fix it by
implementing the code that failed.
We'll
use the simplest possible update. Clearly, this is not how we're going
to ship the game to the customer, but right now, just make the test
pass.”
With
each iteration, Llopis created a new variable, which isolates the
problem when the tests fail. “Only try to get one part to pass,” he
recommends.
Houghton
furthered the examples by showing how a mock object can be used to keep
up the speed and cyclical approach that TDD promotes.
Houghton and Llopis shared a few of the best practices they've established using TDD:
- Test only the code at hand, isolating tests or objects (using mock objects to refrain from having too many objects).
- Keep
tests simple. Houghton and Llopis shared some examples of physics code
tested at High Moon Studios to show how precisely how simple they mean.
The code shown contained about 15 lines.
- Keep
tests fast. With the TDD methodology, tests should not take 20 or 30
seconds to run. Programmers should aim to run 500 or 600 tests in about
half a second.
- Keep
tests independent. “If a previous test messes up the state, then the
next test is going to fail. .. Restore to a cleanroom state before
you're done with your test,” says Houghton.
“Not
only are programmers willing to do the TDD work, they like it so much,
that they go home and they use TDD on their home projects. They really
see the benefits, and their work becomes much more enjoyable,” says
Llopis.
A
paper version of this talk, including more detail about testing API,
middleware, and running tests on consoles, is available on Noel Llopis'
web site: www.gamesfromwithin.com.
_____________________________________________________
|