As software engineers, we have all been there. One class that “seems” wrong, but you do not know that exactly is wrong. There is sound reasoning behind the need for Object Oriented Programming. But the reasons (at least in the books that I have read) are so abstract or so lacking in rigor that, it is hard to apply them to day to day programming tasks.
The software community has come up with patterns and code smells to help the developer identify the positives and negatives in code and write it better. Successfully identifying patterns and code smells generally takes a lot of years of experience. But quite recently the idea that test-ability of a class is an indication of the quality of its design got me quite interested.
When trying to use unit test-ability as a heuristic, I found that it was hard to figure out what exactly is wrong with the class from the test. This is because, the tests of a complex class tend to be complicated themselves. So I started experiments on ways in which to organize tests so that the complexity is broken down which makes it easier to identify candidate classes that can be extracted from the complex class.
The following are general patterns that I have found in jUnit tests of complex classes
- Mocking is difficult. Mocking is important because it simplifies the setup of preconditions for the test, this makes it easier to write unit tests.
- There is a huge number of lines of setup for each test method.
- There is a huge number of assertions in each test method.
- Many test methods have largely similar setup
- Many test methods have very similar assertions (though in this case, the assertions are usually omitted)
- Many test methods have setups that are extensions of some other’s setup
The following is a skeleton of tests for an integer class
As it can be seen from the above example, clear groups like tests on values for addition and subtraction emerge. Whats more, though the example for the sake of simplicity omits the set up, it is very likely that the setup for Addition and Subtraction will be an extension of Integer’s setup.
The aim of junita is to expose these natural groupings, keep the tests understandable and thereby aid decomposition. The following is the skeleton of IntegerTest using junita.
As it can be seen, junita exposes this kind of natural groupings, keep the setups to as small a group as possible and there by make it easier to identify classes that can be extracted from the current class. The test now forms an artifact that can point out the ways to decompose a complex class and more importantly, if used by the entire team, will form a common language with which to reason out the ways to decompose a class.
Quote for the day
“Simplicity is a great virtue but it requires hard work to achieve it and education to appreciate it. And to make matters worse: complexity sells better.” – Edsger W. Dijkstra