学习目标:运行行为测试、读懂失败原因,独立实现筛选功能,并交付可复现的使用说明。
前置:能追踪第 07 课的一条 add 命令。预计 70 分钟,可分两次。
本课交付:通过验收的 practice/task.py,以及自己的项目数据流说明。
工具现在可用,但“这次跑通”只能证明这组输入没出错。我们需要重复检查正常输入、空数据、边界和错误输入,而不是每次凭记忆点一遍。
测试 = 准备输入 → 执行动作 → 比较实际与预期。预期来自需求,不是从实现代码里照抄一个答案。
在 starter-lab 目录:
py -3 -m unittest discover -s tests -v
预期看到 6 个测试方法通过,末尾为 OK。其中的子测试还覆盖多种非法值;6 不代表仅有 6 个输入。
测试包括:分组求和、空记录、校验边界、非法保存不改旧文件、坏 JSON、在全新临时目录调用 CLI 并跨进程读回。临时目录由测试创建和清理,不使用你的 my-records.json。
self.assertEqual(
summarize([]),
{"count": 0, "total_minutes": 0, "by_subject": {}},
)
左边是实际调用,右边是根据“没有记录”的需求写出的预期。失败时应该先看两边具体差异,再定位实现,不能为了变绿就把预期改成错误结果。
测试代码放在测试类中,因此有 self。此时你可以先把它理解为 unittest 提供的一组断言方法;不要求在本课先精通完整面向对象体系。
打开 practice/task.py。它有一个待完成函数:
def select_records(records, minimum):
"""返回 minutes >= minimum 的记录,不修改输入;minimum 为 0–240 的整数。"""
raise NotImplementedError("请按第 08 课完成筛选与 minimum 参数校验")
输入 records 假定已经通过第 06 课校验;你只需检查 minimum,然后筛选。规定:保留输入顺序,不修改原列表,阈值包含等于;minimum 允许 0,因为“0 表示不过滤”有意义;仍拒绝 True、数字字符串和小数。
| 输入情形 | 预期行为 |
|---|---|
| 25、15、20;阈值 20 | 返回第 1、3 条,顺序不变 |
| 阈值 0 | 返回全部记录 |
| 阈值 240 | 样例没有记录满足,返回 [] |
| 空列表 | 返回 [] |
| 阈值 -1、241、True、"20"、1.5 | 抛 ValueError |
运行:
py -3 practice/check.py
还没动手时应失败,原因是 NotImplementedError。这是明确标记的学生练习起点,不是课程工具坏了。
实现建议:第一步检查 minimum;第二步创建空列表;第三步遍历 records,满足条件就 append 到新列表;最后在循环外 return。先自己写,再逐项比对需求表。
完成后重复同一命令,预期 3 个测试方法通过。不要改 check.py 来放宽条件。
卡住时分级看提示:先回看 03 课 if + for,再看 06 课的整数校验,最后才打开 practice/reference.py。
参考实现可以单独验收:
py -3 practice/check.py --reference
该命令检查参考解,不会检查或修改 task.py。参考解通过不代表你的答案通过。
为什么“返回新列表”很重要?如果边遍历边删除原列表的元素,索引会移动,容易跳过数据,也会使调用方手中的记录发生意外变化。先收集满足条件的元素,意图和边界更清楚。
本题只要求筛选动作不修改输入,并不要求深复制每个字典。返回的新列表与原列表仍可引用相同记录;需要完全隔离后续修改时,应另行约定复制行为。
将自己的条件临时从 >= 改为 >,再运行验收。包含 20 分钟的测试应失败。恢复后通过,说明测试确实能拦住这个边界错误。
若运行显示 0 tests,不算通过:检查当前目录、命令和 tests 文件夹。若出现 import 错误,先确认你没有把文件命名为 json.py、argparse.py 等标准库同名文件。
practice/check.py 通过。建议评分:行为与边界 40%,可复现运行 20%,测试与错误定位 20%,数据流解释 20%。代码短不自动加分,能够解释才算完成。
你已经亲手处理过数据、规则、文件、入口和测试。去读《代码课堂》01 时,把 main、summarize、load_records、save_records 映射成入口、业务、读取、保存;02 课只是把“终端参数”换成“HTTP 请求”。
沿 01 → 02 → 04 → 05 → 08 → 12 的 Web 主线继续。每次换教材仍用同一个问题:这次操作从哪里进来,数据在哪里变,失败在哪里返回,我怎样验证它?