5分钟搞定Python测试框架速查手册
官方文档像天书?抓不住重点?别慌。
很多刚接触自动化测试的朋友,打开 pytest 或 unittest 的官方文档,瞬间头晕。全是参数、全是配置,根本不知道从哪下手。
今天这篇测试框架速查手册,不聊虚的。直接给你最实用的代码模板和避坑指南。哪怕你只懂基础 Python,也能照着跑通第一个测试用例。
在掘金技术社区上,我看过太多人因为环境配置卡在第一步。其实,90% 的初学者问题,都源于没搞清楚“测试框架”到底在测试什么。
别急,咱们一步步来。
概念速懂:测试框架到底在干嘛?
先说个扎心的事实:你写的代码,没人替你测。
如果不写测试,每次改完代码,你都得手动点一遍按钮,看一眼界面,查一下日志。稍微改个逻辑,回归测试就得跑半天。
测试框架的作用,就是帮你把这些重复的“人肉测试”变成代码。
想象一下,你写了一个函数 add(a, b)。
- 传统方式:你写个
main函数,调用add(1, 2),打印结果,看是不是 3。 - 测试框架方式:你写一个测试函数,告诉框架:“我要测
add(1, 2),期望结果是 3。如果结果不是 3,你就给我标红报警。”
为什么需要框架,而不是直接写 if 判断?
- 断言标准化:框架提供
assert、self.assertEqual等标准断言,报错信息清晰,能直接告诉你“期望值是3,实际值是4”。 - 自动发现与执行:框架能扫描你的代码,找到所有以
test开头的函数,一键运行。不用你手动一个一个调用。 - 隔离性:每个测试用例独立运行,互不干扰。一个测试挂了,不会影响下一个测试的执行。
核心区别:unittest vs pytest
目前 Python 圈子里,主流是 unittest(标准库)和 pytest(第三方库)。
- unittest:Python 自带,不需要安装。适合企业级大型项目,结构化强,但写起来比较啰嗦,必须继承
TestCase类。 - pytest:第三方库,语法极简。不需要继承类,不需要
self,直接用函数加assert就能写。社区活跃度极高,插件丰富(比如并行测试、参数化)。
建议:如果是个人项目或新手入门,强烈建议直接用 pytest。它的学习曲线更平缓,报错信息更友好。如果是接手老项目,且项目已大量使用 unittest,那就遵循项目规范。
环境准备:3分钟搭建你的测试战场
工欲善其事,必先利其器。
很多人第一步就错了:直接在虚拟环境里装 pytest,结果发现项目依赖包找不到。
正确的姿势是这样的:
- 确认 Python 版本:推荐 Python 3.8+。
- 创建虚拟环境:这是铁律,不要污染全局环境。
# 创建虚拟环境
python -m venv venv# 激活虚拟环境 (Windows)
venv\Scripts\activate# 激活虚拟环境 (Mac/Linux)
source venv/bin/activate
- 安装测试框架:
# 安装 pytest
pip install pytest
- 验证安装:
pytest --version
如果输出了版本号,恭喜你,环境就绪。
避坑指南:
如果你的项目是 Django 或 Flask,记得安装对应的测试插件,比如 pytest-django。否则,框架根本找不到你的数据库连接和测试客户端。
# Django 项目示例
pip install pytest-django
目录结构建议:
保持简洁,别搞太复杂。推荐以下结构:
project/
├── src/
│ └── calculator.py # 你的业务代码
├── tests/
│ ├── __init__.py # 让 Python 识别为包
│ └── test_calculator.py # 你的测试代码
└── pytest.ini # 配置文件 (可选)
关键点:测试文件必须以 test_ 开头,或者以 _test.py 结尾。测试函数必须以 test_ 开头。这是框架发现测试用例的约定,改了就找不到。
核心语法:pytest 的极简美学
为什么说 pytest 是入门首选?因为它的语法简直像说话一样简单。
1. 最基础的测试用例
假设 src/calculator.py 里有这么个函数:
def add(a, b):return a + b
在 tests/test_calculator.py 里,你只需要写:
from src.calculator import adddef test_add_positive():# 正常情况:两个正数相加assert add(1, 2) == 3
就这么简单?是的。
def test_add_positive()::函数名必须以test_开头。assert add(1, 2) == 3:断言。如果add(1, 2)的结果不等于 3,pytest 会抛出AssertionError,并在控制台显示详细的差异。
2. 异常测试
怎么测报错?比如,除以零应该抛出 ZeroDivisionError。
import pytestdef divide(a, b):return a / bdef test_divide_by_zero():# 使用 pytest.raises 上下文管理器with pytest.raises(ZeroDivisionError):divide(10, 0)
避坑:不要用 try...except 来测异常。那样写,如果函数没抛异常,测试也会通过,这就成了假测试。必须用 pytest.raises。
3. 参数化测试(重点)
这是 pytest 最强大的功能之一。
假设你要测试 add 函数,输入有 (1, 2), (0, 0), (-1, 1) 等多种组合。
错误写法:
def test_add():assert add(1, 2) == 3assert add(0, 0) == 0assert add(-1, 1) == 0
如果第二行挂了,第三行根本不会执行。而且报错信息里看不出是哪一行挂的。
正确写法(使用 @pytest.mark.parametrize):
import pytest@pytest.mark.parametrize("a, b, expected", [(1, 2, 3),(0, 0, 0),(-1, 1, 0),(5, -5, 0)
])
def test_add_params(a, b, expected):assert add(a, b) == expected
效果: pytest 会把这 4 组数据拆成 4 个独立的测试用例运行。
- 如果第一组挂了,第二组照常运行。
- 控制台会显示:
test_add_params[1-2-3] PASSED,test_add_params[0-0-0] FAILED。 - 一眼就能看出是哪组数据有问题。
4. 固定装置(Fixture)
测试前需要准备数据(比如连接数据库),测试后需要清理数据。用 fixture 来处理。
import pytest@pytest.fixture
def sample_data():# 测试前:准备数据data = {"name": "Alice", "age": 30}yield data# 测试后:清理数据 (yield 之后的代码)print("Cleaning up...")def test_fixture_usage(sample_data):assert sample_data["name"] == "Alice"
注意:yield 之前的代码在测试前执行,yield 之后的代码在测试后执行。无论测试成功还是失败,清理代码都会执行。
完整代码示例:实战一个用户注册接口
光看语法不过瘾,咱们来点真的。
假设我们有一个简单的用户注册逻辑:
- 用户名不能为空。
- 密码长度至少 6 位。
- 用户名必须唯一(模拟数据库检查)。
业务代码 src/user_service.py:
# 模拟数据库
users_db = {}class UserExistsError(Exception):passdef register_user(username, password):if not username:raise ValueError("Username cannot be empty")if len(password) < 6:raise ValueError("Password too short")if username in users_db:raise UserExistsError("Username already exists")users_db[username] = passwordreturn True
测试代码 tests/test_user_service.py:
import pytest
from src.user_service import register_user, UserExistsError, users_db# 每个测试用例前,清空数据库,确保测试隔离
@pytest.fixture(autouse=True)
def clean_db():users_db.clear()yieldusers_db.clear()class TestRegisterUser:def test_register_success(self):# 正常注册result = register_user("new_user", "123456")assert result is Trueassert "new_user" in users_dbdef test_register_empty_username(self):# 用户名为空with pytest.raises(ValueError, match="Username cannot be empty"):register_user("", "123456")def test_register_short_password(self):# 密码太短with pytest.raises(ValueError, match="Password too short"):register_user("user", "123")def test_register_duplicate_username(self):# 用户名重复register_user("dup_user", "123456")with pytest.raises(UserExistsError, match="Username already exists"):register_user("dup_user", "654321")@pytest.mark.parametrize("username, password, valid", [("alice", "123456", True),("bob", "12345", False), # 密码短("", "123456", False), # 用户名为空])def test_register_params(self, username, password, valid):if valid:assert register_user(username, password) is Trueelse:with pytest.raises(ValueError):register_user(username, password)
运行命令:
pytest -v
-v 参数表示 verbose,会显示每个测试用例的名称和状态。
你会看到类似输出:
test_user_service.py::TestRegisterUser::test_register_success PASSED
test_user_service.py::TestRegisterUser::test_register_empty_username PASSED
...
这个例子覆盖了什么?
- 正常路径:成功注册。
- 异常路径:空用户名、短密码、重复用户名。
- 数据隔离:
clean_dbfixture 确保每个测试用例开始时数据库是空的,避免“测试 A 污染了测试 B”。 - 参数化:用
parametrize批量测试不同输入。
常见报错:踩坑指南
再厉害的框架,新手也会踩坑。这里列出 3 个最高频的错误,看完能省你半天 debug 时间。
1. ModuleNotFoundError: No module named 'src'
- 原因:Python 找不到你的业务代码模块。
- 对策:
- 确保你在项目根目录运行
pytest。 - 在项目根目录添加一个空的
conftest.py文件。pytest 会自动将conftest.py所在目录加入sys.path。 - 或者在
pytest.ini中配置pythonpath = .。
- 确保你在项目根目录运行
2. AssertionError: assert False is True
- 原因:断言失败,实际值和期望值不一致。
- 对策:
- 仔细看报错信息中的
expected和actual。 - 如果是浮点数比较,不要用
==,用pytest.approx。例如:assert 0.1 + 0.2 == pytest.approx(0.3)。
- 仔细看报错信息中的
3. Fixture 'clean_db' not found
- 原因:测试文件找不到 fixture 定义。
- 对策:
- Fixture 必须定义在测试文件内,或者定义在
conftest.py中。 - 如果定义在另一个测试文件里,是不能直接引用的。要么移动到
conftest.py,要么复制过去。
- Fixture 必须定义在测试文件内,或者定义在
进阶技巧:只看失败的测试
测试多了,跑一次要几分钟?加个参数:
pytest -x --lf
-x:遇到第一个失败就停止。--lf:Last Failed,只运行上次失败的测试。
这个组合在调试阶段超级好用,能极大提升反馈速度。
小结:从“能跑”到“能维护”
这篇测试框架速查手册,核心就讲了三件事:
- 选对工具:新手直接用 pytest,语法简单,报错清晰。
- 掌握核心:
assert做断言,parametrize做参数化,fixture做数据隔离。 - 规范目录:
test_开头,conftest.py管理路径和公共 fixture。
测试不是负担,而是保护伞。当你改了代码,跑一遍测试,绿色的 PASSED 就是最大的安心。
最后,抛出一个问题:
你在写测试时,遇到过最难调试的“假阳性”问题吗?就是测试明明挂了,但代码逻辑其实是对的。
还有什么不懂的?评论区留言挨个回。
比如:
- 怎么给 pytest 生成 HTML 报告?
- 怎么 mock 第三方 API 调用?
- 怎么实现并行测试加速?
把你的痛点打在公屏上,咱们下期接着聊。