# Why I Write Tests After the Feature Works (And Still Call It TDD)
By Govind Bajaj · 2025

Tags: testing, tdd, methodology, workflow, software-engineering
I write the feature first. Make it work. Then write tests. Purists call this backwards. My production bug rate says otherwise. Here's the workflow.
I write the feature first. Make it work. Then write tests.

Purists call this backwards. My production bug rate says otherwise. In two years of shipping this way, I have had three production bugs that tests would have caught. Three.

I am not anti-testing. I write comprehensive test suites. I just write them after the code works, not before. The distinction matters less than the quality of the tests.

## Why I Reversed the Order

Traditional TDD says: write a failing test, write the minimum code to pass, refactor. The theory is that tests drive design and ensure coverage.

In practice, I found three problems with strict TDD.

First, premature test design. When I do not know what the code should look like yet, the tests I write are wrong. I end up rewriting tests three times as my understanding evolves. The tests become a constraint, not a guide.

Second, implementation lock-in. Tests written against an early design resist refactoring. Changing the implementation breaks tests that should not care about internals. The tests become the enemy of clean code.

Third, exploration cost. Some problems require exploration. I need to try three approaches before finding the right one. Writing tests for all three is waste.

## My Actual Workflow

Step 1: Explore. Write spike code with no tests. Try approaches. Throw away what does not work. The goal is understanding, not production code.

Step 2: Implement. Write the production feature. Keep it simple. Focus on correctness and edge cases.

Step 3: Verify manually. Test the feature in the browser or with curl. Make sure the happy path and obvious edge cases work.

Step 4: Write tests. Now that the design is stable, write tests that verify behavior. These tests are better because they reflect the actual design, not a guessed one.

Step 5: Refactor. With tests providing safety, clean up the implementation. Extract functions. Rename variables. Simplify logic.

## What I Actually Test

I do not test everything. I test what breaks.

API route handlers: Request validation, authentication checks, response shapes. These break when you change middleware or add fields.

Database queries: Complex aggregations, edge cases with empty results, pagination. These break when schemas evolve.

Business logic: Payment calculations, discount rules, inventory tracking. These break when requirements change.

I do not test: simple CRUD operations that Mongoose handles, React component rendering (unless complex logic), third-party library internals, or CSS.

## The Test Quality Checklist

A good test has three properties. It fails when the behavior breaks. It passes when the behavior works. And it does not break when unrelated code changes.

Tests that fail on refactoring are bad tests. They are coupled to implementation, not behavior. The fix is not to avoid refactoring — it is to write better tests.

## Takeaways

- Test order matters less than test quality — write tests when your design is stable
- Write spike code first for exploration, then production code, then tests
- Tests should verify behavior, not implementation — they should not break during refactoring
- Do not test everything — test what breaks: business logic, API handlers, complex queries
- Manual verification is still valuable — automated tests do not replace thinking
- Three production bugs in two years is a reasonable trade-off for this workflow
- The goal is confidence, not coverage percentage
- Strict TDD works for well-understood problems — most real problems are not well-understood until you explore them