This post presents the value that unit testing adds, presents that state of practice in the project that I am working on, and lays the foundation for a future post on a new way of thinking about and writing unit tests.
At Thougtworks, it is customary to follow Test Driven Development (TDD). It is believed that TDD increases the probability of creating correct, readable and maintainable code, which are valued in that order.
Minimal upfront design
XP, the process that we follow argues for design decisions to be delayed as late as it is sensible. Minimal upfront design leaves the pair working on the task to come up with as much design as they deem necessary. This involves deciding the structure and behavior of all the units necessary to implement the feature, plus refactoring other units whose design does not fit into the current context.
The need to come up with design as well as the pressure to complete the task at hand leads lot of conversations and some frustrations when beginning to play a task. Unit Testing is suggested as a solution to avoid vague and meta physical questions like “should this belong here” that tend to crop up in “design discussions”. So the primary value that is derived from unit testing, is ironically, not correctness. Unit-testing provides, a formal way of thinking about the unit and its responsibilities, which increases the probability that the unit is correct.
A Heuristic for design quality
What unit testing does, that helps a pair out in the discussions about design, is that it provides a ready heuristic to reason about the design.
The rule of thumb is that, if a pair finds it difficult to write unit tests for a class, then there is something wrong with the class.
The rationale, behind the rule goes like this. A unit test represents an “understanding” of the way a class behaves in the given context. This implies that if it is difficult to write a unit test, it is difficult to understand the unit and there by, there is a greater chance that the unit is poorly designed.
Excellent said I, Elementary said He
Difficulty to write a test, is not exactly a very good heuristic, for what is easy for one pair is not necessarily difficult for another. There is another more serious problem with difficulty as a heuristic.
Ignorance is bliss
The heuristic does not capture coverage provided by the test. Now I have to make it clear at this point, that I dont consider code coverage as an indicator for well, anything. The coverage that I’m talking about here is state coverage. Can you assure me that a class is correct for all possible states that comes under its perview. This seems to be a very big ask, but I believe that it is possible using proper abstractions, but that is a topic for another post.
The above shortcoming implies that a pair can decided not to test the unit rigorously, but still pass off the unit as being easily testable. Difficulty as a heuristic, is so subjective that using it as a heuristic is difficult to say the least.
Mind your language
Since, unit test is a language to reason out the unit under test, it is best kept, as simple as possible. The major complexities in unit test, arise around data set up and assertions. The use of good frameworks like factory-girl and hamcrest enables the creation of data and assertions at abstract levels that is necessary for keeping the language of the test simple.
The use of framework not withstanding, rigorousness and complexity involved in creating a rigorous test are major indicators of the complexity of the unit. As the complexity of the test increases, the complexity of unit increases, and hence the unit is poorly designed. Since complexity in itself is not a good heuristic, a better one is needed to make good decisions on the design of the unit.
These are some of the parameters that I have tried to reconcile for sometime, that resulted in the a new format of writing unit tests. More about it, in the subsequent posts.
Quote for the day
“Faith is mortal. Reason is Divine”. – Unknown source