I feel like I try to go around and get developers to do little things that will make a big difference in their work.
For example, if you need to add some behavior to your system, rather than putting that behavior into some pre-existing entity, you should probably create a new class for that new behavior. I like to keep my classes and methods highly focused on one thing so they tend to be rather small, encapsulating no more than a handful of lines of code. I do this primarily for readability so a person can read a method that delegates to other methods and understand the overall flow of what’s going on.
Because some of the methods I write simply delegate to other methods, and most of those methods are private methods, I’m often asked how I test them and the answer is that typically I don’t have to test them directly. If I can exercise the behavior that they evoke by calling test on the public interface of the API then I’ve tested the behavior of those private methods.
I can learn a lot about the cohesion of a system by hearing what people say about how difficult it is to test that system. For example, if someone says to me, “There are too many permutations to test this class,” then I know they probably have cohesion issues.
If a class has only one issue then it requires only one test, but a class with two issues requires two tests, and a class with three issues requires four tests. A class with four issues requires eight tests, a class with five issues requires sixteen tests, and so on. It grows exponentially.
But five classes, each with only one issue, requires only five tests (plus integration). So what would you rather do? Create five classes and five tests or create one class and sixteen tests. I think most of us when we think about it, and if we value testability, will pick the first option.
Typically the first place I look in order to make software more testable is extracting out behavior and entities so classes have well-defined behavior. That lets me give them better names so they’re easier to understand and work with in the future.
One of the most important things in making code testable is making it more cohesive. Smaller behaviors are easier to test than larger behaviors. They also give more meaningful feedback, which makes the system more reliable. When classes are more cohesive, their tests become far simpler to write and test scenarios become more straightforward.
Classes can represent anything. They can represent concepts like an insurance policy or they can represent tangible things like the terrain of a map. If we can name something, we can represent it with a class, and classes are often made up of other classes. Classes can also represent relationships. Really anything we can imagine can be represented by classes. It’s a powerful tool.
But we often build classes that are way too big and have other classes lurking within them that should have been broken out into smaller classes. Smaller classes are easier to test and they are also easier to understand. This drops the cost of ownership for software.
My rule of thumb is to keep methods short and method names expressive. I write tests for the behaviors that I build, but when I’m refactoring code and extracting methods I don’t typically write tests for those methods if the behavior hasn’t changed and as long as the test for the behavior that calls that private method can exercise its behavior. So the private method just tested indirectly, which is typically fine. Remember, the only reason the private method exists in the first place was to call out what the behavior was doing. This is a form of documentation, really. I would much prefer to extract a bit of a behavior into its own method and give it a meaningful name then write a comment on what the code is doing. That’s so 1980s!
Valuing testable code has made me pay great attention to cohesion, which in turn has helped me manage complexity in my code and make my code easier to work with.