TDD in Practice: Red-Green-Refactor
That is why TDD works best as a design discipline, not as a ritual.
Red-Green-Refactor is a thinking cycle
The three steps matter because they force a specific rhythm:
- write a failing test for one small behavior
- implement the smallest change that makes it pass
- refactor while keeping behavior stable
When any step is skipped, the value drops:
- without red, you may not know the test can fail meaningfully
- without green, you may overbuild
- without refactor, the code and tests accumulate duplication
The power of TDD comes from keeping the cycle small enough that each step still teaches something.
TDD is strongest where design uncertainty is high
TDD often provides the best return in areas like:
- business rules with many edge cases
- parsing and transformation logic
- domain logic that changes frequently
- code where correctness is easier to state than the implementation
It is less compelling when the work is dominated by wiring, framework configuration, or exploratory UI assembly with unclear behavior.
Good TDD starts from behavior, not implementation
A strong TDD test describes a business rule or external behavior:
- premium members receive a discount
- invalid input is rejected
- duplicate requests are ignored safely
A weak TDD test starts from internal structure:
- a private helper is called
- a method invokes another method twice
- an object graph looks a certain way after incidental implementation choices
Tests written from implementation shape often make refactoring harder instead of safer.
Keep the cycle small
TDD becomes heavy when the team writes large speculative tests first.
A healthier rhythm is:
- choose one tiny next behavior
- write one failing example
- implement the minimum
- clean the design before moving on
This is what keeps TDD from turning into ceremony.
Refactor is not optional
Many teams practice “red-green-stop” instead of red-green-refactor. That misses much of the design benefit.
Refactor is where the team:
- improves naming
- removes duplication
- reshapes abstractions
- clarifies intent
Without it, tests may pass while the design still drifts toward clutter.
TDD should not be forced onto every task
Applied mechanically to every kind of work, TDD becomes frustrating and wasteful.
Use more judgment when the work is mainly:
- visual styling
- uncertain product discovery
- third-party integration spikes
- broad infrastructure wiring
In those cases, a different feedback loop may be more effective. TDD is a tool for design clarity, not a universal morality rule.
Team adoption should stay sustainable
Healthy TDD practice usually looks like:
- small cycles on rule-heavy code
- readable tests that express intent
- review culture that values refactor quality
- no pressure to produce artificial tests where they add little value
This is far more durable than treating TDD as a dogma.
Common anti-patterns
Watch for these patterns:
- writing large test batches before any feedback
- testing implementation detail instead of behavior
- skipping refactor after green
- using TDD mainly to inflate test counts
- forcing TDD where design feedback is weak
These patterns make TDD feel slow because they remove the very feedback that makes it useful.
Review checklist
Before calling the TDD practice healthy, ask:
- Does the test describe behavior that matters?
- Is the feedback cycle small enough to learn from each step?
- Did the code improve during the refactor step?
- Would the test still be valuable after internal refactoring?
- Is TDD being used where design feedback is genuinely useful?
Closing judgment
Good TDD is not about writing tests early for its own sake. It is about getting design feedback early and often enough to shape better code.
Its real value appears only when the cycle stays small and behavior-focused.
Continue Reading
Related posts
Mock, Stub, and Spy Test Double Design Guide
A practical guide to choosing the right test double. Covers the difference between state verification and interaction verification, the risk of excessive mocking, and how to decide between mocks, stubs, spies, and fakes.
🧪 TestPractical React Testing Library Design Guide
How to use React Testing Library as a user-centered testing tool. Covers query priority, interaction tests, async UI, provider wrappers, and how to avoid excessive mocking.
🔧 ToolsPostman Practical Guide: API Testing, Automation, and Team Collaboration
A practical guide to using Postman for API exploration, environment management, collection design, shared test flows, and Newman-based CI checks.
⚙️ BackendA Guide to Spring Boot Testing Strategy
This guide explains how unit tests, slice tests, and integration tests should be divided in a Spring Boot codebase to balance speed and confidence.
Next Path