Do unit tests improve code quality? Some famous consultants might disagree, but I think they don’t. Testable code isn’t automatically better code. Depending on the capabilities of your language, it’s probably worse.
Now don’t get me wrong, unit testing is a good thing. But I think we need to realise that we’re often making a trade-off between simplicity and testability. To me, simplicity is the most important factor of code quality, but many people lean towards testability and are very successful with that. Maybe a complex, well tested system is better than a simpler system with less test coverage, I can’t answer that. But you can’t have both, at least not in static languages.
Let me explain what I mean: In a unit test, you’re testing a small part of your system in isolation. You’re ensuring that a single module, class or function, works as expected, now and in the future. You only test the unit as it can be used from the outside, and that’s good, because its implementation details shouldn’t matter to the rest of the system. But it’s also a problem.
It’s a problem because you often have to actually change the unit to make it testable. There are plenty of examples for this, but I’ll stick to one in this post: You’re testing a unit that uses a random number generator. Since the behaviour of the unit will be different every time you use it, you need a way to take control of that random number generator in order to test it reliably.
If your language supports object-oriented programming, the common approach is to introduce an interface for the thing you need to control and inject it from the outside. Let’s say we’re creating a RandomNumberGenerator interface and pass an instance of it to our unit. You can then create a fake implementation that does just what you want, and pass that one to the unit from the tests. Now you can make sure that your unit works fine for various random numbers.
However, we have just added to the system’s complexity. We have created a facade for a random number generator, which is very likely already available in your language’s standard library. Anyone working on your code base will now have to know that your facade has to be used instead of the standard method. We have also introduced an interface that doesn’t make much sense right now: Ignoring the tests, there is only one random number generator – why have an interface if there is only one implementation of it? That’s nothing but unnecessary code other poor souls will have to wrap their head around. Maybe you even introduced a dependency injection framework – this is going to make your code base a lot more complex.