Skip to main content

Test-Driven Development: Write the Test First, Then the Code

Advanced25 min5 exercises90 XP
0/5 exercises

Imagine you're drawing a picture. Most people sketch the outline first, then fill in the colors. You wouldn't start by painting random patches of blue and hope a sky appears.

Test-Driven Development (TDD) works the same way. Instead of writing code first and testing later, you write the test first, the "outline" of what your code should do, and then write just enough code to make that test pass.

It sounds backward, but TDD is used by developers at companies like Thoughtbot, Pivotal, and many open-source projects. In this tutorial, you'll learn the TDD workflow step by step and practice it yourself.

What Is the Red-Green-Refactor Cycle?

TDD follows a simple three-step cycle, repeated over and over:

  • RED, Write a test that fails (because the code doesn't exist yet)
  • GREEN, Write the simplest code that makes the test pass
  • REFACTOR, Clean up the code without changing what it does
  • Think of it like building with LEGO. You look at the picture on the box (RED: you know what you want). You snap the pieces together (GREEN: it works). Then you straighten everything up and remove extra pieces (REFACTOR: it's clean).

    The Red-Green-Refactor cycle in action
    Loading editor...

    The key discipline of TDD is that you never write production code without a failing test first. Every line of code exists because a test demanded it.

    How Do You Build FizzBuzz with TDD?

    FizzBuzz is a classic coding challenge: for numbers 1 to N, print "Fizz" if divisible by 3, "Buzz" if divisible by 5, "FizzBuzz" if divisible by both, or the number itself otherwise. Let's build it test by test.

    Cycle 1: Regular numbers. We start with the simplest case, a number that's not divisible by 3 or 5 should just return itself as a string.

    TDD Cycle 1: Regular numbers
    Loading editor...

    Cycle 2: Fizz. Now we add a test for multiples of 3. The current code will fail (RED), so we add just enough logic to pass.

    TDD Cycle 2: Adding Fizz
    Loading editor...

    Cycle 3: Buzz. Same pattern, write a failing test for multiples of 5, then make it pass.

    TDD Cycle 3: Adding Buzz
    Loading editor...

    Cycle 4: FizzBuzz. The final case, multiples of both 3 and 5. This check must come first!

    TDD Cycle 4: Complete FizzBuzz
    Loading editor...

    What Are the Benefits of Test-Driven Development?

    TDD gives you several superpowers:

  • Fewer bugs: you catch problems before they exist
  • Better design, writing tests first forces you to think about your function's interface before its implementation
  • Built-in documentation, your tests show exactly how the code should behave
  • Confidence to refactor, change anything, run the tests, know instantly if you broke something
  • Here's a subtle benefit: TDD prevents you from writing code you don't need. If there's no test for it, you don't write it. This keeps your codebase lean and focused.

    Without TDD (code-first)
    # Write a big function
    # Hope it works
    # Manually test a few cases
    # Ship it
    # Find bugs in production
    # Fix bugs under pressure
    With TDD (test-first)
    # Write a small test
    # Write minimal code to pass
    # Refactor if needed
    # Add next test
    # Repeat until done
    # Ship with confidence

    When Does TDD Work Best?

    TDD shines in certain situations:

  • Clear requirements: you know what the function should return for given inputs
  • Business logic, calculations, validations, data transformations
  • Bug fixes, write a test that reproduces the bug, then fix it
  • Library code, functions that others will depend on
  • TDD is harder (but still possible) when:

  • UI work, testing visual layouts is tricky
  • Exploring a new problem: you might not know the right interface yet
  • Integration code, connecting to real databases or APIs (use mocks!)
  • What Mistakes Should You Avoid with TDD?

    Beginners often make these TDD mistakes:

  • Writing too many tests at once: you should write ONE test, make it pass, then write the next
  • Writing too much code in the GREEN step, only write what's needed to pass the current test
  • Skipping the REFACTOR step, clean code is just as important as working code
  • Testing implementation instead of behavior, test WHAT the code does, not HOW it does it
  • Test behavior, not implementation
    Loading editor...

    Another common mistake is writing tests that are too tightly coupled. Each test should check one thing. If a test fails, you should know exactly what went wrong.

    Too many things in one test
    def test_everything():
        assert add(1, 2) == 3
        assert subtract(5, 3) == 2
        assert multiply(4, 3) == 12
        # If this fails, which function broke?
    One concern per test
    def test_add():
        assert add(1, 2) == 3
    
    def test_subtract():
        assert subtract(5, 3) == 2
    
    def test_multiply():
        assert multiply(4, 3) == 12

    Practice Exercises

    Your First TDD Cycle
    Write Code

    Use TDD to build an is_palindrome function. Write the test first, then the function. Your function should return True if a string reads the same forwards and backwards (case-insensitive), and False otherwise. Test with at least 3 cases: a palindrome, a non-palindrome, and an empty string. Print TDD palindrome test passed! at the end.

    Loading editor...
    TDD: Build a Password Validator
    Write Code

    Use TDD to build a validate_password function that returns a list of error messages. Rules: (1) must be at least 8 characters, (2) must contain at least one digit, (3) must contain at least one uppercase letter. Return an empty list if the password is valid. Write tests for: a valid password, a too-short password, one missing a digit, and one missing uppercase. Print each test result as PASS: <test_name>, then print Password validator complete!.

    Loading editor...
    Predict the TDD Process
    Predict Output

    This code simulates a TDD process with a test runner that reports RED or GREEN for each cycle. Read carefully and predict what gets printed.

    Loading editor...
    TDD Bug Fix: Reproduce Then Fix
    Write Code

    A user reported that count_vowels returns wrong results for uppercase strings. Use the TDD bug-fix approach: (1) write a test called test_uppercase_vowels that reproduces the bug with "HELLO" (should return 2), (2) fix the function, (3) add a test called test_mixed_case for "PyThOn" (should return 1), (4) make sure all tests pass. Print PASS: <test_name> for each test, then Bug fix verified!.

    Loading editor...
    TDD Challenge: Build a Word Counter
    Write Code

    Use TDD to build a word_count function that takes a string and returns a dictionary with each word (lowercased) as a key and its count as the value. Handle these cases: (1) normal sentence, (2) repeated words, (3) mixed case (treat "The" and "the" as the same), (4) empty string returns empty dict. Write a test for each case, run them all, and print PASS: <test_name> for each, then Word counter complete!.

    Loading editor...

    Related Tutorials

    Save your progress across devices

    Never lose your code, challenges, or XP. Sign up free, no password needed.

    Already have an account?