The biggest problem with unit tests are their name. Engineers will argue for ages about what a “unit of work” really is. Articles about how to do unit testing well all start with defining a unit of work, and it’s a shame — because it doesn’t fucking matter.
Unit of Work doesn’t help engineers produce more value. It does not make their lives easier. And after some twenty years writing unit tests, it’s often counter-productive guidance. It doesn’t help you achieve your goal.
But what is the goal here? It is not making sure that your code is correct. That is not what tests do. All testing is about risk mitigation. The goal of software testing in particular has two parts:
- Reduce the risk that you made a mistake writing the code.
- Reduce the risk when people change the code.
That’s it. There are of course many ways to achieve these goals. Unit testing is just one particular technique. Sometimes other techniques will be more useful. Keep an open mind.
Cool, on to the meat of it all. What differentiates unit testing from other techniques is isolation.
The theoretical ideal is that there is a 1:1 ratio between unit tests and potential mistakes in the software. One mistake can cause one and only one test to fail, and that test can only fail from one and only one mistake. Then the tests would be super useful! The tests would tell you what is broken without you having to do any investigation.
Like most theoretical ideals, it’s impossible for non-trivial use. For example, it’s kind of hard to test a
count function on a list when
add is broken. Yet perfect isolation isn’t necessary. Yes, knowing exactly what line the mistake is on is best, but narrowing it down to a single function or single class out of a million is still very useful.
How to do that?
Isolating the code for one test means breaking any coupling between that code and code you’re not testing. For something like a pure function which uses no state and has no side effects, it’s easy since there is no coupling. Even those classes which only work with internal instance state are easy to unit test since the code is already isolated.