No Seams
Second in the legacy-code series. I wanted to write one test and couldn't, because testing one piece meant standing up the whole app. On why "add tests later" is a lie until you carve seams, and why splitting a monolith isn't tidiness.
I wanted to write one test. One. A single honest check around one piece of behaviour, so I could change it without holding my breath. I couldn't, and the reason why turned out to be the whole diagnosis.
To test that one piece, I'd have had to stand up nearly the entire application. The database, the services around it, half the system's state, all of it, just to exercise one small behaviour in the middle. The test wasn't hard to write. It was impossible to isolate, and a test you can't isolate isn't a test, it's a second copy of the app that also doesn't work.
Coupling is the actual disease
It's tempting to call this an untested codebase and move on, but "untested" is the symptom. The disease is coupling. Everything reached directly into everything else, no boundaries, no interfaces, no place where one part ended and the next began. The monolith wasn't badly tested. It was structurally untestable, and those are very different problems.
This is the thing that makes "we'll add tests later" a comforting lie. You can't bolt tests onto a tangle. There is nowhere to attach them. A test needs a seam, an edge it can grab where it can hold one piece still and poke it in isolation. In a fully coupled system there are no seams, so there is nothing for a test to hold, so "later" never arrives, because the precondition for testing was never built.
Splitting isn't tidiness
So the first real work wasn't writing tests at all. It was carving the monolith into separate projects with boundaries between them, and it's easy to mistake that for cosmetic cleanup, moving files into nicer folders. It isn't. Every boundary you introduce is a seam, and every seam is a place a test can finally get a grip.
You pull a piece out behind an interface, and suddenly you can stand that piece up on its own, feed it what you choose, and check what it does, without dragging the database and the universe along with it. The split is what makes the piece isolatable, and isolatable is what makes it testable. You are not organizing code for the sake of neatness. You are installing the handholds that tests require, in a building that was poured as one solid block.
The order is the point
Which is why this post sits where it does in the sequence. You make the data honest first, then you carve seams, and only then can tests exist, and only with tests as a net can you safely do the scariest refactors. Try to reverse it and you stall. Reach for tests before there are seams and you find nothing to attach them to. Reach for the big refactor before there are tests and you're working without a net over exactly the code most likely to drop you.
Seams first. Not because boundaries are pretty, but because boundaries are the precondition for every safe thing you want to do next. The whole series is one move repeated: take what the system fused together silently and separate it into parts that can be seen, named, and checked. Post one did it to a data shape. This one does it to the code itself.