by Thomas Papendieck ; last updated - 6 minutes read

I always wondered how to write truly efficient and useful unit tests. So I'm glad my co-worker Thomas Papendieck offered to write a guest article, sharing his expertise with you. Thomas, it's your stage!

Unit tests are cool!

Unit tests secure already existing behavior in the code base and support the programmer while doing changes by detecting damages.

Almost every programmer knows this statement.

...but are they really?

On the other hand, almost every programmer made the opposite experience: Unit tests come in the way of the programmer when she wants to introduce changes to the production code. After the change, a whole bunch of unit tests broke and it takes a big effort to make them pass or even compile again. I remember projects where fixing tests took up to ten times longer that the actual change.

Why does that happen? Is it that unit tests are only hyped by ivory-tower screwballs propagating academic ideals? Those lucky dreamers never being responsible for a real life application?

The staggering answer is: No.

The problem is not caused by the unit tests but by the programmers who created them. Unit tests must have certain qualities to be useful. And the only person able to put these qualities into unit tests is the programmer writing them.

How to do it right

But what are these qualities that morph unit tests from an obstruction into a base? Best point to start may be an advice already known to every computer user: RTFM. But let's give that acronym a new meaning:

Readable

Readability means that the names of methods, identifiers, and references in test code clearly convey the intention of the programmer: What behavior is unter test? What are the tests preconditions? What are the important qualities of parameters and result? Shortness also improves readability of a test method as well as the absence of sophisticated logic. Each test method should be clearly structured in 3 sections (prepare, act, assert).

Trustworthy

Are you sure your test really executes the production code at the point of interest? The best way to guarantee this is to write the test before the production code so that you see the test turning green while you're changing the production code.

The lesser logic we have in the test code the lesser is the risk that the test itself is bust. Therefore stupid test methods (in the sense of not having sophisticated logic) increase thrust.

But most important: A certain test should only fail for a single reason. It should not fail because of a changed (not necessarily wrong) behavior of one of its dependencies (which is any part of the production code that communicates with the unit under test but does not really contribute to the behavior tested).

Fast

The most important benefit of unit tests is that they inform the programmer when already finished behavior of the production code changed unintentionally. But this can only happen when the tests are executed actually. Unit tests that run once a month during QA examination are effectively worthless. At best all of our unit tests (or at least all tests affected by the current change) run after we saved the changed file. But this will not happen if we have to wait more than 3 seconds after pressing CTRL+S. And this is the reason why unit tests must be fast. And I mean really fast. But relax: Unit tests that are both, trustworthy and maintainable, usually are fast (enough) too.

Maintainable

Maintainability depends to a large extend on "Readability" and "Trustworthiness". Beside that this point focusses on minimizing the influence of changes of the interface of the unit under test or its dependencies. Keywords here are "centralized instantiating" and "mocking frameworks".

Also important is to limit the focus of a certain test. Each test (that is: a method within the test code) asserts a single assumption about the unit under test. The more specific this assumption is the lesser is the influence of changes on that unit or its dependencies. It is surprisingly hard to express the unit test's assumption in one sentence only. But if you can't then the focus of the test is too big. And the unit under test might be too big as well...

And what's the problem?

When unit tests turn out to complicate changes in the production code usually trustworthiness and maintainability of the unit tests are the main problems. In most cases the main reason is insufficient isolation of the unit under test, that is, the specific portion of the production code that provides the tested behavior. This lack of isolation has multiple reasons. Usually these reasons are:

  • no mocking framework nor own fake implementations for dependencies of the tested unit
  • mighty units in production code having to many responsibilities
  • inseparable dependencies in production code, e.g.:
    • no dependency injection
    • no decoupling via interfaces
    • usage of new keyword in constructor or inline initialization
    • (Java-) singletons
    • final declared classes and methods
    • transitive access (aka "Train Wrecking")

and of cause any combination of that...

And why is that hard?

As you might have noticed: most of these problems do not exist in the test code but in the roduction code. There is a close relationship between unit tests and the production code they test. This finding is quite important because the consequence is: If we want to write better unit tests we have to write better testable production code in the first place. Unfortunately, this wisdom is not that popular as it should be.

The good news is: If we follow the rules explained by Robert "Uncle Bob" Martin in his book "Clean Code", then our production code becomes testable magically.

But let's make that as clear as possible:
It's hard to write good testable code and unit tests that support the programmer to identify failing production code and don't restrain changes. It requires experience which can be gained by practicing only.
My suggestion is to choose an experienced partner and do pair programming with her. A great opportunity to do so is the annual Global Day of Coderetreat.

So don't despair and keep creating unit tests! Happy coding - the safe way!

About the author

Thomas Papendieck is Senior Developer at OPITZ CONSULTING Germany. After being Soldier for 12 years, the father of 3 studied information science at the University of Applied Science at Fulda. Since then he gained 10+ years experience in Java development. His mission is to promote programmers self-conception as craftsmen.


Comments