测试基础:让机器替你验证代码
从一个会算错的 is_even 函数开始,写出你人生第一组自动化测试:断言、测试用例三要素、回归测试的真正含义,附真实 AssertionError 调试实录与梯度练习。
前置知识
- 会用 Python 跑脚本:能运行
python 文件名.py并看懂输出(第二门语言体验 级别即可); - 不需要任何测试框架,本文只用 Python 自带的
assert语句。
学习目标
读完本文你将能够:
- 说出「测试」在工程里的准确定义:用代码描述期望行为,让机器替你反复验证;
- 写出第一组自动化测试,并亲眼看到它抓住一个真实 bug;
- 用「输入、执行、断言」三要素拆解任何一个测试用例;
- 解释为什么「修好 bug 后补一条测试」是程序员最重要的自保习惯;
- 识别「永远通过的假测试」这类新手陷阱。
预计 45 到 60 分钟。
1. 你现在要解决什么问题
你写了一个判断偶数的函数,交给同事用。第二天有人反馈:「输入 -2 它说是奇数」。你打开代码看了看,改好了,发回去。三天后又有反馈:「2000 年是不是闰年你们算错了」——另一个函数的另一个边界。
问题不在你改得慢,而在验证方式:每次都是「人手点一遍」。改一处、点一遍,改十处、点十遍,漏点一遍就上线一个 bug。更糟的是,上个月修好的问题,这个月改别处时又悄悄弄坏,没有任何东西提醒你。
你需要的是:把「应该怎样」写成代码,让机器在你每次改完代码后自动检查一遍。这就是自动化测试。
2. 先不要看解释,先试试看
新建目录 test-lab,放入被测函数文件 even.py:
def is_even(n):
return n % 2 == 1 # 注意:这里有一个真 bug,先别改,往下走再写一个验证脚本 verify_even.py:
from even import is_even
assert is_even(4), "4 应该是偶数"
assert is_even(0), "0 应该是偶数"
assert is_even(-2), "-2 应该是偶数"
assert not is_even(7), "7 不应该被判定为偶数"
print("全部通过")先预测:这个脚本能跑到最后打出「全部通过」吗?如果会报错,报在第几行、错误信息是什么?把预测写下来,然后运行:
python verify_even.py3. 最小可运行示例:测试抓住 bug 的现场
预期输出(真实文本):
Traceback (most recent call last):
File "verify_even.py", line 3, in <module>
assert is_even(4), "4 应该是偶数"
AssertionError: 4 应该是偶数第一条测试就挂了。恭喜——你的测试在你用户之前抓住了 bug:n % 2 == 1 判断的其实是「是不是奇数」。
现在修 bug(把 even.py 的判断改为 n % 2 == 0),重新运行:
全部通过四条验证全部变绿。这两次运行合起来,就是本文要教的一切的浓缩:把期望写成断言,机器替你发现「现实与期望不符」。
4. 发生了什么
assert 条件, 消息是 Python 原生语句:条件为真,什么都不发生,脚本继续走;条件为假,立刻抛出AssertionError并显示你的消息;- 断言遇错即停:第一条失败后,后面的测试根本没跑——这就是为什么你只看到一条报错(这个特性第 9 节会变成练习);
- 修完 bug 重跑,脚本安静地打印「全部通过」。测试的价值不只在于抓错,更在于「绿」给你的底气:这四个行为你随时可以证明,而不是靠记忆。
读报错三步同样适用:line 3 告诉你哪条测试失败,assert is_even(4) 告诉你哪个输入出的问题,4 应该是偶数 是你自己写给未来的自己的说明。好测试失败的瞬间,问题已经定位了一半。
5. 核心概念
测试用例三要素:任何一个测试都能拆成三段——
| 要素 | 本文示例 | 说明 |
|---|---|---|
| 准备输入 | 调用 is_even(4) | 给定一个具体场景 |
| 执行 | 函数真的跑一遍 | 触发被测行为 |
| 断言 | assert 结果为真 | 把「期望」写死在代码里 |
缺任何一环都不算测试。最常见的残缺是「有执行没断言」——代码跑了、没报错,就以为对了,实际上什么都没验证(第 7 节实录)。
回归测试:第 1 节那个「上个月修好的问题这个月又坏了」的场景,术语叫回归。自动化测试存在的最大理由就是防回归:测试写好一次,之后每次改动代码都可以全部重跑,机器不嫌烦。修 bug 的标准姿势因此变成两步:先写一条能复现 bug 的测试(此刻它应该变红),再修代码让它变绿,测试从此留下,永久守护这个边界。
好测试的三个标准:快(毫秒级,才敢频繁跑);可重复(不依赖网络、随机、日期——跑一万次结果一致);失败时信息充分(断言消息写「-2 应该是偶数」而不是「出错了」)。
测试金字塔(概念预告):单元测试(本文这种,测一个函数)量最大、跑最快;往上是对几个模块协作的集成测试;最顶上是对整个系统界面操作的端到端测试。比例大致是「多、中、少」,展开在 测试级别。
6. 修改实验
- 给
verify_even.py增加一行assert is_even(1000000), "大偶数也应通过",重跑——绿了?把你 even.py 改回 bug 版,验证测试确实会红,再修回来。亲手体验「测试在守护你」; - 把第一条断言的消息删掉,改回 bug 版运行,对比报错信息——体会「失败时信息充分」这条标准值多少钱;
- 新增一个断言
assert is_even(2.0), "2.0 是偶数",先预测(提示:2.0 % 2 == 0吗?),再运行。发现自己对函数行为了解不全,就是测试的目的达成。
7. 调试实录:永远通过的假测试
同事说他写了测试,全绿。代码如下:
def test_add():
result = 2 + 3
print("计算完成")这段「测试」永远通过——它只执行、不断言,print 不检查任何东西。没有断言的测试等于没有测试,甚至更糟:它给你虚假的安全感。修复:
def test_add():
result = 2 + 3
assert result == 5, "2 + 3 应该等于 5"第二个真实陷阱方向相反:改测试凑合代码。测试红了,第一反应应该是「代码错了还是测试错了」,两种情况都有可能,但必须查明原因再动笔。直接把期望值改成代码的实际输出让它变绿,等于把「验证」偷换成「记录现状」——红灯是测试唯一的价值,亲手拧灭它不如删掉测试。
第三个陷阱:把一堆断言塞进一个巨大测试函数。失败时只能看到第一个错,后面的全被跳过。按「一个测试函数验证一个行为」拆开,失败信息才清晰(这个组织工作就是 pytest 的用武之地)。
附注一个准确性细节:Python 解释器加 -O 参数运行时会跳过所有 assert,所以 assert 适合本文的教学与内部脚本;正式项目的测试用 pytest 等框架的断言机制,不受此影响。
8. 实际项目中的使用场景
- 代码仓库里通常有一个
tests/目录与被测代码平行,CI 流水线在每次提交时自动跑全部测试,红了就拦下合并——你以后在 GitHub 上看到的绿色对勾就是它; - 修任何 bug 之前先写复现测试,是团队里公认的靠谱信号;
- 重构(不改行为、只改结构)敢不敢大刀阔斧,完全取决于有没有一套可信的测试——这就是「重构的勇气是测试给的」。
9. 小练习
预测题:下面的脚本会打印什么?先写答案再运行:
assert 1 + 1 == 2, "第一条"
assert 2 * 2 == 5, "第二条"
assert 10 / 2 == 5, "第三条"
print("全对")修改题:给 verify_even.py 补上一组负奇数与零的断言,并让每个断言的消息都具体到「输入是什么、期望是什么」。
修 Bug 题:下面的「测试」永远通过,指出两处问题并重写:一处在函数内部,一处在调用方式上——
def test_is_even():
is_even(6)
print("done")
test_is_even()挑战题(不看提示自己写):新函数登场,leap.py 里是同事写的闰年判断:
def is_leap(year):
return year % 4 == 0先写一组断言暴露问题:至少覆盖 2024、2023、2000、1900 四个年份(其中有两个会打脸)。运行确认测试变红后,把函数修成完整规则(能被 4 整除且不能被 100 整除,或能被 400 整除),让测试变绿。最后回答:如果 1900 这个用例一开始就不写,bug 会在什么时候被谁发现?
10. 什么时候应该 / 不应该写测试
应该:任何有维护价值的逻辑(业务规则、工具函数、修过的 bug);团队项目的一切核心路径;你打算以后重构的代码(测试先行的安全网)。
不应该:一次性探索脚本、跑完就扔的数据处理;还没想清楚要什么的 spike 原型(等方向定了再补);界面「好看不好看」这类主观判断(视觉回归是另一个专门话题)。
11. 与之前和之后的知识的关系
- 往前:你只需要会跑 Python——测试入门的门槛就这么低;
- 往后:测试概念与原则 把「缺陷、验证、确认」这些术语与本文的实践对上号;测试级别 展开金字塔;等价类划分 回答「该挑哪些输入当用例」(本文的 2000 与 1900 就是边界值思路的第一次亮相);pytest 把今天手写的 assert 脚本升级成可自动发现、可分组、可出报告的正式用例。
12. 官方文档
- Python
assert语句(含 -O 行为说明):https://docs.python.org/zh-cn/3/reference/simple_stmts.html#the-assert-statement - 标准库 unittest(不装任何框架的正式测试方案):https://docs.python.org/zh-cn/3/library/unittest.html
- pytest 官方文档:https://docs.pytest.org/en/stable/
13. 自我检查
- 能不看示例默写「一个函数文件 + 一个验证脚本」并跑通红绿两个状态;
- 能用三要素拆解任何一条测试;
- 能解释「修 bug 先写复现测试」的完整流程与价值;
- 能识别无断言假测试、改测试凑代码、巨型测试函数三个陷阱。
本章总结
测试不是「点一遍看看」,而是把期望写成机器可执行的断言:三要素(输入、执行、断言)构成用例,回归价值靠「测试留下、每次重跑」兑现,可信度靠「失败信息充分」支撑。你今天用四行 assert 体验了测试的全部核心——之后的框架、级别、方法论,都是在这四行上长出来的。
下一步
进入 测试概念与原则,把实践背后的术语体系补齐。