# I Haven't Written a Test in a Year
## And My Test Suite Is Better for It
*Draft for public journal - January 2026*
---
I haven't written a test in a year.
For a developer who built an entire philosophy around "Bugs as Missing Test Cases," this sounds like professional negligence. Like I've abandoned the craft.
The tests exist. I just didn't write them.
---
## What Actually Happened
AI writes the tests now. I review them.
This isn't vibe-testing. I don't blindly accept whatever the AI generates because it looks plausible and the green checkmarks feel reassuring. This is a systematic workflow:
1. I implement a feature (or AI does, and I verify)
2. I describe what the tests should cover
3. AI generates the test file
4. I review the generated tests
5. I run them, verify they test what matters, request changes if needed
6. Tests get committed
Most days this works. But step 4 is where it breaks down more than I'd like to admit. Sometimes I'm three reviews deep and realize the AI keeps generating tests that pass but don't actually prove anything. They're testing the happy path six different ways while ignoring the one boundary condition that caused last month's production incident. That's when I have to dictate the test structure myself.
The 20-30 minutes I used to spend writing a test file? Now I spend 10 minutes reviewing one. The output is the same. Often better, because AI doesn't get bored writing the tedious edge cases.
But the skill has fundamentally changed. I used to write good tests. Now I catch bad tests.
Same outcome. Different process. Different expertise.
---
## What I Review For
When AI generates a test file, I'm checking judgment, not syntax.
**Does it test the right thing?**
AI tests what it sees. I know what the code is *supposed* to do, which isn't always what it does. The test needs to verify intent, not just current behavior.
**Does it catch the edge cases I know about?**
I've lived in this codebase for years. I know where the dragons are. If the AI-generated tests don't poke at the known failure modes, they're not good enough.
**Is it testing behavior or implementation?**
Bad tests break when you refactor. Good tests break when you break functionality. AI often couples too tightly to implementation details. I push back toward behavior.
**Will it break for the wrong reasons?**
Date-dependent tests. Order-dependent assertions. Hardcoded values that become false in six months. AI doesn't feel the pain of maintaining tests over time. I do.
This review process takes domain knowledge. It takes experience with what breaks. It takes judgment that AI doesn't have.
### A Review in Practice
I asked Claude to generate tests for a service that processes subscription renewals. The first version came back with something like this:
```ruby
test "processes renewal successfully" do
subscription = create(:subscription, status: "active",
expires_at: Time.current + 1.day)
result = RenewalService.call(subscription)
assert result.success?
assert_equal "active", subscription.reload.status
end
test "handles expired subscription" do
subscription = create(:subscription, status: "active",
expires_at: Time.current - 1.day)
result = RenewalService.call(subscription)
assert result.success?
assert_equal "renewed", subscription.reload.status
end
```
Both tests pass. Both miss the point.
The first test renews a subscription that isn't due yet. Why would you renew something that doesn't expire for another day? It should either be a no-op or an error. The second test asserts `success?` for an expired subscription, but the actual business rule is that subscriptions expired for more than 30 days should *fail* renewal and require manual intervention.
The AI tested the code paths. I know the business rules. After review, the tests looked like this:
```ruby
test "renews subscription expiring within 7 days" do
subscription = create(:subscription, status: "active",
expires_at: 3.days.from_now)
result = RenewalService.call(subscription)
assert result.success?
assert_equal "active", subscription.reload.status
assert subscription.expires_at > 3.days.from_now
end
test "skips subscription not yet due for renewal" do
subscription = create(:subscription, status: "active",
expires_at: 30.days.from_now)
result = RenewalService.call(subscription)
assert result.skipped?
end
test "fails renewal for subscription expired over 30 days" do
subscription = create(:subscription, status: "active",
expires_at: 45.days.ago)
result = RenewalService.call(subscription)
refute result.success?
assert_includes result.errors, "requires manual review"
end
```
Same service. Completely different tests. The difference isn't syntax. It's knowing that "expired" means different things depending on *how long ago* it expired.
This is what test review looks like. The AI wrote plausible tests. I caught the ones that would have let real bugs through.
---
## What I've Gained
**Coverage I wouldn't have written.**
How many tedious edge cases do you skip because life is short? AI doesn't skip. It will generate the boring permutation tests that you technically know you should write but never do. My coverage is higher because AI has more patience than I do. Sometimes it tests something and I think: "Oh. Yeah. That could definitely break."
**Faster iteration on test quality.**
"This test is too coupled to implementation. Rewrite it to test behavior." That sentence takes three seconds to type and gets me a better test faster than rewriting it myself.
**More time for test strategy.**
What should we test? How should the test suite be organized? What's the failure mode we're not covering? These strategic questions matter more than the syntax. Offloading the typing lets me think at a higher level.
---
## What I've Lost
**Muscle memory for test syntax.**
I used to type `assert_equal` without thinking. Now I sometimes pause on the argument order. The syntax has faded because I don't practice it.
**The design pressure of writing tests first.**
Last month I built a service to retry failed payments. I wrote the implementation first, then generated tests. The tests passed. Three days later, I needed to add partial refunds. The test structure fought me—I had to rewrite three tests to accommodate the new flow. If I'd written tests first, I would have noticed the inflexible design before I built it. That was the moment I realized: TDD isn't just about coverage, it's about design feedback loops. I'm not sure I've replaced that loop with anything better.
That retry service taught me something else, too. Something happens when you manually type out test cases. You think of cases you wouldn't have thought to ask for. The writing *is* the thinking. Reviewing someone else's tests (even AI-generated ones) doesn't trigger the same discovery process.
And if I had to write tests without AI tomorrow, I'd be slower than I was a year ago. If my internet went down and a critical test needed writing, I'd need to look up assertion syntax I used to know cold. That dependency is real.
**There's a more dangerous version of this story: what if I'm not just losing syntax memory, but losing the ability to even recognize bad tests?** So far, my review skills are sharp. But reviewing is a downstream skill. If I stop writing entirely, does my review quality decay? The muscle you don't use eventually can't even judge the muscles that do. I don't think I'm there yet. But it's the risk I track most carefully.
---
## The Uncomfortable Truth
**I'm a worse test-writer than I was a year ago.**
If you put me in a room with a whiteboard and asked me to write test cases from scratch, I'd be rustier than I was in early 2025. The skill has atrophied from disuse.
**I'm a better test-reviewer than I was a year ago.**
I've spent a year reading and critiquing AI-generated tests. I know what bad AI tests look like. I know what's missing, what's over-specified, what will break in six months. My review skills are sharper than ever.
**These are different skills.**
Writing and reviewing are not the same expertise. The market doesn't recognize this yet. Job interviews still ask you to write tests on a whiteboard. They don't ask you to review AI-generated tests for quality.
But the job has changed. The interview just hasn't caught up.
---
## The Team Question
Last month, a junior dev wrote tests for a new API endpoint. I reviewed them and requested five changes. They all passed. Two days later, I realized we needed a sixth test case—a subtle race condition under concurrent load that I've seen break production twice. I wrote that test myself. The junior dev learned the pattern. But they also learned that my AI-generated tests wouldn't have caught it either. The asymmetry is teaching them something about my process, and I haven't decided if that's good or bad.
If we both lost AI access tomorrow, they'd be better prepared than I am. I've traded that resilience for throughput. That's a bet, and I haven't fully reckoned with the downside.
---
## What This Means
Testing expertise isn't obsolete. It's transformed.
The knowledge of *what* to test matters more than ever. Knowing which edge cases break, understanding the domain, recognizing when a test is too fragile. That judgment is exactly what AI lacks. The expertise is the same; the expression is different.
**Knowing what to test matters more than knowing how to type it.**
I still apply "Bugs as Missing Test Cases" every day, a principle I've followed for over a decade: every production bug reveals a test that should have existed. When something breaks, I still ask: "What test would have caught this before users did?" The philosophy is unchanged. But the hands that write the test are different now.
**Quality judgment can't be automated (yet).**
AI can generate a test that passes. It can't generate a test that will still be valuable in two years. It can't weigh the maintenance cost against the coverage benefit. It can't feel which tests are worth having. That judgment is human.
**The skill transfer is real.**
A developer who never learned to write tests won't be a good test reviewer. You need to have written bad tests to recognize them. The foundation is still required. It's just not the daily practice anymore.
This is part of the broader implementor-to-orchestrator shift. The pattern holds for code, for tests, for documentation. The skill is verification and judgment, not production.
---
I haven't written a test in a year. But I've reviewed a thousand of them.
And when I interview for my next job, they'll ask me to write tests on a whiteboard. They won't ask me to review AI-generated tests for correctness, spot the business logic gaps, or evaluate whether a test suite will survive the next refactor.
The job has changed. The interview hasn't. Yet.
I've already chosen my side of that gap. I traded writing muscle for judgment muscle, syntax memory for review sharpness. The test suite is better than it's ever been. My hands are rustier than they've ever been. I'd make the same trade again tomorrow.
---
## Revision Notes (v6)
**Key changes from v5:**
1. **Cut "quality judgment at level 1" framework reference** — Removed the Judgment Over Execution Premium framework label (formerly line 182). The surrounding paragraph already makes the point; the framework scaffolding was an internal note leaked into a public article.
2. **Tightened "The Team Question" from three concerns to two** — Cut the AI test labeling paragraph ("I don't hide it, but I also don't label every test file"). It raised an ethical question it couldn't answer in two sentences. Section now carries two focused threads: the junior dev skill asymmetry anecdote and the resilience-vs-throughput trade-off.
3. **Smoothed transition at retry service** — Added "That retry service taught me something else, too." to bridge the TDD design pressure example into the writing-is-thinking observation. Previously these were two disconnected ideas in sequence.
4. **Made Orchestrator Identity reference standalone** — Removed parenthetical "(a transition I explored in The Orchestrator Identity)." Article now reads as standalone without dead references to unpublished work.
5. **Strengthened closing from uncertainty to conviction** — Replaced "I'm not sure which side of that gap I want to be on" with a decisive statement: the trade has already been made, and the author would make it again. The article earned the right to end on a claim, not a hedge. The closing now escalates rather than restating the same uncertainty from "The Uncomfortable Truth."
**Previous versions:**
- **v5**: Grounded TDD loss in concrete example, added verification collapse risk, expanded team question
- **v4**: De-AI'd (removed em-dashes, "here's"/"let me" constructions, varied formatting patterns)
- **v3**: Added concrete examples, trimmed abstractions, made Orchestrator Identity cross-reference explicit
- **v2**: Removed TL;DR, reordered Gains/Losses, added subscription renewal test example, deepened TDD uncertainty
- **v1**: Initial draft
**Status: Publication-ready. All framework references removed, section bloat trimmed, closing upgraded from doubt to decision.**