Test-Driven Development — TDD Workflow Explained (2026)
In this tutorial, you'll learn about Test. We cover key concepts, practical examples, and best practices.
Test-driven development (TDD) is a software development practice where you write the test before the implementation code, following a strict red-green-refactor cycle that ensures every line of code has a purpose.
What You'll Learn
You'll master the red-green-refactor cycle, work through a real TDD walkthrough building FizzBuzz, understand the London vs Detroit schools of TDD, learn when TDD works best, and recognize common adoption challenges teams face.
Why TDD Matters
TDD shifts testing from an afterthought to a design tool. Writing the test first forces you to think about the API before the implementation — producing cleaner interfaces, better separation of concerns, and near-zero bug escape rates. At DodaTech, Doda Browser's rendering engine was built test-first, catching layout calculation errors before they ever rendered.
TDD Learning Path
flowchart LR A[Unit Testing Guide] --> B[TDD Workflow] B --> C[Test Pyramid] B --> D[CI/CD Testing Pipeline] style B fill:#f90,color:#fff
Red-Green-Refactor Cycle
The TDD cycle has three phases:
- Red: Write a failing test
- Green: Write the minimum code to pass
- Refactor: Clean up while keeping tests green
Each cycle takes 1-5 minutes. This keeps you focused and ensures every line of code has a reason to exist.
Real TDD Walkthrough: FizzBuzz
Let's build FizzBuzz with TDD. The rules: return "Fizz" for multiples of 3, "Buzz" for multiples of 5, "FizzBuzz" for multiples of both, and the number as a string otherwise.
Step 1: Red — Write the First Failing Test
// fizzbuzz.test.js
const fizzbuzz = require('./fizzbuzz');
test('returns "Fizz" for multiples of 3', () => {
expect(fizzbuzz(3)).toBe('Fizz');
});
Run the test — it fails because fizzbuzz doesn't exist yet.
FAIL ./fizzbuzz.test.js
✕ returns "Fizz" for multiples of 3
ReferenceError: fizzbuzz is not defined
Step 2: Green — Write Minimum Code
// fizzbuzz.js
function fizzbuzz(n) {
return 'Fizz';
}
module.exports = fizzbuzz;
Run the test — it passes.
Step 3: Add Next Test
test('returns "Buzz" for multiples of 5', () => {
expect(fizzbuzz(5)).toBe('Buzz');
});
Red again. Now generalize:
function fizzbuzz(n) {
if (n % 3 === 0 && n % 5 === 0) return 'FizzBuzz';
if (n % 3 === 0) return 'Fizz';
if (n % 5 === 0) return 'Buzz';
return String(n);
}
Step 4: Complete the Suite
test('returns "FizzBuzz" for multiples of 3 and 5', () => {
expect(fizzbuzz(15)).toBe('FizzBuzz');
});
test('returns the number as string otherwise', () => {
expect(fizzbuzz(2)).toBe('2');
});
test('works for 0', () => {
expect(fizzbuzz(0)).toBe('FizzBuzz');
});
Expected output:
PASS ./fizzbuzz.test.js
✓ returns "Fizz" for multiples of 3 (1 ms)
✓ returns "Buzz" for multiples of 5 (1 ms)
✓ returns "FizzBuzz" for multiples of 3 and 5 (1 ms)
✓ returns the number as string otherwise (1 ms)
✓ works for 0 (1 ms)
London vs Detroit Schools
Two schools of thought in TDD:
| Aspect | London School | Detroit School |
|---|---|---|
| Focus | Behavior of a single object | State of the overall system |
| Mocks | Heavy mocking of all collaborators | Mocks only at external boundaries |
| Test style | Interaction-based | State-based |
| Suitable for | Complex object interactions | Data-centric applications |
Both are valid. The London school works well for service layers with many collaborators. The Detroit school suits data pipelines and pure computations.
When TDD Works Best
TDD excels in these scenarios:
- New development: Greenfield projects where the API is still being designed
- Bug fixes: Write a test that reproduces the bug, then fix until it passes
- Complex logic: Algorithms, validation rules, and business logic benefit from test-first thinking
- Refactoring: A test suite gives you the safety net to restructure code confidently
Baby Steps
Take tiny steps when the problem is hard:
// Baby step: start with the simplest case
test('returns "1" when given 1', () => {
expect(fizzbuzz(1)).toBe('1');
});
// Then add complexity one test at a time
test('returns "2" when given 2', () => {
expect(fizzbuzz(2)).toBe('2');
});
If a test stays red for more than 5 minutes, you're taking too big a step. Revert and take a smaller one.
Common Adoption Challenges
1. "It Slows Me Down"
TDD feels slower at first. After 2-3 weeks of practice, most developers are faster with TDD than without — fewer debugging sessions, less rework.
2. "We Don't Have Time to Write Tests"
Skipping tests today creates "bug debt" that costs more later. A bug in production costs 10x more to fix than during development.
3. "What Should I Test First?"
Start with the happy path — the simplest case that succeeds. Then add edge cases, error conditions, and boundary values.
4. "My Tests Are Brittle"
Brittle tests usually test implementation details. Focus on behavior, not internal state.
5. "TDD Doesn't Work for Legacy Code"
True — TDD is hard to apply to untested legacy code. Use characterization tests instead: write tests that capture current behavior, then refactor with safety.
Practice Questions
1. What are the three phases of the TDD cycle? Red (write failing test), Green (write minimum code to pass), Refactor (clean up while keeping tests green).
2. What is the difference between London and Detroit TDD? London school mocks all collaborators and tests interactions. Detroit school tests state with minimal mocking.
3. What do you do if a test stays red for more than 5 minutes? Revert and take a smaller step. The test is too big.
4. How do you apply TDD to legacy code? Write characterization tests that capture current behavior before refactoring.
5. Challenge: Build a shopping cart using TDD. Start with adding items, then calculate total, apply discounts, remove items, and handle empty cart.
Mini Project: TDD Calculator
// calculator.js
function add(a, b) { return a + b; }
function subtract(a, b) { return a - b; }
function multiply(a, b) { return a * b; }
function divide(a, b) {
if (b === 0) throw new Error('Division by zero');
return a / b;
}
module.exports = { add, subtract, multiply, divide };
Build this with TDD — one operation at a time, red-green-refactor for each.
FAQ
What's Next
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro