Most developers write tests to prove their code works. That's useful, but it's not the most interesting thing tests can do for you.
I used to write tests after the fact. Build the thing, make it work, slap some tests on it so the CI pipeline doesn't complain. The tests passed. The code shipped. I felt fine about it.
Then I started paying attention to what happened when writing the tests was genuinely painful. Not just tedious, but uncomfortable. The kind of friction that makes you question your choices. That discomfort turned out to be useful signal.
The Test Is Trying to Tell You Something
When a test is hard to write, there's almost always a reason. The function does too much. The dependencies are tangled up inside instead of passed in. The output depends on global state you didn't realise was there. The interface made sense when you were building it, but from the outside, as a caller, it's awkward.
Tests force you to be a caller of your own code. That perspective shift is underrated. You stop thinking about implementation and start thinking about use. Those are different questions, and they often have different answers.
A function that's hard to test is usually hard to use. Not always, but it's a reliable enough heuristic to take seriously.
What Changes When You Write Tests First
Test-driven development gets a lot of hype and a fair amount of backlash. Some of the backlash is deserved. Strict TDD can feel like a ritual more than a tool, and plenty of people apply it mechanically without getting much out of it.
But the core idea is sound: if you sketch out how you want to call something before you build it, you end up with a better interface.
It's the same reason writing documentation before code sometimes helps. You're forced to describe what the thing does without knowing exactly how it does it. That constraint is productive.
I don't always write tests first. Sometimes I need to explore the problem before I know what the right interface looks like. Exploratory code is fine, just don't mistake it for production code. Once I understand the shape of the problem, I'll often throw the first draft away and start again with the tests leading.
The Specific Things Tests Reveal
Over time I've noticed a few patterns in what painful tests are trying to tell me.
If the setup for a single test takes twenty lines, the function has too many dependencies. The test is reflecting the real cost of using that code. It's just making it visible all at once instead of hiding it across the codebase.
If I have to mock half the world to test one thing, my abstractions are leaking. The code knows too much about its environment. Passing dependencies in rather than reaching for them makes both the code and the tests simpler.
If I can't describe what a test is checking in one sentence, the function probably does more than one thing. Split it. The tests become clearer, and so does the code.
Tests as Documentation
Good tests are also one of the better forms of documentation, because they don't lie. Comments go stale. READMEs drift. Tests, if they're in the CI pipeline, have to stay true or the build breaks.
A test that shows how to construct the inputs, call the function, and check the output is often more useful than a paragraph of prose explaining what the function does. You can run it. You can modify it. You can use it as a starting point.
This only works if the tests are readable. A test full of magic numbers and opaque setup doesn't document anything. Write tests the way you'd write an example in documentation: minimal, clear, focused on one behaviour.
The Habit Worth Building
I'm not arguing for strict TDD or 100% coverage or any particular methodology. I'm arguing for paying attention when writing tests is hard.
That friction is feedback. It's your code telling you something about itself. Learning to read that signal, instead of pushing through it or ignoring it, is one of the more practical skills you can build as a developer.
The tests that are easiest to write are usually the ones for code that's easiest to maintain. That's not a coincidence.



