pytest:灵活强大的 Python 测试框架
Python 测试框架 pytest 完全入门指南
为什么选择 pytest
pytest 是 Python 生态中最受欢迎的测试框架之一。它语法简洁、可扩展性强,支持从简单的单元测试到复杂的功能测试。与 unittest 相比,pytest 无需继承特定类,断言直接使用原生 assert 语句,失败时会提供丰富的上下文信息。
环境准备与安装
pip install pytest
建议在虚拟环境中操作,安装完成后验证版本:
pytest --version
编写第一个测试
pytest 自动发现符合命名规范的文件和函数:测试文件以 test_ 开头或以 _test 结尾,测试函数以 test_ 开头。
创建文件 test_calculator.py:
def add(a, b):
return a + b
def test_add():
assert add(2, 3) == 5
assert add(-1, 1) == 0
运行测试:
pytest
pytest 会收集并执行所有测试,输出简洁的结果报告。
强大而直观的断言
pytest 允许使用 Python 原生的 assert 语句,重写了断言重写机制,使得失败时的错误信息更有帮助。
def test_string():
assert "hello".upper() == "HELLO"
def test_list():
lst = [1, 2, 3]
assert len(lst) == 3
assert lst[0] == 1
def test_dict():
person = {"name": "Alice", "age": 30}
assert person["name"] == "Alice"
对于浮点数比较,可使用 pytest.approx:
def test_float():
assert 0.1 + 0.2 == pytest.approx(0.3)
对于异常测试,使用 pytest.raises 上下文管理器:
import pytest
def test_zero_division():
with pytest.raises(ZeroDivisionError):
1 / 0
测试固件 (Fixtures)
fixture 是 pytest 的核心功能,用于提供测试所需的依赖(如数据库连接、测试数据等)。使用 @pytest.fixture 装饰器定义,测试函数将 fixture 名称作为参数即可注入。
import pytest
@pytest.fixture
def sample_data():
return {"numbers": [1, 2, 3, 4], "name": "test"}
def test_sum(sample_data):
assert sum(sample_data["numbers"]) == 10
def test_name(sample_data):
assert sample_data["name"] == "test"
fixture 作用域
通过 scope 参数控制 fixture 的生命周期:function(默认,每个测试函数执行一次)、class、module、package、session。
@pytest.fixture(scope="module")
def db_connection():
# 模块中所有测试共享同一个连接
conn = create_connection()
yield conn
conn.close()
使用 yield 可以在 fixture 中实现 setup 和 teardown 逻辑。
参数化 Fixture
fixture 可以间接参数化,但更常见的测试参数化直接在测试函数上使用 @pytest.mark.parametrize。
参数化测试
同一测试逻辑使用多组不同数据运行,避免重复代码。
import pytest
def multiply(a, b):
return a * b
@pytest.mark.parametrize("a, b, expected", [
(2, 3, 6),
(0, 5, 0),
(-2, -3, 6),
(1.5, 2, 3)
])
def test_multiply(a, b, expected):
assert multiply(a, b) == expected
参数可以堆叠多个 parametrize 装饰器,实现组合测试:
@pytest.mark.parametrize("x", [0, 1])
@pytest.mark.parametrize("y", [2, 3])
def test_combination(x, y):
assert x + y == x + y
上述将生成 4 个测试用例 (0,2), (0,3), (1,2), (1,3)。
使用 Markers 标记测试
pytest 提供内置标记,也可自定义标记,用于筛选或跳过测试。
跳过测试
@pytest.mark.skip(reason="功能未实现")
def test_unfinished():
pass
@pytest.mark.skipif(not hasattr(math, "isclose"), reason="需要 math.isclose")
def test_using_isclose():
assert math.isclose(0.1 + 0.2, 0.3)
预期失败
@pytest.mark.xfail(raises=ZeroDivisionError)
def test_expected_failure():
1 / 0
自定义标记
在 pytest.ini、pyproject.toml 或 tox.ini 中注册标记:
[pytest]
markers =
slow: marks tests as slow (deselect with '-m "not slow"')
integration: integration tests
然后使用:pytest -m "slow" 只运行慢测试,-m "not slow" 跳过慢测试。
测试发现与运行控制
指定测试路径
pytest tests/
pytest tests/test_sample.py::test_function
pytest tests/test_sample.py::TestClass::test_method
常用选项
-v:详细输出-s:允许打印输出(不捕获)-k:按关键字表达式筛选测试
pytest -k "add or multiply" # 运行名称中包含 add 或 multiply 的测试
--lf:只运行上次失败的测试--ff:先运行上次失败的测试,再运行其他-x:遇到第一个失败时停止--maxfail=N:失败 N 次后停止
测试覆盖率报告
安装 pytest-cov 插件:
pip install pytest-cov
运行并生成覆盖率:
pytest --cov=my_module tests/
pytest --cov=my_module --cov-report=html
会在 htmlcov 目录生成可视化报告。
常用插件推荐
pytest-django:Django 项目测试支持pytest-asyncio:异步代码测试pytest-xdist:分布式执行测试,加速 CIpytest-timeout:设置测试超时时间pytest-mock:简化使用unittest.mock的 fixture
组织大型测试项目
推荐目录结构:
project/
├── src/
│ └── calculator.py
├── tests/
│ ├── __init__.py
│ ├── conftest.py # 共享 fixture
│ ├── test_calculator.py
│ └── integration/
│ └── test_api.py
└── pytest.ini # 配置文件
conftest.py 用于放置共享的 fixture 和插件配置,pytest 会自动加载同目录及父目录的 conftest。
实战示例:测试一个用户管理模块
假设 user.py:
class User:
def __init__(self, name, age):
self.name = name
self.age = age
def is_adult(self):
return self.age >= 18
测试文件 test_user.py 使用 fixture 和参数化:
import pytest
from user import User
@pytest.fixture
def default_user():
return User("Bob", 25)
def test_user_creation(default_user):
assert default_user.name == "Bob"
assert default_user.age == 25
@pytest.mark.parametrize("age, expected", [
(17, False),
(18, True),
(21, True)
])
def test_is_adult(age, expected):
user = User("Test", age)
assert user.is_adult() == expected
总结
pytest 凭借其零模板的编写方式、强大的 fixture 管理和丰富的插件生态,成为 Python 测试的首选。掌握上述核心特性后,你可以轻松构建可维护、高效的测试套件,并将其集成到持续集成流水线中。开始编写你的第一个 pytest 测试,让代码质量更有保障。