Python 常用自动化测试框架——Pytest
一、Pytest 简介
Pytest 是 Python 生态中最流行的第三方测试框架,相比标准库 unittest,它具有以下特点:
- 语法简洁:使用普通函数编写测试,无需继承测试类
- 断言原生:直接使用
assert,无需self.assertEqual()等 - 插件丰富:内置参数化、fixture、标记等功能,社区插件生态完善
- 兼容 unittest:可以运行已有的
unittest用例
安装:
pip install pytest
二、快速上手
2.1 基本示例
# test_example.py
def test_addition():
assert 1 + 1 == 2
def test_string_upper():
assert "hello".upper() == "HELLO"
运行测试:
pytest test_example.py
2.2 运行方式
# 运行当前目录下所有测试文件
pytest
# 运行指定文件
pytest test_example.py
# 运行指定函数
pytest test_example.py::test_addition
# 运行指定类中的方法
pytest test_example.py::TestClass::test_method
# 显示详细输出
pytest -v
# 显示 print 输出
pytest -s
三、断言
Pytest 直接使用 Python 原生 assert 语句,失败时会自动展示详细的差异信息。
def test_list_containment():
fruits = ["apple", "banana", "cherry"]
assert "banana" in fruits
def test_exception():
# 使用 pytest.raises 检查是否抛出指定异常
with pytest.raises(ValueError):
int("invalid")
# 同时检查异常信息
with pytest.raises(ValueError, match="invalid literal"):
int("abc")
常用断言写法:
assert a == b # 相等
assert a != b # 不等
assert a in b # 包含
assert a is True # 布尔值
assert a is None # None 检查
assert len(a) == 3 # 长度
四、Fixture——测试前置/后置
Fixture 是 Pytest 的核心机制,用于管理测试的 setup 和 teardown。
4.1 基本用法
import pytest
@pytest.fixture
def sample_data():
"""每个使用该 fixture 的测试都会独立调用"""
data = {"name": "Alice", "age": 30}
yield data # yield 之前的代码是 setup,之后是 teardown
# yield 之后的代码在测试结束后执行(teardown)
def test_name(sample_data):
assert sample_data["name"] == "Alice"
def test_age(sample_data):
assert sample_data["age"] == 30
4.2 Fixture 作用域
通过 scope 参数控制 fixture 的生命周期:
| scope 值 | 说明 |
|---|---|
function | 默认值,每个测试函数执行一次 |
class | 每个测试类执行一次 |
module | 每个 .py 文件执行一次 |
package | 每个包执行一次 |
session | 整个测试会话只执行一次 |
@pytest.fixture(scope="session")
def db_connection():
conn = create_connection()
yield conn
conn.close()
4.3 conftest.py——共享 Fixture
在项目目录下创建 conftest.py,其中定义的 fixture 会被同目录及子目录下的所有测试自动发现:
tests/
├── conftest.py # 公共 fixture
├── test_user.py
└── api/
├── conftest.py # api 模块专用 fixture
└── test_order.py
4.4 Fixture 依赖
@pytest.fixture
def database():
return Database()
@pytest.fixture
def user_table(database):
db = database
db.create_table("users")
yield db.get_table("users")
db.drop_table("users")
def test_insert(user_table):
user_table.insert({"id": 1, "name": "Alice"})
assert user_table.count() == 1
五、参数化测试
使用 @pytest.mark.parametrize 对同一测试逻辑传入多组数据:
import pytest
@pytest.mark.parametrize("input, expected", [
(1, 2),
(2, 3),
(10, 11),
(-1, 0),
])
def test_increment(input, expected):
assert input + 1 == expected
多参数组合(笛卡尔积):
@pytest.mark.parametrize("x", [1, 2])
@pytest.mark.parametrize("y", [10, 20])
def test_multiply(x, y):
assert x * y > 0
# 会生成 4 个测试用例: (1,10), (1,20), (2,10), (2,20)
六、标记(Mark)
使用标记对测试进行分类和筛选。
6.1 内置标记
import pytest
@pytest.mark.skip(reason="暂未实现")
def test_not_implemented():
pass
@pytest.mark.skipif(sys.version_info < (3, 10), reason="需要 Python 3.10+")
def test_new_syntax():
pass
@pytest.mark.xfail(reason="已知缺陷,待修复")
def test_known_bug():
assert 1 == 2
6.2 自定义标记
首先在 pytest.ini 或 pyproject.toml 中注册标记:
# pytest.ini
[pytest]
markers =
slow: 耗时较长的测试
smoke: 冒烟测试
使用自定义标记:
@pytest.mark.slow
def test_large_data_processing():
...
@pytest.mark.smoke
def test_basic_flow():
...
按标记筛选运行:
pytest -m slow # 只运行标记为 slow 的测试
pytest -m "slow and smoke" # 同时标记
pytest -m "not slow" # 排除 slow
七、测试报告
7.1 终端报告
pytest -v # 详细模式
pytest --tb=short # 简短回溯
pytest --tb=line # 单行回溯
pytest -q # 安静模式
pytest --failed-first # 失败优先显示
7.2 生成 HTML 报告
pip install pytest-html
pytest --html=report.html --self-contained-html
7.3 生成 JUnit XML(CI/CD 集成)
pytest --junitxml=report.xml
八、配置文件
Pytest 支持多种配置文件(按优先级):
pytest.inipyproject.tomltox.inisetup.cfg
pytest.ini 示例:
[pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts = -v --tb=short
markers =
slow: 慢速测试
smoke: 冒烟测试
九、常用插件
| 插件 | 用途 |
|---|---|
pytest-cov | 测试覆盖率 |
pytest-html | HTML 测试报告 |
pytest-xdist | 并行执行测试 |
pytest-mock | Mock/patch 增强 |
pytest-timeout | 超时控制 |
pytest-asyncio | 异步测试支持 |
pytest-selenium | Selenium 自动化测试 |
pytest-allure | Allure 测试报告 |
覆盖率示例:
pip install pytest-cov
pytest --cov=myapp --cov-report=html
并行执行示例:
pip install pytest-xdist
pytest -n auto # 自动检测 CPU 核心数并行执行
十、Mock
from unittest.mock import Mock, patch
def test_mock_basic():
mock_obj = Mock()
mock_obj.get.return_value = 42
assert mock_obj.get() == 42
mock_obj.get.assert_called_once()
def test_with_patch():
with patch("module.external_api_call") as mock_api:
mock_api.return_value = {"status": "ok"}
result = function_under_test()
assert result["status"] == "ok"
mock_api.assert_called_once()
配合 pytest-mock 使用更简洁的 fixture:
def test_with_mocker(mocker):
mocker.patch("module.external_api_call", return_value={"status": "ok"})
result = function_under_test()
assert result["status"] == "ok"
十一、实用技巧
11.1 临时目录与文件
def test_with_tmp_path(tmp_path):
# tmp_path 是 pytest 内置 fixture,提供隔离的临时目录
file = tmp_path / "data.txt"
file.write_text("hello")
assert file.read_text() == "hello"
# 测试结束后自动清理
11.2 捕获输出
def test_output(capsys):
print("hello")
captured = capsys.readouterr()
assert captured.out == "hello\n"
11.3 环境变量
pip install pytest-env
[pytest]
env =
TEST_MODE=staging
DB_HOST=localhost
11.4 失败重试
pip install pytest-rerunfailures
pytest --reruns 3 --reruns-delay 1
十二、总结
| 特性 | unittest | pytest |
|---|---|---|
| 测试编写 | 需继承 TestCase | 普通函数即可 |
| 断言方式 | self.assertXxx() | 原生 assert |
| 测试发现 | 遵循命名约定 | 命名约定 + 插件增强 |
| Fixture | setUp/tearDown | @fixture,更灵活 |
| 参数化 | 需手写循环 | @parametrize 原生支持 |
| 插件生态 | 较少 | 非常丰富 |
| 学习成本 | 低(标准库) | 中等 |
Pytest 凭借简洁的语法、强大的 fixture 机制和丰富的插件生态,已成为 Python 自动化测试的首选框架。

1万+

被折叠的 条评论
为什么被折叠?



