Skip to content

Lesson 04 – TDD and Unit Testing Basics

Test-Driven Development (TDD) emphasizes writing tests before implementing code. This lesson explains the Red-Green-Refactor cycle and demonstrates how small, focused unit tests improve software design.


1. What Is TDD?

TDD is an iterative workflow where you:

  1. Write a failing test – specify desired behaviour.
  2. Make the test pass – implement just enough code.
  3. Refactor – clean up while keeping tests green.

Repeating this loop encourages incremental development and continuous validation of your assumptions.


2. Unit Tests vs. Other Tests

  • Unit tests verify individual functions or methods in isolation.
  • Integration tests check that multiple components work together.
  • End-to-end tests simulate real user scenarios.

TDD focuses on unit tests, but the same discipline applies at higher levels.


3. Sample Workflow

Consider a simple function that applies a discount. Following TDD:

  1. Write a test using unittest or pytest that asserts the correct discounted price.
  2. Implement the function with type hints and docstrings.
  3. Refactor for clarity while keeping the test green.
import pytest

from shop.pricing import calculate_discount


def test_calculate_discount() -> None:
    """Verify that a valid discount is applied."""
    assert calculate_discount(100.0, 15.0) == 85.0

And the implementation:

# shop/pricing.py

def calculate_discount(price: float, discount_rate: float) -> float:
    """Calculate the discounted price based on the original price and discount rate.

    This method applies a percentage discount to the given price and returns
    the final amount. It does not handle negative prices or discount rates
    greater than 100%.

    Args:
        price (float): The original price of the item.
        discount_rate (float): The discount rate as a percentage (e.g., 15.0 for 15%).

    Returns:
        float: The discounted price.

    Raises:
        ValueError: If price is negative or discount_rate is not between 0 and 100.

    Examples:
        >>> calculate_discount(100.0, 15.0)
        85.0
    """
    if price < 0 or not 0 <= discount_rate <= 100:
        raise ValueError("Invalid input")
    return price * (1 - discount_rate / 100)

4. Benefits of TDD

  • Forces you to think about requirements before coding.
  • Provides a safety net for refactoring.
  • Results in a thorough regression suite.
  • Encourages modular, decoupled design.

5. Best Practices

  • Keep tests independent and repeatable.
  • Name tests clearly to document behaviour.
  • Use fixtures to set up complex objects.
  • Run the test suite frequently during development.

TDD takes discipline, but the rapid feedback cycle leads to more reliable code and fewer regressions.

Next, you'll analyze cyclomatic complexity to see how branching paths affect your testing strategy.


Next up: Lesson 05 – Cyclomatic Complexity and Unit Testing