Why Trial (Not Adopt)
TDD is powerful but dogmatic application hurts productivity. I use TDD selectively, not religiously.
When TDD Shines
- Complex algorithms - Writing tests first clarifies the spec
- Bug fixes - Write a failing test, then fix
- API contracts - Tests document expected behavior
- Refactoring - Tests provide safety net
- Pair programming - One writes tests, one implements
When TDD Hurts
- Exploratory coding - Don’t know what I’m building yet
- UI work - Tests are brittle, iteration is fast
- Integration code - Mocking external services is tedious
- Prototypes - Throw-away code doesn’t need tests
My Actual Practice
Services - Usually TDD. Business logic is testable and stable.
# Write test first
test "processes valid order" do
result = Orders::Process.new(order: valid_order).call
assert result.success?
end
# Then implement
Controllers - Test after. I want to see the feature work first.
Views - Minimal testing. System tests catch integration issues.
AI-generated code - Never TDD. Let Claude generate, then write tests to verify.
The Honest Truth
Pure TDD advocates say “never write code without a failing test first.”
In practice, I write tests first maybe 30% of the time. The other 70%:
- Spike first, test after
- Let the feature stabilize, then lock it down
- AI generates code, I verify with tests
The goal is confidence in your code, not adherence to a process.
Test Coverage Goals
I aim for:
- 90%+ on services and business logic
- 70%+ on controllers
- System tests for critical paths
- Skip testing obvious framework behavior
High coverage everywhere is a vanity metric. High coverage on risky code is engineering.