In software Engineering we write code and we write tests. I think there’s a bi-directional relationship between unit tests and good code.
I think writing good code, where good roughly means clean, makes writing unit tests easy.
I think writing unit tests makes writing good code easy.
I think that this leads to a self-reinforcing dynamic where bad, untested code tends to get worse and more untested over time while good code tends to get better and more tested.
Why does this relationship exist? Let’s look at it from a "Why is good code easy to test" perspective
- Small functions that do one thing are easy to write tests for. It’s clear what the paths are and hence what needs to be tested.
- Pure functions or functions where impure things (e.g: api clients) are passed in (aka dependency injection) are easy to write tests for. No mocking and minimal implementation knowledge is required.
- Code with clear typing is easy to write tests for. You know what the range of possible inputs you needs to test for is (e.g: is
someVarnullable?). You know what the outputs are or should be.
The converse of all of these is true as well.
Why do tests encourage good code? I think the reasons boil down to writing tests making bad code painful. You feel the pain when you have large methods which do multiple things. You feel the pain when typing is unclear and you have no idea what inputs to test for. Most of all, you feel the pain when the way impurity is handled is bad because you have to reach deep inside your code and mock, making your tests fragile and closely tied to implementation.
That’s one thing. Another thing is that having good tests makes refactoring safe and painless. You feel free to mess around with breaking up the logic in different ways. Bad tests are either insufficient, making you worry about breaking things by accident without knowing, or tied to implementation, making any refactor a pain as you know you’ll have to tinker with tests.
As always, I think craftsmanship in software requires not cutting corners. I also think that bad code is essentially a collective action problem, something I’ll write more about later.