Python 测试框架选型:unittest、pytest、nose 对比
做 SDK 验证,选对测试框架很重要。这就像盖房子选地基。地基选不好,后面全得返工。
Python 世界里,主流的测试框架就三个:unittest、pytest、nose。今天咱们把它们拉到一起,好好比一比。
1. unittest:官方正统,但有点重
unittest 是 Python 官方自带的测试框架。功能很全,但用起来有点重。我们项目初期就用的 unittest,后来发现 setup/teardown 写了一大堆,看着都烦。
举个例子,你想测一个 SDK 初始化函数。用 unittest 你得这么写:
import unittest
from my_sdk import Client
class TestClient(unittest.TestCase):
def setUp(self):
# 每个用例前都要初始化
self.client = Client(="test_key")
def test_connection(self):
result = self.client.connect()
self.assertTrue(result)
def tearDown(self):
self.client.close()
if __name__ == '__main__':
unittest.main()
每个测试类必须继承 unittest.TestCase。断言方法也得用 self.assertEqual 这种形式。说实话,有点啰嗦。
2. nose:曾经辉煌,现在停产
nose 是第三方框架,比 unittest 简洁不少。它能自动收集测试用例,不用你手动 import。我在职业生涯早期用过一阵子 nose,体验确实比 unittest 好。
但有个问题——nose 已经停止维护了。大家用的都是 nose2,功能跟 nose 差不多。但社区活跃度远不如 pytest。
3. pytest:现代测试框架的标杆
pytest 是我现在的主力框架。它灵活、强大、写法简洁。你想想看,同样测试刚才的 SDK 初始化,在 pytest 里怎么写:
# test_sdk.py
import pytest
from my_sdk import Client
@pytest.fixture
def client():
# 相当于 setUp,但更灵活
return Client(="test_key")
def test_connection(client):
result = client.connect()
assert result # 直接用 assert
不需要继承任何类。直接用 assert 做断言。fixture 比 setUp/tearDown 灵活得多。
核心对比:为什么我选 pytest
下面这张表,我建议你存下来。每次选型翻出来看看:
| 维度 | unittest | nose/nose2 | pytest |
|---|---|---|---|
| 社区活跃度 | 官方维护,但更新慢 | 基本停滞 | ★★★★★ 极活跃 |
| 用例收集 | 需手动组合 TestSuite | 自动收集 | 自动收集,规则清晰 |
| 断言写法 | self.assertXxx() | 支持 assert | 原生 assert + 增强提示 |
| 参数化 | 需子测试,语法复杂 | 有限支持 | @pytest.mark.parametrize |
| 插件生态 | 极少 | 有限 | 300+ 官方插件 |
| fixture 管理 | setUp/tearDown | 类似 unittest | 函数式 fixture,自动清理 |
pytest 的核心竞争力
既然选了 pytest,咱们就深入聊聊它到底好在哪。
自动发现测试用例
你不用手动指定跑哪些文件。pytest 会自动扫描当前目录,找到所有以 test_ 开头或 _test 结尾的 .py 文件。
嗯,这里要注意:自定义测试目录时,记得配 pytest.ini:
[pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
fixture 机制,甩开 unittest几条街
我个人习惯把 fixture 理解成「依赖注入」。举个例子:
@pytest.fixture
def db_connection():
conn = create_db_conn()
yield conn # 测试用
conn.close() # 自动清理
yield 前面的代码是 setup,后面的是 teardown。比 unittest 的 setUp/tearDown 干净多了。
参数化测试,覆盖所有边界
SDK 验证里,同一个接口往往要测很多组参数。pytest 的参数化写起来就很优雅:
@pytest.mark.parametrize("input_value,expected", [
(1, 2),
(3, 6),
(-1, -2),
(0, 0),
])
def test_multiply(input_value, expected):
assert sdk_func(input_value) == expected
我在项目中遇到过漏测边界条件导致线上事故的情况。后来所有 API 都加了参数化用例,覆盖率直线上升。
插件生态,要啥有啥
pytest 的插件体系非常成熟。常用的几个:
- pytest-xdist:并行执行用例,跑得快
- pytest-cov:生成覆盖率报告
- pytest-html:生成漂亮的测试报告
- pytest-mock:更方便地 mock 外部依赖
- pytest-timeout:给用例设置超时
我曾经遇到过测试卡住不归零的情况。加了 pytest-timeout 后,超过 30 秒的用例直接标记失败,再也不用等半天了。