Testable code

Previously I mentioned that one of the most important benefits I get from using TDD is that it drives me to write more testable code. I've been thinking quite a lot about this, and another recent post from Chris Dickens made we even more aware of the importance behind writing testable code.

Chris states:

 "As you might expect, before we can start serious development we have to write specs. Those aren't my job (that's what Program Managers do) but developers and testers are actively involved in critiquing the specs. Technically my purpose in those meetings is to flag any design issues that would make the product untestable, but in reality I'm there to provide my input on what the product should do."

I've found (the hard way) that trying to test code that wasn't written with testing in mind, is extremely hard, especially when you find tightly-coupled code.

TDD promotes decoupling and this, by itself, is very helpful in producing testable code, because there's some way to plug into the production logic you want to test. If the code you're testing depends on other parts of the system you can find yourself writing a huge amount of code into the setup methods of the unit tests. This, unfortunately, introduces a high degree of coupling into the testing logic, particularly if the code you're dependant on belongs to another domain (i.e. it's an external system)

Using mock objects can help us develop loosely-coupled unit tests. Considering this, some questions seem to arise almost instantly... "When should mock objects be used?", "Is it recommended to use mock objects to represent every external system that our production code interacts with?", etc.

Here's a definition of a Mock Object:

  1. is easily created
  2. is easily set up
  3. is quick
  4. is deterministic
  5. has easily caused behaviour
  6. has no direct user interface
  7. is directly queriable

From the above definition, it's possible to identify a couple of situations where using mock objects can be valuable in coding unit tests:

  • the real object doesn't yet exist
  • the real object is difficult to setup
  • the real object is slow (if it takes too long to run the tests, you might feel discouraged to run them)
  • the real object produces unpredictable results which may cause the test to behave in a nondeterministic way;
  • the real object is a user interface
  • the test needs to ask the real object how it was used (for instance, the test might need to verify if an event was raised and caught correctly)

Developing unit tests with mock objects, improves domain code by preserving encapsulation, reducing global dependencies, and clarifying the interactions between classes [Mackinnon, 2000].