TDD — Red-Green-Refactor cycle
WHAT is TDD?
The three states:
| Phase | Color | Goal | Rule |
|---|---|---|---|
| Red | 🔴 | Write a test that fails | Don't write production code without a failing test |
| Green | 🟢 | Make it pass fast (even ugly) | Write the minimum code, even fake it |
| Refactor | 🔵 | Improve design, no new behavior | Only refactor on green; tests must stay passing |

WHY do it this way?
HOW the cycle runs (step by step)
Worked Example 1 — A FizzBuzz from scratch (derivation of code from tests)
We derive the implementation purely by following Red-Green-Refactor.
Cycle 1 — RED
def test_one_returns_1():
assert fizzbuzz(1) == "1"Why this step? Smallest possible behavior. Running it fails: fizzbuzz doesn't exist (Red for the right reason after we stub it).
Cycle 1 — GREEN
def fizzbuzz(n):
return "1" # fake it — hardcoded!Why this step? Minimum to pass. Hardcoding is intentional — it forces the next test to drive generalization.
Cycle 2 — RED
def test_two_returns_2():
assert fizzbuzz(2) == "2"Why? This forces us to stop hardcoding "1".
Cycle 2 — GREEN
def fizzbuzz(n):
return str(n)Why? Now generalized just enough. Both tests pass.
Cycle 3 — RED → GREEN (the Fizz rule)
def test_three_returns_Fizz():
assert fizzbuzz(3) == "Fizz"def fizzbuzz(n):
if n % 3 == 0:
return "Fizz"
return str(n)Cycle 4 — add Buzz, then FizzBuzz, each driven by its own failing test. After all tests green:
REFACTOR
def fizzbuzz(n):
out = ""
if n % 3 == 0: out += "Fizz"
if n % 5 == 0: out += "Buzz"
return out or str(n)Why this step? Removed the branchy duplication. No new test needed — behavior is identical; tests still pass, proving the refactor is safe.
Worked Example 2 — Driving a bug fix with a test
You found a bug: divide(10, 0) crashes ungracefully.
RED — reproduce the bug as a test
def test_divide_by_zero_raises_value_error():
with pytest.raises(ValueError):
divide(10, 0)Why? The failing test captures the bug precisely. It fails now (raises ZeroDivisionError, not ValueError).
GREEN
def divide(a, b):
if b == 0:
raise ValueError("cannot divide by zero")
return a / bWhy? Minimal guard. Test passes — and it stays in the suite forever as a regression guard so the bug can never silently return.
The economics — WHY short cycles matter
Flashcards
What are the three phases of TDD in order?
In TDD, what must you do before writing production code?
Why must the test fail first (the Red step)?
What is the rule about how much code to write in the Green phase?
What is the one constraint on the Refactor phase?
Why is it valid to hardcode a return value in early Green steps?
What does "triangulation" mean in TDD?
How does TDD turn a bug into permanent protection?
What does it mean if a test is painful to write?
One behavior per test or one test per feature — which and why?
Recall Feynman: explain it to a 12-year-old
Imagine building LEGO with a checklist. Before you snap any brick, you write down: "When I'm done, the door should open." Right now the door doesn't even exist, so the checklist item is red (not done). Then you add the smallest bit of LEGO to make the door open — now it's green (done!). Then you tidy the bricks so it looks neat, but you keep checking the door still opens (stay green). You do this one tiny thing at a time. The cool part: every checklist item stays forever, so if you bump the LEGO later and break the door, the checklist instantly turns red and tells you. You always know exactly what works.
Connections
- Unit Testing — TDD is built on unit tests as the feedback unit.
- Refactoring — the third phase; safe only because tests are green.
- Red-Green-Refactor vs BDD - Behavior Driven Development — BDD raises TDD to user-story language.
- Regression Testing — every TDD test becomes a regression guard.
- Code Coverage — a side effect of TDD, not the goal.
- Continuous Integration — the green test suite is the gate for merging.
- SOLID Principles — testable code naturally trends toward loose coupling.
- Test Doubles - Mocks Stubs Fakes — used to isolate the unit under test.
Concept Map
Hinglish (regional understanding)
Intuition Hinglish mein samjho
TDD ka simple matlab hai: pehle test likho, phir code. Ulta lagta hai na? Par idea ye hai ki test tumhara "spec" ban jaata hai — woh batata hai ki "done" ka kya matlab hai, code likhne se pehle hi. Cycle teen step ka hai: Red (ek chhota failing test likho jo abhi pass nahi hoga), Green (sirf utna code likho jitna test pass karne ke liye chahiye, hardcoding bhi chalegi), aur Refactor (code ko saaf-suthra karo bina behaviour badle, tests green hi rehne chahiye).
Red step zaroori kyun? Kyunki agar test pehle fail nahi hua, toh tumhe pata hi nahi chala ki test mein "dum" hai ya nahi. Fail hona prove karta hai ki test galti pakad sakta hai. Phir Green mein tum sabse simple code likhte ho — kabhi-kabhi to value hardcode kar dete ho — aur agla test tumhe generalize karne par majboor karta hai (isko triangulation bolte hain).
Fayda kya? Bug agar tum likhte-likhte hi pakad lo, toh seconds lagte hain fix karne mein. Wahi bug production mein mile toh ghante aur paisa dono jaate hain. Har test hamesha ke liye suite mein reh jaata hai, toh agar future mein koi cheez tootegi, test turant red ho ke bata dega. Yahi 80/20 hai — thodi si discipline (chhote loops) sabse badi pareshani (late, tangled bugs) ko khatam kar deti hai.
Ek common galti: "abhi code likh leta hoon, test baad mein add karunga." Ye fast lagta hai par dhokha hai — baad waale tests aksar likhe hi nahi jaate, aur code-first code testable hota hi nahi (tightly coupled). Toh test ko spec maano, pehle likho, tabhi asli speed milegi.