Reducing Coupling Through Unit Tests
After my previous post on the subject of coupling and cohesion, a lot of the feedback I've gotten has been from people who want examples of lowering coupling, or want to know how they can see if their code is loosely coupled. (Reposted from my blog.)
The easiest way I know of doing that also has other benefits:
Whether you prefer Behavior Driven Development or test driven development, or you call it something completely different, writing unit tests provide you with one or two essential things:
- If you write the tests up front, you will instinctively be guided to writing loosely coupled code
- If you add tests "after the fact", you will clearly see whether your code is loosely coupled.
How unit testing help reduce and/or measure couplingThe Wikipedia article on coupling provides this definition:
Low coupling refers to a relationship in which one module interacts with another module through a stable interface and does not need to be concerned with the other module's internal implementation.
The thing is, when you write unit tests, you are putting the unit being tested into a harness. You need it to interact with your test code, and still carry out its functions.
If you write the tests upfront, you have two alternatives: Either you create a hugely complex harness with lots of mock objects, or you write your code in a way that makes it easy to call in isolation.
That is another way of formulating low coupling: A module exhibits low coupling if it is easy to call in isolation.
Unit testing under any name is a good test of the ability to call your code in isolation
Testing after the fact gives you a measure of how well you have done: If you have written code with low coupling, it should be easy to unit test. If you have written code with high degrees of coupling, you're likely to be in for a world of pain as you try to shoehorn the code into your test harness.
If you haven't bought into TDD/BDD for other reasons, consider it for this reason. Try it. The structure of your code will change if it wasn't previously loosely coupled, as you'll quickly tire of writing horrendously complicated tests.
Some indicators of the level of coupling
- Can you test the code with trivial unit tests? (I.e. no or few mock objects, no huge amounts of setup or teardown code.) If you can, your code is likely loosely coupled.
- Do you find yourself struggling to test modules independently of eachother? Almost certainly high coupling.
- Does the code have lots of side-effects in low level code? This is a warning sign - it very often lead to code that is harder to test, for example because it might mutate state on lots of unrelated objects. It's not always avoidable, though, and if well managed, it doesn't need to lead to high coupling - the trick is to isolate the calls that cause the side effects from the external environment through the use of the adapter pattern or similar.
- Is the code "controlled from the top"? In other words, does the code pass results indicating actions up the call chain instead of having side effects? Code that consistently does this tend to be looser coupled (and easier to unit test) than code that have side effects, as long as the actions passed up are as generic as possible (in other words, it doesn't help if the action passed back for example is an object that when called will invoke a specific method on an object of a specific class - in that case you're just delaying execution of a side effect rather than decoupling anything).
- Is the code highly cohesive? That is, does each module carry a single, reasonably simple responsibility, and is all the code with the same responsibility combined in a single module? If code implementing a single feature of your application is littered all over the place, or if your methods and classes try to do many different things, you almost invariable end up with a lot of coupling between them, so code with low cohesion is a big red flag alerting you to the likelihood of high coupling as well.