No one is perfect – you make mistakes eventually. Unit testing is designed to catch and pinpoint the location of a mistake, in order to:
- increase the confidence about the correctness of the code you write
- increase the confidence about the correctness of refactors
- make tracking down where a bug was introduced in the testing phase much simpler
Error handling and logging only helps when the bug is triggered; unit testing is what makes bugs be triggered in testing rather than production.
Consider the following…
You have a piece of software with 3 different parts, each of which has 2 different options.
A C E
/ \ / \ / \
in-< >--< >--< >-out
\ / \ / \ /
B D F
You could test this by manually putting in inputs and checking outputs – first you’d put in some inputs that triggered A,C,E; then you’d put in some that did A,C,F, and so on, until you covered everything through B,D,F.
But keep in mind that B, D, and F each have their own individual parameters and flows that need to be tested – we’ll say there’s maybe 10 variations for each. So that’s at least 10*10*10 = 1000
different inputs you need to check, just for the A,C,E case. There’s 8 different possible flows through these 6 components, so that’s 8000 different combinations of inputs you need to check just to make sure you hit all of the different possible inputs.
On the other hand, you could unit test. If you clearly define the unit boundaries for each component, then you can write 10 unit tests for A, 10 for B, and so on, testing those boundaries. That gives you a total of 60 unit tests for the components, plus a handful (say 5 per flow, so 40) integration tests that make sure all of the components are tied together properly. That’s a total of 100 tests which accomplish effectively the same coverage of functionality.
By using unit testing, you’ve reduced the amount of testing required to get an equivalent amount of coverage by a factor of about 80x! And that’s for a relatively trivial system. Now consider more complex software where the number of components is almost certainly greater than 6, and the number of possible cases those components handle is almost certainly greater than 10. The savings you get from unit testing rather than just integration testing keep building.