I am a lazy, lazy man.
Why, then, do I feel so strongly that a team should spend so much time and effort writing and maintaining a unit test suite that will never be part of the application?

I hate puzzling through crappy code.  I hate debugging why a seemingly insignificant change causes the application to fall over.  I hate small functional changes that take weeks to deliver.  When I get off my ass to do something, I want to focus on it, get it done, and go get a beer.  And I don’t even want to think about interrupting beer time to go back and fix something that I thought was done.

So…how can unit tests help me be lazy?  <sips beer>  My prerequisites for being lazy are:

  1. If I break something, I know right away that I broke it
  2. If I break something, I know something about why it is broken
  3. The production code is easy to understand
  4. The production code is easy to change

Whoa, whoa now!  It’s fairly obvious how points 1 & 2 might be achieved via a strong unit test suite.  Good unit tests act as living documentation; a specification of the assumptions and expectations of your production code.  If I make a change that violates the spec, a test will fail and I’ll know what the problem is.  But the unit tests are not production code; how will my unit test suite make my production code easier to work with?

Points 3 & 4 are starting to bump against the idea of well-factored and/or clean code.  (Clean code is one of those things that, like pornography, is hard to define but you know it when you see it.  And everyone has a different opinion about what makes it good.  But I think I mentioned I’m lazy; if you’re interested in a more detailed discussion, I suggest you consult the internets.)

Most teams I’ve worked on have some working agreements related to refactoring; we’d often intone a mantra such as ‘make it work, make it right, make it fast’ while coding.  But it can be hard for a team to quantify when the code is ‘right.’  Code reviews (good) or pair programming (better) helps to achieve rightness; but even mature, rational developers can disagree about when code is clean enough. Code review often isn’t enough to maintain a clean code base.

So, how do unit tests make code clean?  Well…they don’t, necessarily.  It’s more accurate to say that strong working agreements around unit testing will drive the team to exhibit code-cleaning behavior.

  • Test-Driven Development (TDD): if you want to make a change in code, you have to write a failing test specifying what the behavior should be.
  • 100% coverage: If you change a line of code, at least one unit test will fail.
  • ‘Grok’ test: coming in cold, an experienced developer can understand what the code does in a few seconds.

If you’re reading this, you’re probably someone who works with code.  Take a minute and go find some code you maintain that you don’t think is ‘clean.’  Now, allow me to make some predictions:

  • it has low or no unit test coverage
  • what tests it has tend to have more lines of code doing ‘setup’ and tweaking input parameters than actual testing
  • it takes a bit of time to understand what each test is actually testing
  • it isn’t easy to predict which tests will fail (if any) if you tweak a line of code
  • it isn’t easy to assert, with confidence, that all the assumptions and expectations of the code under test are covered by unit tests
Now, imagine your team adopts the working agreements I mentioned; how would you meet them?  You could start writing tests.  Lots and lots of tests hitting the same functions, each with slightly tweaked inputs.  You could start writing test frameworks to make it easier to set up the tests. Eventually, you’ll have test coverage.  You could start adding comments, and writing supplementary documents with colorful pictures to help other developers understand the code.  Of course, you’d probably still have to be on hand to answer questions.  And when you need to make a change, you dust off that test framework and figure out how to write a test for the behavior you want to change.  I hope that framework code is clean, because you’re not going to want to spend a lot of time figuring out how to use it.

Or, you could start cleaning that crappy code up.  Separate some concerns, extract some classes and methods (testing them along the way, of course), rename some things to better describe what they do…in other words, refactoring. Not only will the code be easier to understand and easier to test, I bet you’ll actually have to write fewer tests to get coverage. Testing poorly factored code is actually more work than testing and refactoring.

I’ve never seen poorly factored code that had a strong unit test suite.  Unit tests won’t guarantee clean code, but working agreements around maintaining a strong unit test suite will drive your team to want to refactor more.  Because all developers are lazy.