Test-Driven Development: A Practical Introduction

TDD’s red-green-refactor cycle sounds rigid on paper, but the underlying idea is simple: write a failing test that describes what you want, write the minimum code to pass it, then clean up. Here’s what it actually looks like in practice, not in theory.

Red: Write a Failing Test First

def test_empty_cart_total_is_zero():
    cart = ShoppingCart()
    assert cart.total() == 0

def test_adding_item_updates_total():
    cart = ShoppingCart()
    cart.add_item(name="Widget", price=9.99, quantity=2)
    assert cart.total() == 19.98

Running this immediately fails — ShoppingCart doesn’t exist yet. That’s expected and correct; the test defines the contract before the implementation exists.

Green: Write the Minimum Code to Pass

class ShoppingCart:
    def __init__(self):
        self._items = []

    def add_item(self, name, price, quantity):
        self._items.append({"name": name, "price": price, "quantity": quantity})

    def total(self):
        return sum(item["price"] * item["quantity"] for item in self._items)

Resist the urge to add features the tests don’t require yet — discounts, tax calculation, whatever. Write exactly enough to turn the test green.

Refactor: Clean Up with a Safety Net

With passing tests in place, you can now confidently rename variables, extract methods, or restructure the implementation — the tests immediately tell you if a “safe” refactor actually broke something.

Where TDD Actually Pays Off

  • Business logic with clear, well-defined rules (pricing, validation, state machines)
  • Bug fixes — write a test that reproduces the bug first, then fix it; the test then prevents regression forever
  • APIs and libraries other code will depend on, where the contract matters more than the implementation details

Where It’s Often More Trouble Than It’s Worth

Exploratory UI work, throwaway scripts, and situations where requirements are still actively changing shape — writing tests against a design you’re about to discard is wasted effort. TDD is a tool for well-understood problems, not a universal law.