Tests That Earn Their Keep
You already know you should write tests. The skill nobody teaches is writing ones that earn their keep, instead of a wall of green that protects nothing. On testing behaviour not implementation, why brittle tests are worse than none, and why "hard to test" is the design talking.
A test suite can sit at ninety percent coverage and catch nothing that matters. If you've worked in a big enough codebase, you've met one: hundreds of green checks, and bugs still sail straight through to production. Coverage went up and confidence didn't, and everyone quietly stopped trusting the suite.
That's the gap this is about. Not whether to write tests, you already know you should. How to write ones that actually earn the time you put into them, instead of a wall of green that protects nothing.
Test behaviour, not implementation
This is the one that fixes most bad suites. A good test checks what a piece of code does, not how it does it. It calls the thing the way a real caller would, hands it inputs, and asserts on the outcome the rest of the system actually depends on.
The failure mode is tests that reach inside and assert on internals, the private methods, the exact call order, the shape of some intermediate value. Those tests pass only as long as nobody refactors, and the whole point of having tests is to make refactoring safe. So you've built the opposite of what you wanted: a suite that breaks every time you improve the code, and stays green when the behaviour actually breaks. Test the promise the code makes to the outside world. Leave it free to keep that promise however it likes.
A brittle test is worse than no test
A test that cries wolf on every unrelated change is not a safety net, it's a nuisance that trains you to ignore your safety net. And a team that has learned to reflexively re-run and shrug at red is in a worse spot than one with no tests at all, because now the failures are noise and the real one hides in the noise.
So brittleness isn't a minor annoyance to tolerate. It's a defect in the test. If a check breaks constantly for reasons that aren't real regressions, that check is costing you the thing tests exist to buy, which is a red light you believe.
Test the edges and the bugs, skip the framework
Where does the time actually pay off? The boring middle rarely needs much. The value is at the edges: the empty input, the huge input, the thing that's null when nobody expected null, the off-by-one at the boundary. That's where real bugs live, so that's where tests earn their keep.
Two quick rules that save a lot of wasted effort. Don't test the framework or the language, they're already tested, and a test that only confirms the ORM can save a row is theatre. And every time you fix a bug, write the test that would have caught it first. That one habit builds a suite shaped like your actual failure history instead of your imagination, and it's the highest-value test you'll ever write because you have proof it catches something.
Hard to test is a design smell
Here's the part that connects testing to everything else. When a piece is genuinely painful to test, the test is usually not the problem. The design is. Code that can't be exercised without standing up the whole world is code that's too coupled, and the difficulty you're feeling is the coupling talking.
So a stubborn testing problem is worth listening to rather than muscling through. Reach for a test, feel it fight back, and more often than not you've just found a seam that isn't there yet. Add the seam and the test gets easy, and the code gets better in the same move. Tests aren't only a safety net. They're the most honest design feedback you get, delivered before the bad structure has had years to set.
Good tests aren't about the number on the coverage report. They're about whether, a year from now, a red light means something real and a green one lets you ship without holding your breath. Write the ones that give you that, and cheerfully skip the ones that don't.