12 / 90

Day 12: 测试与可重复的程序

编写检查可观察行为的测试断言,在临时目录中构建一次性 fixture,并在改动任何东西之前把失败的测试分诊为代码缺陷、测试缺陷或环境故障。 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 分钟

记录行为的测试断言

只有在记录预测和观察到的证据之后才允许协助。

Prerequisites

完成上一个活动;如遇阻碍,使用列出的恢复链接。

Learning Objective

用 assertEqual 和 assertRaises 编写 unittest.TestCase,运行 python -m unittest -v,并解释结尾的 OK 行证明了什么、没证明什么。

Instructions

每个 test_ 方法是一条行为主张,名字应像文档一样可读,例如 test_empty_list_returns_zero。assertEqual(actual, expected) 检查值;with self.assertRaises(异常类型): 证明调用按契约所说的方式失败。运行测试有两种方式。在项目目录执行 python -m unittest -v(等同于 python -m unittest discover -v),会发现该目录中名为 test_*.py 的模块。python test_counter.py 只通过文件末尾的 unittest.main() 运行这一个文件里定义的测试;unittest.main() 不会发现旁边的 test_*.py 模块。

Practice & Prediction Log

在空目录中编写 counter.py,其中的 count_non_empty(lines) 返回含有非空白字符的元素个数,元素不是 str 时抛出 TypeError。在 test_counter.py 中编写含四个测试的 CounterTests 类:空列表返回 0;纯空白行被忽略;混合行只数非空行;非字符串元素(例如 None)抛出 TypeError,用 assertRaises 检查。先预测四个结果,再在该目录运行 python -m unittest -v。Python 3.13/3.14 上的预期运行(测试按字母顺序执行;耗时会变化;较旧的 Python 括号内的名字可能更短):
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

运行以 Ran 4 tests ... OK 结束且其中包含一个 assertRaises 测试,并能说明 OK 的含义是这些输入下每条断言都成立——而不是函数对所有可能输入都正确。

Authoritative Source Resources & Provenance

linking PSF License Version 2

Python Standard Library: unittest

作者 / 机构: Python Software Foundation | 原始出处链接 ↗

对应章节 / 时间戳: Classes and functions: Test cases (TestCase assert methods)

验证版本与日期: Python 3.12 documentation; verified 2026-10-30

入选理由:

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

来源前置条件:

安全回退方案(离线弹性):

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

对比候选资源与选型分析 (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 分钟

用临时 fixture 测试文件处理

只有在记录预测和观察到的证据之后才允许协助。

Prerequisites

完成上一个活动;如遇阻碍,使用列出的恢复链接。

Learning Objective

在测试内用 tempfile.TemporaryDirectory 和 Path.write_text 构建文件 fixture,覆盖 summarize_scores 的三种行为:有效内容、缺失文件、无效内容。

Instructions

tempfile.TemporaryDirectory() 创建会自动删除的私有目录,所以测试绝不碰真实用户文件。测试依赖的每条路径——包括必须不存在的那条——都要在该目录里构造:Path(d) / "does-not-exist.txt" 不会和任何东西冲突,而裸写的 "does-not-exist.txt" 会相对当前工作目录解析,那里若恰好有同名文件,test_missing_file 就会失败。本活动使用严格契约:summarize_scores(path) 返回 {name: 最高分},忽略空行,文件缺失时抛 FileNotFoundError,分数行无效时抛 ValueError。若你改为测试 Day 10 的容错契约,则断言其 missing 标志和 skipped 列表——选定一种契约并一致地测试它。

Practice & Prediction Log

编写 scores.py,使下面这个测试套件(同一目录下的 test_scores.py)通过:
# 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
先预测,再运行 python -m unittest -v。预期:Ran 3 tests ... OK。然后在该目录创建一个名为 does-not-exist.txt 的文件并再次运行:结果必须仍是 Ran 3 tests ... OK,因为缺失路径位于它自己的全新临时目录中。

Independent Mastery Criterion

测试套件自包含且在干净运行中通过;fixture 用完即弃;每个预期值都在测试名或断言中可见,而不是靠暗示。

Authoritative Source Resources & Provenance

linking PSF License Version 2

Python Standard Library: unittest

作者 / 机构: Python Software Foundation | 原始出处链接 ↗

对应章节 / 时间戳: Classes and functions: Test cases (TestCase assert methods)

验证版本与日期: Python 3.12 documentation; verified 2026-10-30

入选理由:

Normative reference for the three-behavior fixture test suite.

来源前置条件:

安全回退方案(离线弹性):

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

对比候选资源与选型分析 (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

作者 / 机构: Python Software Foundation | 原始出处链接 ↗

对应章节 / 时间戳: TemporaryDirectory for disposable on-disk fixtures

验证版本与日期: Python 3.12 documentation; verified 2026-10-30

入选理由:

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

来源前置条件:

安全回退方案(离线弹性):

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

对比候选资源与选型分析 (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 分钟

先诊断失败的测试再动手

只有在记录预测和观察到的证据之后才允许协助。

Prerequisites

完成上一个活动;如遇阻碍,使用列出的恢复链接。

Learning Objective

拿到 total_score 和一个报 AssertionError: 20 != 30 的失败测试,在改动任何东西之前判定是代码错、测试错还是环境错。

Instructions

FAIL 块会给出测试名、失败的断言行以及观察值与期望值。异常类别是线索,不是判决。ModuleNotFoundError/ImportError 或 FileNotFoundError 会让环境——运行的是哪个解释器、装了哪些包、工作目录、某条路径——成为首要嫌疑,但在归咎之前要用 sys.executable、Path.cwd()、Path(...).exists() 等证据确认。AssertionError 表示代码运行了,却产生了测试未预期的值;这通常指向代码或测试,但配置、数据文件或环境的差异也可能改变结果,所以仅凭异常类型永远不能排除环境。这里的证据是:测试在 TemporaryDirectory 中自己写入三行 fixture(没有外部文件,也不依赖 cwd),在你试过的每个解释器下都复现同样的失败,而观察 20 与期望 30 恰好相差一行 10 分。total_score 调用 f.readlines()[1:] 想跳过 fixture 里并不存在的表头,于是丢掉了第一行真实数据。测试期望是对的——文件没有表头——所以最小修复是删掉切片,而不是改测试。

Practice & Prediction Log

把下面两个文件放进空目录:
# 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
先预测结果,再运行 python -m unittest -v。它报告 FAIL: test_three_rows ... AssertionError: 20 != 30。改动之前先记录诊断:症状(丢了一行)、证据(fixture 在测试内构造;30 - 20 = 一行 10 分)、假设([1:] 切片假设有表头)、分类(代码缺陷,测试为权威)、最小修复(读取所有行),然后重跑看到 OK。

Independent Mastery Criterion

你凭证据把失败正确归类为代码缺陷,论证测试的期望为何成立,且最小修复后重跑报告 OK。

Authoritative Source Resources & Provenance

linking PSF License Version 2

Python Standard Library: unittest

作者 / 机构: Python Software Foundation | 原始出处链接 ↗

对应章节 / 时间戳: Classes and functions: Test cases (TestCase assert methods)

验证版本与日期: Python 3.12 documentation; verified 2026-10-30

入选理由:

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

来源前置条件:

安全回退方案(离线弹性):

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

对比候选资源与选型分析 (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 分钟

可重复程序顶点任务

独立掌握检查(不使用 AI 协助)。

Prerequisites

完成上一个活动;如遇阻碍,使用列出的恢复链接。

Learning Objective

在全新临时目录中组装可重复程序:实现 summarize_scores 的 scores.py、覆盖三个 fixture 的 test_scores.py,以及 requirements.txt;然后运行 python -m unittest -v 并展示所有测试通过。

Instructions

闭卷。requirements.txt 记录依赖;当程序只用标准库时,一行注释如 # stdlib only, no third-party dependencies 就是合法的 requirements 文件。用你选定的解释器跑套件并记录下来。如果运行报告没有测试被执行(Ran 0 tests),检查 test_*.py 命名约定和运行发现命令的目录。

Practice & Prediction Log

预期结果:在只含 scores.py、test_scores.py 和 requirements.txt 的全新目录中,python -m unittest -v 以 Ran 3 tests ... OK 结束(有效文件、缺失文件、无效分数)。命令行 python scores.py FILE 成功时打印汇总字典并以 0 退出;文件缺失或行格式错误时,向 stderr 打印一行错误并以 1 退出,而不是显示 traceback,因此只有意外错误才会以 traceback 结束。日志记录预测、观察输出和所用解释器。

Independent Mastery Criterion

独立完成:干净目录运行中三个测试全部 OK;requirements.txt 如实列出依赖;书面恢复计划写明套件失败时去哪里查。

Authoritative Source Resources & Provenance

linking PSF License Version 2

Python Standard Library: unittest

作者 / 机构: Python Software Foundation | 原始出处链接 ↗

对应章节 / 时间戳: Classes and functions: Test cases (TestCase assert methods)

验证版本与日期: Python 3.12 documentation; verified 2026-10-30

入选理由:

Normative reference for the discovery convention the capstone depends on.

来源前置条件:

安全回退方案(离线弹性):

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

对比候选资源与选型分析 (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

作者 / 机构: Python Software Foundation | 原始出处链接 ↗

对应章节 / 时间戳: Section 12 (requirements files and reproducible environments)

验证版本与日期: Python 3.12 documentation; verified 2026-10-30

入选理由:

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

来源前置条件:

安全回退方案(离线弹性):

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

对比候选资源与选型分析 (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.