Theory: Extreme Programming & the TDD Cycle
Traditional development writes the code first and tests it afterwards, if there is time. Extreme programming turns that order upside down: write the test first. This lesson explains why, then walks through the red-green-refactor cycle, its UML state diagram (which the professor said is very likely in the exam), and the Fibonacci example the professor built step by step in class.
The Problem with Traditional Testing
- Tests come after development, if at all. Testing sits at the end of the schedule, so when the deadline approaches it is the part that gets squeezed. Near the deadline there is simply no time left.
- Fixing a bug adds new code, and new code can bring new bugs. Without a full set of tests to re-run, every fix is a gamble.
- Many feature freezes and branches. Freeze the features, test that version, fix, branch again, freeze again… The process becomes complex and slow.
The Extreme Idea: Test First
Extreme programming (XP) is a lightweight agile process. Its idea for testing is to flip the order: write the test first, then write the code that passes it.
Think of a tailor. A careless tailor sews a suit and then checks whether it fits. A good tailor takes the measurements first, and every seam is cut to match them. The measurements are the tests: they exist before the cloth is cut, and the suit is finished exactly when it matches them all.
Diagram loads as you scroll.
XP brings several practices with it:
- Tests define “done”. If all the tests pass, the code meets every requirement you tested, and no extra code is needed.
- There is always a working prototype, so the customer, who in XP is on site with the team, can check it early and often.
- Pair programming: two people work at one computer and share the responsibility for the code.
- Refactoring: making the code cleaner without changing what it does. It has three steps: freeze the features, refactor, then run a regression test to prove nothing changed.
Test-Driven Design: Red, Green, Refactor
Test-driven design (TDD) works in a short repeating cycle, named like a traffic light:
- Red: write a small test that fails (the code does not exist yet, or does not yet do this).
- Green: write the simplest code that makes the test pass. Small steps only!
- Refactor: when the code becomes messy, clean it up. All tests must stay green.
…and then start again with the next failing test.
The TDD state diagram
The professor drew the cycle as a UML state diagram. The boxes are states, the words in square brackets are state conditions (guards), and the text on each arrow is the trigger that moves you to the next state.
Diagram loads as you scroll.
Exam radar: the professor said this diagram is very likely in the exam, and that if the exam gives it to you, you can answer the questions from it. Learn the two states with their conditions (GREEN: all tests pass, RED: 1+ tests fail) and the five transitions.
Walk through it in five steps:
- Start → GREEN. Nothing has been written yet, so no test fails: you begin in GREEN.
- GREEN → RED. The sensible first move is to write a failing test. You do not refactor or end at the very start, because there is nothing to refactor yet.
- RED → GREEN. In RED there is only one thing to do: fix the functional code until 0 tests fail. (You do not write another test while you are red.)
- Repeat, then refactor. Write more failing tests a few times. When the code gets messy, refactor. If you are lucky, no tests are broken and you stay in GREEN. If you are unlucky, tests are broken and you go to RED and fix them.
- End. When you cannot think of any more useful tests, you stop. The professor’s phrase: “fear has turned into boredom.” At first you are afraid the code is wrong, so you write tests; when writing more tests feels boring because you are sure they will pass, you are done.
Three ways to get to green
| Way | What you do | Example |
|---|
| Fake it | Return a hard-coded value (a stub) that passes the test. Improve it later. | return 0; |
| Obvious implementation | If the real code is simple and clear, just write it. | return a + b; |
| Triangulation | Generalize the code only when two or more examples need it. | return fib(n-1) + fib(n-2); |
Triangulation is named after the way a surveyor fixes a position: one sighting gives only a line, two sightings from different places pin down the point. One test such as add(2, 2) == 4 can be passed by return 4;, but a second test add(3, 5) == 8 cannot, and now the general return a + b; is justified.
The Fibonacci Example, Step by Step
Fibonacci: fib(0)=0, fib(1)=1, and fib(n)=fib(n−1)+fib(n−2). The professor built it with TDD:
| Step | Test added | Code of fib(n) | State |
|---|
| 1 | assertEquals(0, fib(0)) | (no fib yet) | RED: fails, fib does not exist |
| 2 | | return 0; | GREEN (fake it) |
| 3 | assertEquals(1, fib(1)) | return 0; | RED: 0 is not 1 |
| 4 | | if (n < 1) return 0; return 1; | GREEN |
| 5 | assertEquals(1, fib(2)) | (same) | GREEN already: the tests start to repeat, so refactor the tests into a table |
| 6 | assertEquals(2, fib(3)) | (same) | RED: returns 1, not 2 |
| 7 | | if (n < 2) return n; return fib(n-1) + fib(n-2); | GREEN: triangulation, the code is now general |
Exam radar: the professor asked the class: in step 1 the test is correct, so why does it fail? Because the fib function does not exist yet. In TDD you write the test before the code, so the first run of a new test is expected to fail.
Refactoring the test code
At step 5 the test methods start to look identical apart from their numbers. So the professor refactored the test code: he put the cases in a two-dimensional array of {n, expected} pairs and checked them in a loop. Refactoring cleans test code too, not only production code.
@Test
public void fibonacciValues() {
int[][] cases = { {0, 0}, {1, 1}, {2, 1}, {3, 2} }; // each row: {n, expected}
for (int[] c : cases) {
assertEquals(c[1], fib(c[0])); // (expected, actual)
}
}
Adding a new case is now one more row in the table instead of a new method.
Worked Examples
Exercise 1
Name three problems with traditional testing that extreme programming tries to solve.
Show solution ↓Hide solution ↑
Solution
- Tests come after development, if at all, so near the deadline there is no time left for them.
- Fixing a bug adds new code, which can bring new bugs, and without a ready set of tests these new bugs go unnoticed.
- Many feature freezes and branches (freeze, test that version, fix, branch again) make the process complex and slow.
XP answers all three by writing the tests first: they exist before the code, they are re-run after every change, and they always describe the current version.
Exercise 2
What is refactoring, and what are its three steps? Why is the last step necessary?
Show solution ↓Hide solution ↑
Solution
Refactoring means making the code cleaner without changing what it does. Its three steps are: freeze the features, refactor, then run a regression test.
The regression test is necessary because refactoring is only correct if behaviour is unchanged, and the only way to show that is to re-run all the existing tests. If they all still pass, the cleanup changed the structure but not the behaviour; if any break, the refactoring introduced a bug.
Exercise 3
Using the TDD state diagram, name the two states with their conditions, and list every transition with its trigger.
Show solution ↓Hide solution ↑
Solution
States:
- GREEN [all tests pass]
- RED [1+ tests fail]
Transitions:
| From | Trigger [condition] | To |
|---|
| start | (enter) | GREEN |
| GREEN | write a failing test | RED |
| RED | fix the functional code [0 tests fail] | GREEN |
| GREEN | refactor [no broken tests] | GREEN |
| GREEN | refactor [tests broken] | RED |
| GREEN | no more tests | end |
Exercise 4
A developer is in GREEN, refactors the code, and two tests break. Which state are they in now, what must they do next, and what may they not do?
Show solution ↓Hide solution ↑
Solution
The transition is refactor [tests broken], so the developer is now in RED [1+ tests fail].
In RED there is only one allowed transition: fix the functional code until 0 tests fail, which returns them to GREEN. They may not write a new failing test or end the cycle while in RED: new tests and stopping are only possible from GREEN.
Exercise 5
In step 1 of the Fibonacci example, the test assertEquals(0, fib(0)) is correct, yet it fails. Why? And at step 5, assertEquals(1, fib(2)) passes immediately. Why, and what did the professor do next?
Show solution ↓Hide solution ↑
Solution
Step 1 fails because the fib function does not exist yet. In TDD the test is written before the code, so the first run of a new test is expected to be red; the failure is not caused by a wrong test.
Step 5 passes immediately because the step-4 code (if (n < 1) return 0; return 1;) returns 1 for every n≥1, and fib(2)=1. The new test added no new behaviour. The professor noticed the test methods were starting to repeat, so he refactored the test code into a two-dimensional array of {n, expected} pairs checked in a loop.
Exercise 6
Classify each way of reaching green as fake it, obvious implementation or triangulation:
- The only test is
assertEquals(5, max(5, 3)), and the code is return 5;.
- The test is
assertEquals(12, area(3, 4)) for a rectangle, and the code is return w * h;.
- After tests
fib(1) == 1 and fib(3) == 2 force it, the code becomes if (n < 2) return n; return fib(n-1) + fib(n-2);.
Show solution ↓Hide solution ↑
Solution
- Fake it: a hard-coded constant that passes the single test; it will be improved when a second test (for example
max(2, 9) == 9) fails.
- Obvious implementation: the real code is simple and clear, so the developer writes it directly.
- Triangulation: the code was generalized only once two or more examples could not be satisfied by the simpler version.
Exercise 7
After step 7 of the Fibonacci example, the developer adds {4, 3} and {5, 5} to the test table. What happens, and what does it suggest about the cycle?
Show solution ↓Hide solution ↑
Solution
Check with the general code: fib(4)=fib(3)+fib(2)=2+1=3 ✓ and fib(5)=fib(4)+fib(3)=3+2=5 ✓. Both new tests pass immediately: the developer stays in GREEN.
New tests that cannot fail add confidence but no new behaviour. When every test you can think of passes straight away and writing more feels pointless, fear has turned into boredom, and the cycle can take the no more tests → end transition.
Exercise 8
(Explain a process.) Explain the red-green-refactor cycle in test-driven development.
Show solution ↓Hide solution ↑
Solution
How to write the answer: name the starting state, describe each transition in order with the evidence that allows it, include the feedback loop (refactoring can send you back to red), and say when the process stops.
The cycle begins in GREEN, meaning all existing tests pass. The developer writes the smallest useful new test, and that test should fail for the intended reason. This creates the RED state and confirms that the test can detect the missing behaviour.
The developer then writes the minimum production code needed to make the new test and all earlier tests pass, which returns the system to GREEN. From green, the developer may add another failing test or refactor the implementation. Refactoring improves the structure without intentionally changing observable behaviour, so the complete regression suite must still pass; if refactoring breaks a test, the code returns to RED and must be repaired before new functionality is added. The cycle repeats until the required behaviour is covered and the code is cleanly structured, when fear has turned into boredom. Passing unit tests still does not replace acceptance testing or prove that the customer will accept the product.