pytest更贴合Python开发者直觉:无需继承TestCase、直接用test_函数和原生assert,失败时显示上下文;支持parametrize参数化、fixture依赖注入与多作用域管理,兼容unittest旧用例。

pytest 不是“比 unittest 更好用”,而是对绝大多数新项目、中小团队和日常开发来说,pytest 的默认行为更贴合 Python 开发者的直觉,省去了大量样板代码和隐式约束。
写测试函数不用继承 TestCase 类
unittest 要求每个测试必须包装在类里,且该类必须继承 unittest.TestCase;哪怕你只测一个函数,也得写三行“仪式感”代码。而 pytest 直接识别任意以 test_ 开头的普通函数——没有类、没有 self、没有强制结构。
常见错误现象:把 unittest.TestCase 忘了继承,或者误在函数里用了 self.assertEqual 却没定义 self,报 AttributeError: 'function' object has no attribute 'assertEqual'。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 新测试文件直接写
def test_add(): assert add(1, 2) == 3,保存为test_math.py,运行pytest就能发现并执行 - 如果已有
unittest风格的类,pytest默认也能运行它,无需改写——这是兼容性红利,不是设计目标
断言失败时能直接看到变量值
unittest 的 self.assertEqual(a, b) 失败时只告诉你 “Expected X, got Y”,但不会显示 a 或 b 是怎么算出来的;而 pytest 把原生 assert 编译成带上下文的表达式树,失败时自动展开。
使用场景:调试数据处理逻辑、验证 API 返回字段、检查嵌套字典是否符合预期——这些地方你不想反复加 print() 或进 debugger。
示例对比:
assert response.json()['items'][0]['id'] == expected_id
失败时 pytest 会告诉你:AssertionError: assert 1001 == 1002,并附上 response.json() 的实际内容片段;而 unittest 的 self.assertEqual(response.json()['items'][0]['id'], expected_id) 只会输出两行原始值,不带路径上下文。
@pytest.mark.parametrize 比 unittest.subTest 更轻量
参数化是高频需求,但 unittest 的 subTest 必须嵌套在方法内、不支持跨方法复用、失败时不单独计数;pytest 的 @pytest.mark.parametrize 是装饰器,一行声明、多组输入、独立报告、支持组合和 ID 命名。
容易踩的坑:
- 在
unittest中误把subTest当成真正的子测试,结果某组失败后其余组被跳过(实际不会,但很多人这么以为) - 用
unittest手写循环 +self.assertEqual,导致失败堆栈指向循环体而非具体数据项 -
pytest的parametrize默认按位置绑定参数,若参数名和 fixture 名冲突(比如都叫client),会静默覆盖,建议显式用ids参数标注用例含义
fixture 机制天然支持依赖注入和作用域控制
unittest 的 setUp/tearDown 是扁平的、固定生命周期(每个 test 方法前后)、无法传参、不能复用;pytest 的 @pytest.fixture 是函数级抽象,可指定 scope='function'、'class'、'module' 甚至 'session',还能被其他 fixture 依赖——比如数据库 fixture 依赖配置 fixture,测试函数只声明需要数据库,不用管配置怎么来。
性能影响:scope='session' 的 fixture(如启动一次 mock server)在整个测试会话中只初始化一次,避免重复开销;而 unittest 没有等价机制,只能靠全局变量或模块级缓存,容易引发状态污染。
关键提醒:fixture 的执行顺序由依赖图决定,不是按代码顺序;如果你在 fixture 里做了副作用操作(比如清空数据库),又没正确声明 scope 或 autouse,很容易出现测试间干扰——这恰恰是迁移时最常被忽略的复杂点。


















