单元测试是保障代码质量的底线——它让你在修改代码后能快速验证核心逻辑是否仍然正确。Python 标准库自带 unittest,而社区首选 pytest 则用更简洁的写法实现了更强的功能。这一节就讲清楚两者的基本用法和实际中怎么选。
unittest:标准库中的测试框架
unittest 的灵感来自 Java 的 JUnit,采用面向对象的方式组织测试。
基本结构
- 继承
unittest.TestCase - 测试方法名必须以
test_开头 - 通过
assertEqual、assertTrue等方法做断言 setUp/tearDown在每个测试前后执行,用于准备和清理环境
让一个例子说话:
import unittest
def add(a, b):
return a + b
class TestAdd(unittest.TestCase):
def test_add_positive(self):
self.assertEqual(add(1, 2), 3)
def test_add_negative(self):
self.assertEqual(add(-1, -1), -2)
if __name__ == '__main__':
unittest.main()
运行:python test_add.py 或 python -m unittest test_add.py
优点:无需安装,Python 自带,适合对第三方库零依赖要求的场景。
局限:写法模板化,断言方法名冗长,测试发现和参数化支持较弱。
pytest:现代、简洁、强大的测试框架
pytest 是目前 Python 社区事实上的标准测试工具,用简单的函数和 assert 就能写测试,同时拥有强大的插件体系。
基本结构
- 测试文件和函数名都以
test_开头(或_test结尾) - 直接用内置的
assert表达式做断言,失败时会自动展示详细对比信息 - 提供
fixture机制管理前置条件和公共资源 - 支持参数化测试,一行装饰器覆盖多组用例
改写上面的例子:
# 无需继承类,无需导入 unittest
def test_add_positive():
assert add(1, 2) == 3
def test_add_negative():
assert add(-1, -1) == -2
运行:pytest test_add.py
核心实用特性
- 断言内省:当
assert add(1, 2) == 4失败时,pytest 会明确告诉你assert 3 == 4,而不像 unittest 只抛出AssertionError。 - fixture 管理:用
@pytest.fixture创建可复用的前置资源(数据库连接、临时文件、测试数据等)。
import pytest
@pytest.fixture
def numbers():
return [1, 2, 3]
def test_sum(numbers):
assert sum(numbers) == 6
- 参数化测试:轻松用多组数据测试同一逻辑。
@pytest.mark.parametrize("a,b,expected", [
(1, 2, 3),
(0, 0, 0),
(-1, 1, 0),
])
def test_add(a, b, expected):
assert add(a, b) == expected
- 丰富的插件:
pytest-cov统计覆盖率,pytest-mock简化 mock,pytest-django集成 Django 测试等。
实际中怎么选
- 从零开始的新项目,直接选 pytest。它的写法更自然,功能更多,效率更高,是当前行业主流。
- 如果接手的老项目已大量使用 unittest,或者运行环境不能安装第三方库,继续用 unittest 也没问题。两者可以在同一个项目中混用:pytest 可以直接运行 unittest 写的测试用例。
单元测试的基本原则(不论用哪个框架)
- 测试粒度要小:一个测试只验证一个行为。
- 隔离性:测试之间不互相依赖,不依赖执行顺序。
- 可重复:每次运行结果一致,不依赖外部状态(网络、数据库等,必要时通过 mock 或 fixture 控制)。
- 命名有意义:测试函数名描述清楚“测了什么、在什么条件下、期望什么结果”,例如
test_add_with_two_negatives_returns_negative。
掌握了这些,你就可以在日常开发中建立起“改代码 → 跑测试 → 看绿灯”的安全感循环。