Unit 14 · lesson

Build a Verification Suite

Core path: 35 minutes

A test suite is not useful because it produces a lot of green text.

It is useful because each test makes a specific claim about what the software is supposed to do.

In this lesson, the code starts wrong on purpose. Your tests define the intended behavior first, expose the defects, and then give you evidence that the repair works.

Start with the buggy module

Create text_utils.py:

def count_vowels(text):
    count = 0
    for char in text:
        if char in "aeiou":
            count += 1
    return count


def is_palindrome(text):
    return text == text[::-1]


def extract_domain(email):
    parts = email.split("@")
    return parts[1]

Create test_text_utils.py beside it.

Make the vowel contract explicit

Write tests for:

lowercase vowels
uppercase vowels
no vowels

Run:

python -m pytest

At least one test should fail against the starting implementation if your contract says uppercase vowels count.

Do not edit count_vowels() until you have seen the failure.

Then repair the implementation and rerun the suite.

Now you have before/after evidence.

Decide what palindrome means before coding the fix

Test:

radar
Radar
race car

There is no universal law saying your function must ignore capitalization or spaces.

That behavior belongs to the contract you choose.

Write the expected behavior first. Then normalize the input in a way that matches the stated contract.

The test is not there to bless whatever the code already happens to do.

Turn accidental failure into intentional behavior

The starting extract_domain() raises an IndexError when @ is missing.

That is an implementation accident.

Define the public behavior:

import pytest


def test_extract_domain_invalid():
    with pytest.raises(ValueError):
        extract_domain("userexample.com")

Run the test.

It should fail because the current function raises the wrong exception.

Now repair:

def extract_domain(email):
    if "@" not in email:
        raise ValueError("email must contain @")

    _, domain = email.split("@", 1)
    return domain

Run the suite again.

The test has now helped separate intended behavior from whatever exception happened to fall out of the old code.

Refactor while the tests watch

Choose one function and change its internal implementation without changing the contract.

Before the refactor:

python -m pytest

After the refactor:

python -m pytest

If the suite remains green, that is evidence that the tested behavior survived the structural change.

It is not proof that no bug exists anywhere. Tests only cover the claims they actually test.

Read one failure carefully

For one failing test, record:

test name:
expected value/behavior:
actual value/behavior:
relevant assertion:
implementation line you investigated:
repair:
result after rerun:

A failed test is not just a red light. It is a comparison between a claim and an observed result.

Success evidence

Submit or preserve:

  • the final passing suite;
  • at least one captured failing test that exposed a real defect;
  • the invalid-email test proving ValueError is the intended contract;
  • one refactor performed with the suite in place; and
  • one explanation of what the test suite does not prove.

Reliable code is not code that has never failed.

It is code whose important behavior can be checked repeatedly.