4.5.14Software Engineering

TDD — Red-Green-Refactor cycle

2,084 words9 min readdifficulty · medium

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
Figure — TDD — Red-Green-Refactor cycle

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 / b

Why? 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?
Red (write a failing test) → Green (minimal code to pass) → Refactor (clean up while staying green).
In TDD, what must you do before writing production code?
Write a test that fails for the right reason.
Why must the test fail first (the Red step)?
To prove the test actually has the power to detect the missing/broken behavior — otherwise it might pass for the wrong reason and never catch a bug.
What is the rule about how much code to write in the Green phase?
The minimum needed to pass — hardcoding/faking is allowed; later tests force generalization.
What is the one constraint on the Refactor phase?
You may improve structure but must NOT change observable behavior; all tests stay green.
Why is it valid to hardcode a return value in early Green steps?
It's the simplest pass; the next failing test (triangulation) forces you to generalize, so design grows only as needed.
What does "triangulation" mean in TDD?
Adding multiple small concrete tests to force code to generalize from a fake/hardcoded answer to the real algorithm.
How does TDD turn a bug into permanent protection?
You write a failing test that reproduces the bug; once green, that test stays in the suite as a regression guard.
What does it mean if a test is painful to write?
It's design feedback — your code is likely too coupled or has hidden dependencies; refactor for testability.
One behavior per test or one test per feature — which and why?
One behavior per test, so a failure pinpoints exactly what broke (failure localization).

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

starts with

then

then

repeat next behavior

acts as

preserves

failing first

minimal slice

painful test signals

only on

TDD Workflow

RED write failing test

GREEN minimal code to pass

REFACTOR improve design

Test as specification

Test as safety net

Proves test can fail

Testable design emerges

Build only what is needed

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.

Go deeper — visual, from zero

Test yourself — Software Engineering

Connections