“Why do we need unit tests?”
Seems like a silly question. We want to check if something is working, right?
Let’s dig deeper, though.
At the time of writing, if we’re using test-first or TDD, we’re designing interfaces, apply usage scenarios and refactor with a safety net. In test-after mode, we’re confirming that what we think the software should do, it actually does.
But unit tests don’t help us only at the time of writing of course. Once written and run as part of an automated suite, the unit tests change their initial role, and serve two goals:
- Alert us if something is wrong.
- Help us solve the problem as quickly as possibly.
The first one is obvious. The second one is subtle and easily missed.
Imagine it’s 6 months from now, and the build just broke on the unit tests you’ve just finished writing. Also, imagine you’re on vacation, and somebody else is responsible. Maybe you’re off the hook (for now).
But now someone else needs to solve a puzzle:
Unit Tests Puzzle: What Just Happened Here?
I think we can agree that our brave developer (not the one on vacation) wants to spend as little time as possible on this. After all, she was working on something completely different for a couple of days, committed her code and suddenly: Bam!
Well, guess what: your unit tests can help her. Let’s go over our inventory of evidence:
- The code changes. For smaller commits, we’ll know pretty quickly, but bigger commits are bigger haystacks.
- The unit test names. Descriptive unit test names should tell us the exact scenarios that have failed, and what was important about those scenario, including the main reason for the unit test that is failing. Integration tests have more generic names and are not much help here.
- The sibling tests. Maybe we have a couple or more of unit tests failing, but what about the tests that run through close scenarios? Their results should also direct us to where the problem lies (and where it doesn’t).
- The unit test code. The final part of the puzzle (if we have to use it) is the unit test code, but not all of it – what differentiates it from the code of its siblings. All common code is clutter.
This process is called triangulation. In our heads, we try to gather all the pieces of evidence. We want to locate where the problem code is hiding, and understand the effect that caused those unit tests to fail (and leave others passing).
The more details we leave ourselves (and others), the better the breadcrumb trail, it’s going to be easier to solve the mystery. Without it, it’s off to the debugger, Sherlock!
And we all know how Sherlock feels about debuggers.