4.5.14 · D1Software Engineering

Foundations — TDD — Red-Green-Refactor cycle

1,960 words9 min readBack to topic

Before you can follow the parent note, you need to already own a small pile of ideas and symbols. The parent assumes them. This page builds every single one from zero, in an order where each idea rests on the one before it. Nothing is used before it is drawn and named.


0. What even is a "test"? (the anchor picture)

Picture two boxes. On the left, the real code — the machine you are building. On the right, the test — a little inspector that pokes the machine with a known question and holds up the answer sheet.

Figure — TDD — Red-Green-Refactor cycle

Look at the red arrow: the inspector compares actual (what the machine returned) against expected (what the answer sheet says). That single comparison is the heart of everything. The parent's whole "Red / Green" language is just "did that comparison match or not?"


1. function and calling it — fizzbuzz(1)

The round brackets () are the "feed it this" symbol. Whatever sits inside is the argument (the input). So in the parent:

  • fizzbuzz(1) → feed 1, expect back the text "1".
  • divide(10, 0) → feed two inputs, 10 and 0.

2. Return values and the str(n) symbol

Two flavours of value appear in the parent, and mixing them up breaks tests:

str(n) is the machine that converts a number n into its text form: str(2) gives "2". The parent needs this because FizzBuzz must return text ("1", "Fizz"), not the number 1.


3. The equality symbol == (and why it's doubled)

Figure — TDD — Red-Green-Refactor cycle

The red arrow shows == producing a single True/False verdict. That verdict is what an assertion (next section) inspects.


4. assert — the moment a test decides Red or Green

So assert fizzbuzz(1) == "1" reads as: "I claim: running fizzbuzz on 1 gives the text 1. Prove me wrong."

  • Claim True → test is Green 🟢
  • Claim False → test is Red 🔴

This is the exact bridge from the parent's colours to real code: the colour is just the result of the assert.


5. pytest.raises and exceptions — testing that code breaks correctly

The parent's bug example needs a way to say "I expect an error here." That's with pytest.raises(ValueError): ...:

ValueError and ZeroDivisionError are just names of kinds of errors. The parent's bug is that divide(10,0) throws the wrong-named error (ZeroDivisionError), and the fix makes it throw the intended ValueError.


6. The modulo symbol % — "the remainder"

This is the one symbol most likely to trip a newcomer, so it gets its own picture.

Figure — TDD — Red-Green-Refactor cycle

Look at the number line: we chop it into blocks of 3. The red marker shows the leftover distance past the last complete block. When the marker lands exactly on a block edge, the leftover is 0.

Why does FizzBuzz need it? Because "divisible by 3" is exactly "n % 3 == 0" (nothing left over). Walk every case:

n n % 3 meaning
3 0 divisible → "Fizz"
4 1 one past a block
5 2 two past a block
6 0 divisible → "Fizz"
0 0 zero is divisible by everything

Use \% in prose so it renders, but in code it's the bare %.


7. if / branching and truthiness of strings — out or str(n)

The refactored FizzBuzz ends with return out or str(n). This uses a subtle rule:

That single line replaces a pile of ifs — and because a test already pins the behaviour, the parent can safely make this clever swap during Refactor and trust the tests to confirm nothing changed.


8. Putting the vocabulary into the cycle

Now every word on the parent page is grounded. Here's how these foundations feed the topic:

Function and calling it

Return value

Number vs string and str()

Equality == returns True or False

assert makes a claim

Red or Green colour

Exception and pytest raises

Modulo percent for divisible-by

if branch and truthiness

Minimal code in Green

Red-Green-Refactor cycle

Regression guard forever

Each foundation on the left is a prerequisite the parent silently relies on. Once you own them, the loop 🔴→🟢→🔵 is just: write an assert that's False, make it True with the least code, tidy while it stays True.


Where these connect in the vault

  • The "keep the check forever" idea becomes Regression Testing and the safety net in Refactoring.
  • The Red/Green/Refactor rhythm itself: Red-Green-Refactor and the parent TDD parent.
  • Writing single-behaviour tests is the craft of Unit Testing.
  • When code has hidden dependencies (the "painful to write" feedback), you reach for Test Doubles - Mocks Stubs Fakes and cleaner designs via SOLID Principles.
  • Specifying behaviour in plain language is the sibling style BDD - Behavior Driven Development; running tests automatically on every change is Continuous Integration; measuring how much code your tests touch is Code Coverage.

Equipment checklist

Self-test: can you answer each before reading the parent?

What does fizzbuzz(1) mean, and what do the brackets do?
It runs the machine named fizzbuzz with input 1; the brackets () feed the input (argument) in.
Difference between "1" (string) and 1 (number)?
"1" is text (a character in quotes), 1 is a quantity; a test comparing against "1" fails if you return the number 1.
What does str(n) do?
Converts the number n into its text form, e.g. str(2)"2".
Single = vs double ==?
= stores a value into a name; == asks "are these equal?" and returns True or False.
What does assert X do when X is False?
It fails loudly and stops — that failure is the Red state of a test.
What is n % 3 and when is it 0?
The remainder after dividing n by 3; it's 0 exactly when n is divisible by 3 (including n = 0).
What does with pytest.raises(ValueError): check?
That the code inside throws a ValueError; it passes only if that specific exception is raised.
Why does return out or str(n) fall back to str(n)?
Because an empty string "" is falsy, so if out is empty the or returns str(n) instead.
In one sentence, what is a test's lasting value?
It's a check you can re-run for free forever, so it keeps guarding against regressions.
Recall Quick sanity: what colour is

assert 2 == 3? Red — the claim is False, so the assertion fails.