12 / 90

Day 12: Tests and repeatable programs

Write assertions that check observable behavior, build disposable fixtures in temporary directories, and triage a failing test as a code bug, a test bug, or an environment fault before editing anything. Retrieval and lower-new-content review are part of the 480-minute ledger.

Self-Directed Learning & Python 480 active minutes (8 hours focused study)
01 120 min

Test assertions that document behavior

Assistance allowed only after prediction and observed evidence are recorded.

Prerequisites

Complete the previous activity; use the listed recovery link if blocked.

Learning Objective

Write a unittest.TestCase using assertEqual and assertRaises, run python -m unittest -v, and explain what the final OK line proves and does not prove.

Instructions

Each test_ method is one behavior claim; its name should read like documentation, for example test_empty_list_returns_zero. assertEqual(actual, expected) checks a value; with self.assertRaises(ExceptionType): proves that a call fails the way the contract says. There are two ways to run tests. python -m unittest -v in the project directory (the same as python -m unittest discover -v) discovers the modules named test_*.py in that directory. python test_counter.py runs only the tests defined in that one file, through the unittest.main() call at its bottom; unittest.main() does not discover sibling test_*.py modules.

Practice & Prediction Log

In an empty directory write counter.py containing count_non_empty(lines), which returns how many items contain non-whitespace text and raises TypeError when an item is not a str. In test_counter.py write a CounterTests class with four tests: an empty list returns 0; a whitespace-only line is ignored; mixed lines count only the non-empty ones; and a non-string item (for example None) raises TypeError, checked with assertRaises. Predict the four outcomes, then run python -m unittest -v in that directory. Expected run on Python 3.13/3.14 (tests run in alphabetical order; the timing varies; older Python versions may print a shorter name in the parentheses):
test_empty_list_returns_zero (test_counter.CounterTests.test_empty_list_returns_zero) ... ok
test_mixed_lines_count_only_non_empty (test_counter.CounterTests.test_mixed_lines_count_only_non_empty) ... ok
test_non_string_item_raises_type_error (test_counter.CounterTests.test_non_string_item_raises_type_error) ... ok
test_whitespace_only_line_is_ignored (test_counter.CounterTests.test_whitespace_only_line_is_ignored) ... ok
...
Ran 4 tests in 0.001s
OK

Independent Mastery Criterion

The run ends with Ran 4 tests ... OK, including one assertRaises test, and you state that OK means every assertion held for these inputs — not that the function is correct for all possible inputs.

Authoritative Source Resources & Provenance

linking PSF License Version 2

Python Standard Library: unittest

Author: Python Software Foundation | Original Source Link ↗

Relevant Section / Range: Classes and functions: Test cases (TestCase assert methods)

Verified Version & Date: Python 3.12 documentation; verified 2026-10-30

Selection Rationale:

Normative reference for unittest.TestCase, assertEqual, assertRaises, and python -m unittest output.

Source Prerequisites:

Safe Fallback (Offline Resilience):

unittest ships with the interpreter, so every example runs offline; help(unittest.TestCase) documents the assert methods.

Compared Candidate Sources & Selection Analysis (2)
pytest documentation ↗ compared_alternative [MIT]

Strong third-party runner, but it adds an installation step and its own conventions; unittest needs no environment change and is the baseline the rest of the curriculum assumes.

doctest — Test interactive Python examples ↗ compared_alternative [PSF License Version 2]

Docstring-embedded examples suit tiny pure functions; unittest was selected for the structured TestCase isolation the check suites need.

02 120 min

Testing file processing with temporary fixtures

Assistance allowed only after prediction and observed evidence are recorded.

Prerequisites

Complete the previous activity; use the listed recovery link if blocked.

Learning Objective

Build file fixtures inside tests with tempfile.TemporaryDirectory and Path.write_text, covering three behaviors of summarize_scores: valid content, a missing file, and invalid content.

Instructions

tempfile.TemporaryDirectory() creates a private directory that is removed automatically, so tests never touch real user files. Build every path a test depends on inside that directory — including the path that must not exist: Path(d) / "does-not-exist.txt" cannot collide with anything, whereas a bare "does-not-exist.txt" is resolved against the current working directory, and a same-named file there would make test_missing_file fail. This activity uses the strict contract: summarize_scores(path) returns {name: highest score}, ignores blank lines, raises FileNotFoundError for a missing file and ValueError for a malformed score row. If you test Day 10's tolerant contract instead, assert its missing flag and skipped list — pick one contract and test it consistently.

Practice & Prediction Log

Write scores.py so that this suite (test_scores.py, in the same directory) passes:
# file: test_scores.py
import tempfile
import unittest
from pathlib import Path

from scores import summarize_scores


class SummarizeTests(unittest.TestCase):
def test_valid_file(self):
with tempfile.TemporaryDirectory() as d:
p = Path(d) / "scores.txt"
p.write_text("ana,7\nbo,9\nana,8\n", encoding="utf-8")
self.assertEqual(summarize_scores(str(p)), {"ana": 8, "bo": 9})

def test_missing_file(self):
with tempfile.TemporaryDirectory() as d:
missing = Path(d) / "does-not-exist.txt"
with self.assertRaises(FileNotFoundError):
summarize_scores(str(missing))

def test_invalid_score(self):
with tempfile.TemporaryDirectory() as d:
p = Path(d) / "scores.txt"
p.write_text("ana,7\nbo,x9\n", encoding="utf-8")
with self.assertRaises(ValueError):
summarize_scores(str(p))
# end file
Predict, then run python -m unittest -v. Expected: Ran 3 tests ... OK. Then create a file named does-not-exist.txt in that directory and run again: the result must still be Ran 3 tests ... OK, because the missing path lives in its own fresh temporary directory.

Independent Mastery Criterion

The suite is self-contained and passes in a clean run; fixtures are disposable; and each expected value is visible in a test name or assertion rather than implied.

Authoritative Source Resources & Provenance

linking PSF License Version 2

Python Standard Library: unittest

Author: Python Software Foundation | Original Source Link ↗

Relevant Section / Range: Classes and functions: Test cases (TestCase assert methods)

Verified Version & Date: Python 3.12 documentation; verified 2026-10-30

Selection Rationale:

Normative reference for the three-behavior fixture test suite.

Source Prerequisites:

Safe Fallback (Offline Resilience):

The fixture suite runs entirely on the local interpreter and temporary directories.

Compared Candidate Sources & Selection Analysis (2)
pytest documentation ↗ compared_alternative [MIT]

pytest tmp_path fixtures are elegant, but tempfile plus unittest keeps the zero-dependency guarantee for this block.

doctest — Test interactive Python examples ↗ compared_alternative [PSF License Version 2]

Docstring-embedded examples suit tiny pure functions; unittest was selected for the structured TestCase isolation the check suites need.

linking PSF License Version 2

Python Standard Library: tempfile

Author: Python Software Foundation | Original Source Link ↗

Relevant Section / Range: TemporaryDirectory for disposable on-disk fixtures

Verified Version & Date: Python 3.12 documentation; verified 2026-10-30

Selection Rationale:

Normative reference for creating and auto-removing fixture files inside tests.

Source Prerequisites:

Safe Fallback (Offline Resilience):

TemporaryDirectory is part of the installed standard library and needs no page access.

Compared Candidate Sources & Selection Analysis (2)
pytest tmp_path fixture ↗ compared_alternative [MIT]

Equivalent capability scoped to the pytest runner; deferred until the curriculum adopts pytest.

io — Core tools for working with streams ↗ compared_alternative [PSF License Version 2]

Lower-level file-object interface without automatic cleanup semantics; tempfile was selected for the safe scratch-file pattern the drills require.

03 120 min

Diagnose a failing test before editing

Assistance allowed only after prediction and observed evidence are recorded.

Prerequisites

Complete the previous activity; use the listed recovery link if blocked.

Learning Objective

Given total_score plus one failing test reporting AssertionError: 20 != 30, decide whether the code, the test, or the environment is wrong before editing anything.

Instructions

A FAIL block names the test, the failing assertion line, and the observed and expected values. The exception class is a clue, not a verdict. ModuleNotFoundError/ImportError or FileNotFoundError make the environment — which interpreter runs, which packages are installed, the working directory, a path — a strong first suspect, but confirm it with evidence such as sys.executable, Path.cwd() and Path(...).exists() before blaming it. An AssertionError means the code ran and produced a value the test did not expect; that usually points at the code or the test, but different configuration, data files or environment can also change a result, so the exception type alone never rules the environment out. Here the evidence is: the test writes its own three-row fixture inside a TemporaryDirectory (no outside file, no cwd dependency), the same failure reproduces under every interpreter you try, and observed 20 versus expected 30 is exactly one 10-point row missing. total_score calls f.readlines()[1:] to skip a header row that the fixture does not have, so it drops the first real row. The test expectation is correct — the file has no header — so the minimal fix removes the slice; it does not edit the test.

Practice & Prediction Log

Put these two files in an empty directory:
# file: total.py
def total_score(path):
"""Return the sum of the scores in rows like "a,10" (no header row)."""
total = 0
with open(path, encoding="utf-8") as f:
lines = f.readlines()[1:]
for line in lines:
name, _, raw = line.strip().rpartition(",")
total += int(raw)
return total
# end file
# file: test_total.py
import tempfile
import unittest
from pathlib import Path

from total import total_score


class TotalTests(unittest.TestCase):
def test_three_rows(self):
with tempfile.TemporaryDirectory() as d:
p = Path(d) / "rows.txt"
p.write_text("a,10\nb,10\nc,10\n", encoding="utf-8")
self.assertEqual(total_score(str(p)), 30)
# end file
Predict the result, then run python -m unittest -v. It reports FAIL: test_three_rows ... AssertionError: 20 != 30. Before editing, record the diagnosis: symptom (one row dropped), evidence (the fixture is built inside the test; 30 - 20 = one row of 10), hypothesis (the [1:] slice assumes a header), classification (code bug, the test is authoritative), minimal fix (read all lines), then re-run for OK.

Independent Mastery Criterion

You classify the failure correctly as a code bug with evidence, justify why the test's expectation stands, and the re-run reports OK after the minimal fix.

Authoritative Source Resources & Provenance

linking PSF License Version 2

Python Standard Library: unittest

Author: Python Software Foundation | Original Source Link ↗

Relevant Section / Range: Classes and functions: Test cases (TestCase assert methods)

Verified Version & Date: Python 3.12 documentation; verified 2026-10-30

Selection Rationale:

Normative reference for reading unittest failure reports during the code-vs-test-vs-environment triage.

Source Prerequisites:

Safe Fallback (Offline Resilience):

The seeded bug and its failure output reproduce locally with the installed interpreter.

Compared Candidate Sources & Selection Analysis (2)
pytest documentation ↗ compared_alternative [MIT]

pytest produces richer diffs, but the triage skill is taught on the standard runner every learner already has.

doctest — Test interactive Python examples ↗ compared_alternative [PSF License Version 2]

Docstring-embedded examples suit tiny pure functions; unittest was selected for the structured TestCase isolation the check suites need.

04 120 min

Repeatable program capstone

Independent Mastery Check (no AI assistance).

Prerequisites

Complete the previous activity; use the listed recovery link if blocked.

Learning Objective

In a fresh temporary directory assemble a repeatable program: scores.py implementing summarize_scores, test_scores.py covering the three fixtures, and requirements.txt; then run python -m unittest -v and show every test passing.

Instructions

Closed book. requirements.txt documents dependencies; when the program uses only the standard library, a comment line such as # stdlib only, no third-party dependencies is a valid requirements file. Run the suite with the interpreter you choose and record it. If the run reports that no tests ran (Ran 0 tests), check the test_*.py naming convention and the directory you ran discovery from.

Practice & Prediction Log

Expected result: in a fresh directory containing only scores.py, test_scores.py and requirements.txt, python -m unittest -v ends with Ran 3 tests ... OK (valid file, missing file, invalid score). The command line python scores.py FILE prints the summary dictionary and exits 0 on success; for a missing file or a malformed row it prints one error line to stderr and exits 1 instead of showing a traceback, so only an unexpected error ends in a traceback. Your log records prediction, observed output, and the interpreter used.

Independent Mastery Criterion

Unaided, the clean-directory run reports OK for all three tests, requirements.txt truthfully lists the dependencies, and your written recovery plan names where you would go if the suite failed.

Authoritative Source Resources & Provenance

linking PSF License Version 2

Python Standard Library: unittest

Author: Python Software Foundation | Original Source Link ↗

Relevant Section / Range: Classes and functions: Test cases (TestCase assert methods)

Verified Version & Date: Python 3.12 documentation; verified 2026-10-30

Selection Rationale:

Normative reference for the discovery convention the capstone depends on.

Source Prerequisites:

Safe Fallback (Offline Resilience):

The capstone is a clean-directory local run with no online dependency.

Compared Candidate Sources & Selection Analysis (2)
pytest documentation ↗ compared_alternative [MIT]

Kept as a future alternative runner; the capstone must pass with zero third-party installs.

doctest — Test interactive Python examples ↗ compared_alternative [PSF License Version 2]

Docstring-embedded examples suit tiny pure functions; unittest was selected for the structured TestCase isolation the check suites need.

linking PSF License Version 2

The Python Tutorial: Virtual Environments and Packages

Author: Python Software Foundation | Original Source Link ↗

Relevant Section / Range: Section 12 (requirements files and reproducible environments)

Verified Version & Date: Python 3.12 documentation; verified 2026-10-30

Selection Rationale:

Reference for the requirements.txt the capstone assembles to make the program repeatable.

Source Prerequisites:

Safe Fallback (Offline Resilience):

The capstone requirements file only needs the standard library, so no install is required.

Compared Candidate Sources & Selection Analysis (2)
pip documentation ↗ compared_alternative [MIT]

Reference for requirements syntax when the learner later adds real third-party dependencies.

virtualenv documentation ↗ compared_alternative [MIT License]

Mature third-party environment tool with overlapping behavior; the stdlib venv tutorial was selected to avoid an extra dependency for first-time setup.