Test Driven Development: Red-Green-Refactor
Test Driven Development, or TDD, is a development practice where you write tests before writing production code. The familiar Red-Green-Refactor cycle forces developers to think through desired behavior before implementing. The result is cleaner, more testable, and more focused code.
In a Nutshell
- TDD writes tests before production code.
- The cycle consists of Red, Green, and Refactor.
- Red: Write a test that fails.
- Green: Write the minimal code to make the test pass.
- Refactor: Improve the code without changing its behavior.
- TDD encourages small steps, clear requirements, and testable code.
Quick Technical Overview
TDD reverses the traditional order. Instead of writing code first and adding tests later, you start with a failing test. That test describes the desired behavior. Next, you write the smallest possible code that makes the test pass. Finally, you clean up the code without breaking the tests.
The Red-Green-Refactor Cycle
Red
Write a test for a feature that doesn’t yet exist. Run the test. It must fail—otherwise, the test isn’t meaningful.
Green
Write the minimal code needed to make the test pass. Elegance doesn’t matter at this stage; the goal is a passing test.
Refactor
Clean up the code, remove duplication, improve names and structure. All tests must remain green throughout.
Best Practices
- Small steps: Each cycle should take only a few minutes.
- Focus on behavior: Tests describe what the software should do, not how it does it internally.
- No unnecessary code: Write only code that satisfies a test.
- Don’t skip refactoring: The third step is essential for clean code.
- Keep tests readable: Tests document your code’s intent.
Real-World Example: Discount Calculation with TDD
import unittest
from app.pricing import calculate_discount
class TestDiscount(unittest.TestCase):
def test_no_discount_below_threshold(self):
self.assertEqual(calculate_discount(50), 0)
def test_discount_for_threshold(self):
self.assertEqual(calculate_discount(100), 10)
def test_higher_discount_for_large_amount(self):
self.assertEqual(calculate_discount(200), 20)
if __name__ == '__main__':
unittest.main()
def calculate_discount(amount):
if amount >= 200:
return 20
if amount >= 100:
return 10
return 0
Advantages and Disadvantages
Advantages
- Clearer requirements: Tests force you to define desired behavior upfront.
- Fast feedback: Errors surface immediately.
- Better code quality: TDD encourages small, focused functions.
- Safe refactoring: Tests protect against regressions.
- Living documentation: Tests show how the software should be used.
Disadvantages
- Learning curve: TDD requires practice and discipline.
- Initial overhead: Getting started takes more time than conventional programming.
- Not suitable for everything: Spikes, prototypes, and exploratory development benefit less.
- Poor tests lead to poor design: Badly written tests can push your code in the wrong direction.
Key Exam Concepts
- Definition and goal of TDD.
- The three phases: Red, Green, Refactor.
- Difference between TDD and conventional testing.
- Strengths and limitations of TDD.
- Importance of small steps and refactoring.
Common Exam Questions (with Brief Answers)
-
What is TDD? A development practice where tests are written before production code.
-
What are the three phases of the TDD cycle? Red, Green, Refactor.
-
What happens in the Red phase? You write a test that fails because the feature doesn’t yet exist.
-
What is the goal of the Green phase? The smallest amount of code that makes the test pass.
-
What is one advantage of TDD? Fast feedback and better test coverage through small, focused steps.
Key Resources
- https://www.agilealliance.org/glossary/tdd/
- https://martinfowler.com/bliki/TestDrivenDevelopment.html
- https://en.wikipedia.org/wiki/Test-driven_development
Frequently Asked Questions
What does Red-Green-Refactor mean?
Red represents a failing test, Green is the minimal code that makes it pass, and Refactor is cleaning up the code without changing behavior.
Does TDD require testing every single line of code?
No. TDD aims to define behavior through tests, not blindly cover every line. Test quality matters more than test quantity.
Is TDD only for unit tests?
TDD traditionally starts with unit tests but can extend to integration tests or acceptance tests—for example, through ATDD.
What is ATDD?
Acceptance Test Driven Development extends TDD to acceptance tests. Tests are written from the user’s or stakeholder’s perspective before implementation.
How long should a TDD cycle take?
An ideal cycle takes just a few minutes. Small steps provide quick feedback and make refactoring straightforward.
What happens if I skip the Refactor step?
Code becomes hacky and harder to maintain over time. Refactoring is what ensures clean code and sustainable quality.
Can TDD be introduced into existing projects?
Yes, but it often requires refactoring existing code to make it testable. New features and bug fixes are good starting points.
Is TDD slower than conventional programming?
TDD may feel slower at first. Over the long term, however, it reduces the cost of bugs and speeds up refactoring and extensions.
What is a TDD anti-pattern?
A common anti-pattern is writing tests after implementation. Overly detailed mocks or tests that are too large for a single cycle are also problematic.
How does TDD promote clean code?
TDD enforces testable code, which typically results in more modular, loosely coupled, and well-named code.
What’s the difference between TDD and BDD?
TDD focuses on technical tests for developers. BDD translates requirements into examples written in business language to bridge developers and business stakeholders.
Which programming languages are suitable for TDD?
TDD is language-agnostic in principle. It’s particularly popular in Java, Python, JavaScript, C#, and Ruby due to excellent testing frameworks.
What should I test when starting from scratch?
Begin with the simplest, most central behavior. For example, the happy path or an obvious error case before tackling complex edge cases.
How do I handle external dependencies in TDD?
Replace external dependencies with test doubles like mocks, stubs, or fakes so your unit test stays isolated and fast.
What’s a common reason TDD fails?
Steps that are too large, unclear requirements, and skipping the Refactor step often cause TDD to feel frustrating and unsustainable.
Next in the Software Testing Learning Path
The next article in the Software Testing Learning Path covers Behavior Driven Development — how BDD improves communication between developers and stakeholders.



