Software Design by Example 4: Unit Testing

Posted

The first draft of Software Design by Example didn’t have either the warmup chapter on systems programming or this chapter on building a unit testing framework. Instead, it opened with a tiny version control system inspired by Mary Rose Cook’s excellent Gitlet project.

But no book outline ever survives contact with reality. I made so many mistakes while writing the chapter on version control that I realized I’d have to show readers how to test something that interacted with the file system. I could have just used Mocha, but showing how xUnit testing frameworks find files containing tests and run tests from those files turned out to be a great way to introduce some ideas that I needed later.

Backfilling like this is a normal part of writing a technical book. You start by drawing a concept map like this:

File backup concept map

but it is necessarily just a small subgraph of a much larger set of ideas and relationships. The box in the upper left labelled unique name and the arc labelled uniquely identifies can both be implemented incorrectly in several different ways, so you find yourself turning “uniqueness” into a concept of its own and elaborating on it to show how to tell that something really is unique, and then realize that your lesson needs to undergo mitosis because your callout box on testing is now several pages long.

Automatic numbering of cross-references quickly becomes an author’s best friend, but every time a chapter fissions you have to proof-read both parts and everything that depends on them to make sure that (for example) terms are still defined where they are first used. I spend a lot more time on manual checking when I’m writing lessons than when I’m writing reference material, but that’s because a good lesson tells a story with a narrative arc. It all leaves me wondering how the hell people write movies like Memento or Knives Out without going mad…

Terms defined: absolute error, actual result (of test), assertion, caching, defensive programming, design pattern, dynamic loading, error (in a test), exception handler, expected result (of test), exploratory programming, fail (a test), fixture, global variable, introspection, lifecycle, pass (a test), relative error, side effect, Singleton pattern, test runner, test subject, throw (exception), unit test.

Unit testing lifecycle
Figure 4.3: Lifecycle of dynamically-discovered unit tests.