Python 单元测试最省事的做法就两个框架:标准库自带的 unittest 和第三方的 pytest。直接说结论:新手想零配置上手先用 unittest,团队追求更少样板、更强断言,pytest 更顺手。你写业务代码时大概率撞过这种尴尬——改了一处逻辑,主程序没报错,上线后另一个功能却被带崩。本文把两者定位、同一段逻辑的两种写法、以及怎么选一次讲清,并给出能直接运行的代码与失败处理思路。掌握它们,你改完代码后能立刻知道哪里被影响,而不是等上线才暴露问题。

先看结论
- Python 单元测试的两条主线是 unittest(内置、零依赖)和 pytest(第三方、写法更轻)。
- 没有外部依赖、只想快速验证一段函数,unittest 开箱即用,不需要 pip 安装。
- pytest 用普通函数加
assert就能写测试,参数化和报错信息都更直观。 - 选型看团队习惯和项目规模:个人脚本用 unittest 足够,协作项目 pytest 收益更高。
- 无论哪种,断言失败都要看报错里的「期望 vs 实际」,而不是只盯着红字退出码。
一、两种框架到底差在哪
先建立直觉:unittest 借鉴了 Java 的 JUnit,写法是「类 + 方法」,每个测试是 TestCase 里的一个以 test_ 开头的方法;pytest 更贴近 Python 风格,一个普通函数加断言就算一个测试用例。两者覆盖的测试类型完全一致,区别只在写法与生态,选型时不必担心能力缺口。
def greet(name: str) -> str:
# 被测试的目标函数:把姓名拼成问候语
return f"你好,{name}!"
这段逻辑本身很简单,但「简单」恰恰是单元测试最好的练习场:输入确定、输出确定,最容易判断测试写没写对。正式写测试前,建议先过一遍 Python3 教程 的函数与字符串两章,把 f-string 和返回值的概念理顺。
二、用 unittest 写第一个测试
unittest 的核心是继承 unittest.TestCase,测试方法名必须以 test 开头,否则不会被执行。这个 test 前缀不是装饰:pytest 和多数运行器都靠它自动收集用例,漏写前缀用例写了也不会跑,排查时容易误以为是框架问题。
import unittest
def greet(name: str) -> str:
return f"你好,{name}!"
class TestGreet(unittest.TestCase):
def test_greet_normal(self):
self.assertEqual(greet("小明"), "你好,小明!")
def test_greet_empty(self):
self.assertEqual(greet(""), "你好,!")
if __name__ == "__main__":
unittest.main()
上面这段做的是定义两个用例:正常名字、空字符串,分别断言返回值是否符合预期。在终端执行 python test_greet.py,预期输出(未在本机执行,依据 unittest 行为)是 Ran 2 tests in ... OK,表示两个测试用例都通过。

2.1 失败分支:断言写错怎么处理
如果把期望值手滑写成 "你好小明!"(少了逗号),测试会红。典型输出是:
AssertionError: '你好,小明!' != '你好小明!'
- 你好,小明!
? +
+ 你好小明!
这段输出告诉我们「期望」和「实际」差了一个逗号,问题在断言本身而不是被测函数。正确做法是回看需求确认分隔符,改回 "你好,小明!",而不是去改 greet 函数——很多人一看到红就先动业务逻辑,反而把对的代码改坏。字典与字符串的常用断言写法,可对照 Python 速查手册 的断言清单。
三、用 pytest 写同样的测试
pytest 不需要写类,普通函数加 assert 即可,失败时会把表达式两边的值直接展开。它把「函数即用例」做到了极致:不需要继承、不需要样板,一个 assert 就足够描述预期,这也是它在协作文件里更省心的原因。
def greet(name: str) -> str:
return f"你好,{name}!"
def test_greet_normal():
assert greet("小明") == "你好,小明!"
def test_greet_empty():
assert greet("") == "你好,!"
上面这段做的是用两个普通函数覆盖同样的用例。先 pip install pytest,再在目录里执行 pytest -q,预期输出(未在本机执行,依据 pytest 行为)是 2 passed,表示两个用例通过。想系统学断言与夹具,可参考 pytest 教程。
3.1 失败分支:少写逗号的处理
同样把期望写成 "你好小明!",pytest 的报错展示方式和 unittest 不同,它会把断言表达式两边的值直接展开,你一眼就能看出到底差了哪个字符。具体输出形如:
def test_greet_normal():
> assert greet("小明") == "你好小明!"
E assert '你好,小明!' == '你好小明!'
pytest 会把 greet("小明") 的真实返回值算出来摆在你面前,定位比 unittest 更省一步。修复方式一致:核对需求后修正断言字符串。想了解内置测试模块的差异,可看 unittest 模块。

四、项目里到底怎么选
小型脚本、一次性工具,用 unittest 零安装最稳;有多人协作、需要参数化、想接持续集成,pytest 的生态(夹具、插件、覆盖率)更省力。两者写的测试用例可以长期共存,不是二选一砍掉另一个。
一个常见误区是「测试越多越好」。真正有价值的是覆盖了边界输入(空值、超长、特殊字符)的用例,而不是把同一个正常路径复制十遍。
迁移也没有想象中麻烦:老项目里 unittest 的 TestCase 可以原样保留,新用例直接用 pytest 风格写,pytest 会自动识别并运行它们,你不必一次性重写历史测试。等团队习惯成型,再逐步把旧的搬过来也不迟。接入持续集成时,pytest 的退出码和覆盖率插件能直接接进流水线,这也是它在协作项目里更受欢迎的现实原因。
总结
要点带走:
- Python 单元测试有 unittest 与 pytest 两条主线,前者内置零依赖,后者写法更轻量;
- unittest 用类与方法组织,pytest 用普通函数加断言,两者断言失败都要看「期望 vs 实际」;
- 选型看依赖与协作规模,脚本用 unittest,团队项目用 pytest;
- 测试的价值在边界覆盖,不在用例数量堆砌;
- 看到红色先确认是断言写错还是逻辑写错,别一红就改业务代码。
下一步建议把手上一段最常被改的函数,先用 unittest 写一个正常用例跑通,再改成 pytest 版对比手感。
常见问题
Q:pytest 和 unittest 能混着用吗?
A:可以。pytest 能直接发现并运行 unittest 的 TestCase,老项目不必一次性迁移,新写的用例用 pytest 风格即可。
Q:测试文件要怎么命名?
A:unittest 约定以 test 开头的方法自动识别;pytest 默认收集 test_*.py 或 *_test.py 里的 test_ 函数,命名统一最省心。
Q:断言里用 assert 和 self.assertEqual 有区别吗?
A:功能上都等于比较,但 pytest 的 assert 失败时会自动展开表达式两边的值,定位更快;unittest 的 assertEqual 需要你点开错误信息才看得到细节。
Q:函数依赖数据库或网络,怎么写单元测试?
A:用 pytest 的夹具或 unittest 的 mock 把外部依赖替换成假对象,只验证你自己的逻辑,不真的去连库或发请求。
Q:覆盖率一定要到 100% 吗?
A:不必。先覆盖核心分支和边界输入,覆盖率只是参考指标,强行凑数会拖慢维护。
延伸学习
想继续深入,可以按这个顺序:
- 系统补 Python 基础,Python 零基础到高薪就业 覆盖了从语法到项目的完整链路;
- 想对比两者取舍,读一篇相关笔记 Pytest 怎么选;
- 想直接在线试跑片段,用 Python 在线运行 粘贴上面的代码即可验证。

TRAE-AI编程



