Unit tests: what we're actually arguing about
Four voices argue about unit tests. The debate dissolves once you separate coverage-as-target from coverage-as-detector, with testability as the real prize.
Four voices argue about unit tests. The debate dissolves once you separate coverage-as-target from coverage-as-detector, with testability as the real prize.
Years ago, on my first day at a new company, I sat down for what I thought was going to be a normal pair-programming session. There was a story on the board — I no longer remember what it was, exactly. Some feature, a handful of requirements, the usual shape of a first ticket at a new job. I read the requirements, my mind raced ahead to how I’d build it, and I started talking through my approach. ...
If you’ve never looked at Ginkgo and Gomega, there’s a decent chance you’re picturing something that replaces go test — a separate test runner, a separate CI step, another tool your team has to adopt wholesale before anyone sees a benefit. That picture is wrong, and it’s worth correcting up front, because it’s the single biggest reason Go developers who’d genuinely enjoy Ginkgo never try it. Here’s the fact that should change your mind: a Ginkgo spec is a Go test. It compiles into the same test binary, it runs when you type go test ./..., and your existing CI pipeline doesn’t need to know Ginkgo exists. What Ginkgo adds is a richer vocabulary for organizing specs — nested Describe/Context/It blocks instead of a flat list of Test* functions — plus first-class suite lifecycle (BeforeSuite/AfterSuite), table-driven specs, async timeouts, and labels for slicing a suite into subsets. Gomega adds the assertion language (Expect(x).To(Equal(y))) that makes failures read like a sentence instead of a %v != %v diff. ...
Most of the debate around test-first development gets stuck on process — red-green-refactor, coverage percentages, whether it slows you down on day one. That debate misses a simpler, more concrete effect, and you don’t need to know Go, or even know how to code, to follow it: Whichever thing you write second has to bend to fit whatever you wrote first. Write the code first and the test second, and the test has no choice but to accommodate whatever the code already does — however it talks to the database, however it calls the network, however it happens to be built. Write the test first, and it’s the code’s turn to bend: it has to be built in whatever shape lets the test — which doesn’t have a real database or a real network sitting around — actually run. That’s the entire mechanism. Nothing about willpower, experience, or caring more. Just: which one existed first gets to set the terms. ...
One Go binary that manages its own Neovim, runs Lua tests inside the real editor, and emits LCOV/Cobertura/JUnit — no system install, no vendored framework, no shell-script gymnastics.