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
ValueErroris 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.