Test-Driven Development: Complete TDD Guide with Examples
Test-Driven Development (TDD) is a software development practice where you write automated tests before writing the production code, cycling through Red (failing test), Green (passing test), and Refactor (clean up) phases for every small piece of functionality.
What You’ll Learn
- The Red-Green-Refactor cycle and how to apply it
- Why TDD leads to better design and fewer bugs
- Real-world TDD examples in Python and JavaScript
- Common TDD pitfalls and how to avoid them
- How to introduce TDD to an existing team
Why TDD Matters
Teams using TDD report 40-80% fewer production defects according to studies from IBM and Microsoft. Beyond bug reduction, TDD fundamentally changes how you design code — you naturally write smaller, more testable, more modular units because you design for testability first. Test-driven code has higher cohesion, lower coupling, and better documentation (the tests themselves serve as executable specifications).
Durga Antivirus Pro uses TDD for all signature parsing modules — when a single missed edge case could let malware through, writing the test first ensures every requirement is explicitly verified.
Learning Path
flowchart LR
A[Testing Basics] --> B[Code Smells & Refactoring]
B --> C[TDD<br/>You are here]
C --> D[Unit Testing]
D --> E[CI/CD Pipeline]
style C fill:#f90,color:#fff
The TDD Cycle
TDD follows three repeating steps, often called Red-Green-Refactor:
1. Red — Write a Failing Test
Write a test that defines the behavior you want. It should fail because the feature doesn’t exist yet.
# test_calculator.py
from calculator import add
def test_add_returns_sum():
result = add(2, 3)
assert result == 5Running this test produces a failure:
E ModuleNotFoundError: No module named 'calculator'2. Green — Write the Minimum Code
Write just enough production code to make the test pass — nothing more.
# calculator.py
def add(a, b):
return a + bNow the test passes:
1 passed in 0.01s3. Refactor — Clean Up
Improve the code without changing its behavior. Tests stay green throughout.
# calculator.py — add type hints and docstring
def add(a: int, b: int) -> int:
"""Return the sum of two integers."""
return a + bRepeat
Each cycle takes 1-5 minutes. You repeat it hundreds of times per feature, building up the solution incrementally.
TDD Example: FizzBuzz
Let’s walk through a complete TDD session for the classic FizzBuzz problem.
First Test: Multiples of 3
# test_fizzbuzz.py
from fizzbuzz import fizzbuzz
def test_returns_fizz_for_multiples_of_3():
assert fizzbuzz(3) == "Fizz"
assert fizzbuzz(6) == "Fizz"Write the minimum code:
# fizzbuzz.py
def fizzbuzz(n):
return "Fizz"Second Test: Multiples of 5
def test_returns_buzz_for_multiples_of_5():
assert fizzbuzz(5) == "Buzz"
assert fizzbuzz(10) == "Buzz"Update code:
def fizzbuzz(n):
if n % 5 == 0:
return "Buzz"
return "Fizz"But this breaks the earlier test — fizzbuzz(3) now returns None. TDD catches this immediately.
Third Test: Multiples of Both 3 and 5
def test_returns_fizzbuzz_for_multiples_of_15():
assert fizzbuzz(15) == "FizzBuzz"Full implementation:
def fizzbuzz(n):
if n % 15 == 0:
return "FizzBuzz"
if n % 5 == 0:
return "Buzz"
if n % 3 == 0:
return "Fizz"
return str(n)Fourth Test: Non-Multiples
def test_returns_number_as_string():
assert fizzbuzz(1) == "1"
assert fizzbuzz(2) == "2"
assert fizzbuzz(7) == "7"All tests pass. We’ve built FizzBuzz with full test coverage, one small step at a time.
Benefits of TDD
Better Design
When you write tests first, you naturally design for testability. This means smaller functions, dependency injection, and clear interfaces. Code written with TDD has measurably lower coupling.
Executable Documentation
Tests are never out of date — if they pass, they accurately describe the system. New team members read tests to understand expected behavior.
Regression Safety Net
Every time you run the test suite, you verify that existing behavior still works. This makes refactoring safe and encourages continuous improvement.
Faster Debugging
When a test fails, you know exactly which behavior broke. Without TDD, you might not discover a regression until staging or production.
Common TDD Mistakes
1. Writing Too Large a Test
Each test should verify one specific behavior. A test that checks five things is hard to debug when it fails.
Fix: One assertion per test. Use descriptive test names.
2. Skipping the Red Phase
Writing a test that already passes means you haven’t verified it tests anything. Always watch it fail first.
Fix: Run the test before writing production code. Confirm it fails.
3. Over-Mocking
Mocking every dependency creates brittle tests that verify mock behavior, not real behavior.
Fix: Mock only external boundaries (network, filesystem, database). Use real objects for internal dependencies.
4. Writing Tests After Code
Writing tests after the fact is testing, not TDD. You lose the design benefits and often end up with untestable code that requires heroic mocking.
Fix: Write the test first. If it’s hard to write, your design needs improvement.
5. Not Refactoring
TDD’s third step is crucial. Without refactoring, you accumulate technical debt even with full test coverage.
Fix: After every green cycle, look for duplication, unclear names, and awkward structures.
6. Abandoning TDD Under Pressure
“Let’s skip tests to ship faster” is tempting. The opposite is true — untested code causes regressions that slow future delivery.
Fix: TDD is fastest in the long run. Short-term speed gains from skipping tests are quickly lost to debugging.
TDD Adoption Strategy
For New Projects
Start with TDD from day one. The first test might be “the app starts” or “the API responds to a health check.” Build the test suite as you build the application.
For Existing Codebases
Introduce TDD incrementally:
- Write tests for new features using TDD
- Add tests when fixing bugs — write a failing test that reproduces the bug, then fix it
- Cover critical paths — authentication, payment, data processing
- Refactor legacy code under test coverage
Team Adoption
TDD requires discipline. Start with pair programming — one developer writes the test, the other writes the code. Rotate pairs to spread the practice.
TDD in JavaScript
// fizzbuzz.test.js
const fizzbuzz = require('./fizzbuzz');
test('returns Fizz for multiples of 3', () => {
expect(fizzbuzz(3)).toBe('Fizz');
expect(fizzbuzz(6)).toBe('Fizz');
});
test('returns Buzz for multiples of 5', () => {
expect(fizzbuzz(5)).toBe('Buzz');
expect(fizzbuzz(10)).toBe('Buzz');
});
test('returns FizzBuzz for multiples of 15', () => {
expect(fizzbuzz(15)).toBe('FizzBuzz');
});
test('returns number as string for non-multiples', () => {
expect(fizzbuzz(1)).toBe('1');
expect(fizzbuzz(7)).toBe('7');
});Practice Questions
1. What are the three phases of the TDD cycle?
Red (write a failing test), Green (write minimum code to pass), Refactor (clean up while keeping tests green).
2. Why should you watch the test fail before writing implementation?
To confirm the test actually tests something. A test that passes immediately might not be verifying the right behavior.
3. What is the maximum recommended duration for a single TDD cycle?
1–5 minutes. If a cycle takes longer, the unit of work is too large.
4. How does TDD improve code design?
By forcing you to design for testability — smaller functions, dependency injection, clear interfaces, and lower coupling.
5. How do you introduce TDD to an existing codebase?
Start with new features (write tests first), add tests when fixing bugs, cover critical paths, then gradually refactor legacy code under test.
Challenge: Implement a shopping cart using strict TDD. Write tests for addItem, removeItem, applyDiscount, and calculateTotal before writing any production code. Every test must fail before it passes.
FAQ
What’s Next
| Tutorial | What You’ll Learn |
|---|---|
| Unit Testing Best Practices | Writing effective, maintainable unit tests |
| Integration Testing Strategies | Testing component interactions |
| Continuous Testing in CI/CD | Automating TDD in the pipeline |
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro. Updated 2026-06-20.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro