This post originated from an RSS feed registered with Agile Buzz
by Steven E. Newton.
Original Post: Unit Tests Manage Complexity
Feed Title: Crater Moon Buzz
Feed URL: http://www.cmdev.com/buzz/blosxom.cgi?flav=rss
Feed Description: Views and experiences from the software world.
Unit testing is more than a design tool, not just a practice that
enhances quality, it is a tool for managing complexity.
A proper, well-written unit test isolates a particular piece of
behavior, and the code that implements it, from all the unnecessary
surrounding context of the running system. The unit test focuses on
an aspect of the code in a way that frees the programmer from the need
to build and maintain a complex mental picture of the running system.
A programmer doesn't have to "hold it all in her head". Where before
a programmer might have had to slowly build up a detailed mental model
of the executing logic in order to understand the complex behavior
sufficiently to (manually) evaluate and compare the results of a test run
with the expected behavior, a good unit test narrows the scope of what
the programmer must comprehend to something so simple that evaluating it
can be done quickly and repeatedly, as often as necessary, automatically.
This narrow and simple focus makes it easer to achieve and recover
"flow", and less costly to lose it because of interruption. It becomes
so easy to maintain flow that it is possible for two programmers to work
together in a combined state of flow.
The unit defined by a test represents a locally self-contained
element of simple behavior. If something is too hard to test, that is
a strong indication that it is not simple enough. The programmer needs
to remove excess behavior from the context until the code being tested
represents a unified conceptual whole.
In my early days as a programmer, the usual process consisted of
thinking a while about the problem until I had a notion about how an
implementation might look.
writing a bunch of code that seemed like a correct solution at a
high level
fixing syntax errors until the code compiled
fixing bugs until the code produced the correct output.
dropping down to a lower level and repeating.
I called this "top-down design".
As the code grew and became more detailed, starting up the system
and running through the steps to verify the latest changes meant keeping
more and more context in my head. This context includes things like
what had just been done, what this run was supposed to have working,
and what might break. It was a lot of work to get into flow, it was not
easy to maintain, and it was easy for distractions to break flow.
Test-driven development breaks the work down into tiny chunks of
independent function that don't require a complex mental context of
logic to be in the programmer's head.
Before TDD: A programmer had to keep a complex logical context in a
mental model. With TDD: The programmer only keeps a simple context of
making the next test pass.
Before TDD: Build and run the entire system, manually check the
behavior, set debug breakpoints, and try to remember what was being
worked on. With TDD: Build the piece being worked on, run the one test
that is failing, have the behavior checked automatically, understand
why the test fails. Do something simple to make the test pass. Repeat.