pytest相关
Pytest 测试框架 系统学习大纲
一、Pytest 概述
- 1.1 Pytest 简介
- 什么是 Pytest
- Pytest 的特点和优势
- 与其他测试框架对比(unittest, nose)
详情关于pytest和unittest对比
对比维度 pytest unittest 设计哲学 更灵活、简洁,遵循“约定优于配置”原则。 基于 Java 的 JUnit 框架,采用经典的 xUnit 风格,结构严谨。 编写风格 使用普通的 Python 函数和 assert语句,代码更简洁。必须继承 unittest.TestCase类,并使用self.assertXxx()系列方法。断言方式 直接使用 Python 原生的 assert语句,失败时信息更清晰易读。使用 self.assertXxx()方法(如assertEqual,assertTrue)。夹具管理 通过 @pytest.fixture装饰器提供强大、灵活的夹具系统,支持作用域和自动使用。通过 setUp/tearDown(方法级)和setUpClass/tearDownClass(类级)管理。测试发现 自动发现以 test_开头的函数和以Test开头的类中的test_方法。自动发现继承 unittest.TestCase的类中的test_开头的方法。插件生态 拥有极其丰富和强大的插件生态系统(如并发执行、HTML报告、覆盖率集成等)。 生态相对简单,功能扩展主要依赖原生模块或少量第三方库。 执行控制 命令行功能强大,支持通过 -k筛选用例、-m标记用例、--lf只运行上次失败的用例等。命令行功能相对基础。 报告输出 默认输出简洁,失败时提供非常详细的上下文差异分析。 输出为标准 xUnit 风格报告。 与unittest兼容 可以无缝运行 unittest 测试用例,无需修改代码。 无法直接运行 pytest 风格的测试函数。 - 1.2 安装与配置
-
pip 安装
- 虚拟环境配置
- 验证安装
pip install pytest pytest --version -
- 1.3 第一个测试用例
- 简单测试函数
- 运行测试
- 测试结果解读
# test_sample.py def func(x): return x + 1 def test_answer(): assert func(3) == 5
二、测试发现与执行
- 2.1 测试发现规则
- 文件命名规则:test_*.py 或 *_test.py
- 函数命名规则:test_*
- 类命名规则:Test*
- 方法命名规则:test_*
- 2.2 执行测试的方式
- 运行所有测试
- 运行指定模块
- 运行指定目录
- 运行指定测试函数
# 运行所有测试 pytest # 运行指定文件 pytest test_module.py # 运行指定测试函数 pytest test_module.py::test_function # 运行指定类 pytest test_module.py::TestClass # 运行指定类中的方法 pytest test_module.py::TestClass::test_method - 2.3 常用命令行选项
- -v: 详细输出
- -q: 简洁输出
- -s: 显示 print 输出
- -k: 关键字过滤
- -m: 标记过滤
- -x: 遇到失败立即停止
- –maxfail=n: 指定最大失败数
- –collect-only: 收集但不执行
- –tb=style: 设置回溯显示样式
pytest -v -s pytest -k "login" pytest -m "slow" pytest --maxfail=2
三、测试用例编写规范
- 3.1 测试函数结构
- Arrange-Act-Assert 模式
- 函数签名
- 返回值
- 3.2 断言使用
- assert 语句
- 内置断言
- 断言重写机制
def test_addition(): # Arrange a, b = 1, 2 # Act result = a + b # Assert assert result == 3 def test_list_operations(): items = [1, 2, 3] assert len(items) == 3 assert 2 in items assert items[0] == 1 - 3.3 测试异常
- pytest.raises
- 异常匹配
- 异常消息验证
import pytest def test_divide_by_zero(): with pytest.raises(ZeroDivisionError): 1 / 0 def test_divide_by_zero_with_message(): with pytest.raises(ZeroDivisionError, match="division by zero"): 1 / 0 - 3.4 测试类组织
- 测试类结构
- 类级别设置
- 实例方法
class TestCalculator: def test_addition(self): assert 1 + 1 == 2 def test_subtraction(self): assert 5 - 3 == 2
四、固件(Fixture)
- 4.1 什么是 Fixture
- 定义和用途
- 生命周期
- 作用域
- 4.2 基本用法
- @pytest.fixture 装饰器
- Fixture 参数
- 使用 Fixture
import pytest @pytest.fixture def sample_data(): return {"name": "Alice", "age": 30} def test_user_data(sample_data): assert sample_data["name"] == "Alice" assert sample_data["age"] == 30 - 4.3 Fixture 作用域
- function: 函数级别
- class: 类级别
- module: 模块级别
- session: 会话级别
@pytest.fixture(scope="session") def db_connection(): # 建立数据库连接 conn = create_connection() yield conn # 清理 conn.close() @pytest.fixture(scope="module") def setup_data(): return {"key": "value"} - 4.4 Fixture 自动使用
- autouse=True
- 全局 Fixture
@pytest.fixture(autouse=True) def setup_each_test(): print("\n=== 开始测试 ===") yield print("\n=== 结束测试 ===") - 4.5 Fixture 之间的依赖
- Fixture 调用其他 Fixture
- 依赖关系
@pytest.fixture def user(): return {"name": "Alice"} @pytest.fixture def user_with_age(user): user["age"] = 30 return user def test_user_info(user_with_age): assert user_with_age["name"] == "Alice" assert user_with_age["age"] == 30 - 4.6 内置 Fixture
- tmp_path: 临时目录
- capsys: 捕获输出
- monkeypatch: 猴子补丁
- mocker: pytest-mock
def test_tmp_path(tmp_path): file_path = tmp_path / "test.txt" file_path.write_text("Hello") assert file_path.read_text() == "Hello" def test_output(capsys): print("Hello World") captured = capsys.readouterr() assert captured.out == "Hello World\n"
五、参数化测试
- 5.1 @pytest.mark.parametrize
- 基本语法
- 多参数
- 参数组合
import pytest @pytest.mark.parametrize("input,expected", [ (1, 2), (2, 3), (3, 4) ]) def test_increment(input, expected): assert input + 1 == expected - 5.2 参数化 Fixture
- indirect 参数
- 动态参数
@pytest.fixture def data(request): return request.param * 2 @pytest.mark.parametrize("data", [1, 2, 3], indirect=True) def test_data(data): assert data % 2 == 0 - 5.3 参数化策略
- 笛卡尔积
- 自定义参数生成
- 从文件读取参数
六、标记(Mark)
- 6.1 内置标记
- @pytest.mark.skip: 跳过测试
- @pytest.mark.skipif: 条件跳过
- @pytest.mark.xfail: 预期失败
- @pytest.mark.parametrize: 参数化
import pytest import sys @pytest.mark.skip(reason="功能未实现") def test_unimplemented(): assert False @pytest.mark.skipif(sys.version_info < (3, 8), reason="需要 Python 3.8+") def test_python38_feature(): assert True @pytest.mark.xfail def test_experimental(): assert False - 6.2 自定义标记
- 注册标记
- 标记分类
- 按标记过滤
# pytest.ini [pytest] markers = slow: 标记慢速测试 integration: 集成测试 smoke: 冒烟测试 # 测试文件 @pytest.mark.slow def test_slow_operation(): import time time.sleep(2) assert True - 6.3 标记与 Fixture 结合
- 根据标记使用不同 Fixture
- 动态 Fixture
七、测试报告与日志
- 7.1 测试结果输出
- 控制台输出
- 详细模式
- 安静模式
- 7.2 报告生成
- 生成 JUnit XML
- 生成 HTML 报告
- 生成 Allure 报告
# 生成 JUnit XML pytest --junit-xml=report.xml # 生成 HTML 报告 pytest --html=report.html # 使用 pytest-html pytest --html=report.html --self-contained-html - 7.3 日志记录
- caplog Fixture
- 配置日志级别
- 捕获日志消息
import logging def test_logging(caplog): caplog.set_level(logging.INFO) logging.info("测试日志") assert "测试日志" in caplog.text - 7.4 测试覆盖率
- pytest-cov 插件
- 覆盖率报告
- 覆盖率配置
# 运行测试并计算覆盖率 pytest --cov=myproject tests/ # 生成 HTML 覆盖率报告 pytest --cov=myproject --cov-report=html tests/
八、Mock 和 Monkeypatch
- 8.1 什么是 Mock
- 模拟对象
- 测试替身
- 使用场景
- 8.2 unittest.mock
- Mock 对象
- MagicMock
- patch 装饰器
from unittest.mock import Mock, patch def test_mock(): mock_obj = Mock() mock_obj.method.return_value = 42 assert mock_obj.method() == 42 mock_obj.method.assert_called_once() @patch("module.function") def test_patch(mock_function): mock_function.return_value = "mocked" result = module.function() assert result == "mocked" - 8.3 pytest-mock 插件
- mocker Fixture
- 简化 Mock
def test_with_mocker(mocker): mock_func = mocker.patch("module.function") mock_func.return_value = "mocked" result = module.function() assert result == "mocked" mock_func.assert_called_once() - 8.4 monkeypatch
- 临时修改
- 环境变量
- 属性修改
import os def test_env_var(monkeypatch): monkeypatch.setenv("API_KEY", "test_key") assert os.getenv("API_KEY") == "test_key" def test_attribute(monkeypatch): class MyClass: value = 1 obj = MyClass() monkeypatch.setattr(obj, "value", 2) assert obj.value == 2
九、测试数据管理
- 9.1 测试数据文件
- JSON 文件
- YAML 文件
- CSV 文件
- 9.2 Fixture 工厂
- 动态生成测试数据
- 数据清理
@pytest.fixture def user_factory(): users = [] def _create_user(name, age): user = {"name": name, "age": age} users.append(user) return user yield _create_user # 清理 for user in users: # 清理逻辑 pass - 9.3 测试数据库
- 测试数据库设置
- 数据迁移
- 事务回滚
- 9.4 临时文件
- tmp_path Fixture
- 临时目录
def test_temp_file(tmp_path): d = tmp_path / "sub" d.mkdir() p = d / "test.txt" p.write_text("Hello") assert p.read_text() == "Hello"
十、插件系统
- 10.1 常用插件
- pytest-html: HTML 报告
- pytest-cov: 测试覆盖率
- pytest-xdist: 并行测试
- pytest-ordering: 测试顺序
- pytest-timeout: 超时控制
- pytest-asyncio: 异步测试
- pytest-rerunfailures: 失败重试
- 10.2 插件安装与配置
-
安装插件
- 插件配置
- 插件选项
pip install pytest-html pytest-cov pytest-xdist pytest --html=report.html --cov=myproject -n 4 -
- 10.3 自定义插件
- 钩子函数
- 命令行选项
- 自定义 Fixture
# conftest.py def pytest_addoption(parser): parser.addoption("--env", action="store", default="test") @pytest.fixture def environment(request): return request.config.getoption("--env")
十一、并发与并行测试
- 11.1 pytest-xdist 插件
- 并行执行
- 分布式测试
- CPU 核心数
# 并行运行测试 pytest -n auto pytest -n 4 - 11.2 并发问题
- 资源竞争
- 测试隔离
- 数据一致性
- 11.3 优化策略
- 测试分组
- 资源管理
- 测试顺序
十二、异步测试
- 12.1 pytest-asyncio
- 异步测试支持
- async/await
- 事件循环
import pytest import asyncio @pytest.mark.asyncio async def test_async(): result = await async_function() assert result == "expected" - 12.2 异步 Fixture
- 异步设置
- 异步清理
@pytest.fixture async def async_fixture(): data = await fetch_data() yield data await cleanup() - 12.3 超时控制
- 测试超时
- 异步超时
@pytest.mark.asyncio @pytest.mark.timeout(5) async def test_timeout(): await asyncio.sleep(10) # 会超时
十三、测试配置
- 13.1 配置文件
- pytest.ini
- setup.cfg
- pyproject.toml
- tox.ini
# pytest.ini [pytest] testpaths = tests python_files = test_*.py python_classes = Test* python_functions = test_* markers = slow: 慢速测试 integration: 集成测试 addopts = -v -s - 13.2 命令行配置
- 默认选项
- 配置文件优先级
- 13.3 环境配置
- 环境变量
- 配置文件
- 动态配置
十四、测试组织结构
- 14.1 测试目录结构
- 扁平结构
- 分层结构
- 包结构
project/ ├── src/ │ └── mypackage/ ├── tests/ │ ├── unit/ │ ├── integration/ │ └── conftest.py ├── pytest.ini └── pyproject.toml - 14.2 conftest.py
- 作用域
- 共享 Fixture
- 插件配置
# tests/conftest.py import pytest @pytest.fixture def shared_data(): return {"key": "value"} # tests/unit/conftest.py @pytest.fixture def unit_data(): return {"unit": "test"} - 14.3 导入路径
- sys.path
- PYTHONPATH
- 相对导入
- 14.4 测试分类
- 单元测试
- 集成测试
- 端到端测试
- 冒烟测试
十五、高级特性
- 15.1 钩子函数
- pytest_addoption
- pytest_configure
- pytest_collection_modifyitems
- pytest_runtest_setup
- pytest_runtest_teardown
# conftest.py def pytest_collection_modifyitems(config, items): # 修改测试项顺序 items.reverse() def pytest_configure(config): # 配置阶段 config .addinivalue_line("markers", "slow: 慢速测试") - 15.2 自定义断言
- 断言重写
- 自定义断言消息
def test_custom_assertion(): a = [1, 2, 3] b = [1, 2, 3] # 自定义断言消息 assert a == b, f"列表不相等: {a} != {b}" - 15.3 测试依赖
- pytest-dependency
- 测试执行顺序
import pytest @pytest.mark.dependency() def test_first(): assert True @pytest.mark.dependency(depends=["test_first"]) def test_second(): assert True - 15.4 跳过条件
- 运行时跳过
- 平台判断
import sys import pytest @pytest.mark.skipif(sys.platform != "linux", reason="仅在 Linux 上运行") def test_linux_only(): assert True
十六、最佳实践
- 16.1 测试设计原则
- 单一职责
- 独立性
- 可重复性
- 快速执行
- 16.2 测试命名规范
- 描述性名称
- 统一格式
- 英文命名
- 16.3 测试维护
- 避免重复
- 及时清理
- 持续重构
- 16.4 测试性能
- 避免 I/O
- 使用 Mock
- 并行执行
- 16.5 测试稳定性
- 避免随机性
- 处理竞态条件
- 资源清理
十七、常见问题与调试
- 17.1 测试失败分析
- 回溯信息
- 断言消息
- 调试输出
- 17.2 常见错误
- Fixture 作用域错误
- 导入错误
- 依赖缺失
- 17.3 调试技巧
- pdb 调试
- print 输出
- 日志记录
def test_debug(): import pdb; pdb.set_trace() # 或者使用断点 breakpoint() - 17.4 测试隔离
- 环境隔离
- 数据隔离
- 进程隔离
十八、CI/CD 集成
- 18.1 持续集成配置
- GitHub Actions
- GitLab CI
- Jenkins
- Travis CI
# .github/workflows/test.yml name: Tests on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Run tests run: | pip install -r requirements.txt pytest --cov=myproject --cov-report=xml - 18.2 测试报告集成
- 测试结果通知
- 覆盖率报告
- 测试趋势
- 18.3 质量门禁
- 测试通过率
- 覆盖率阈值
- 测试时长
pytest --cov=myproject --cov-fail-under=80 pytest --junitxml=report.xml
十九、学习资源
- 19.1 官方文档
- Pytest 官方文档
- GitHub 仓库
- 官方示例
- 19.2 书籍推荐
- 《Python Testing with pytest》
- 《Pytest Quick Start Guide》
- 19.3 在线教程
- 官方教程
- 视频课程
- 博客文章
- 19.4 社区资源
- Stack Overflow
- GitHub Issues
- 中文社区
- 19.5 进阶学习
- 插件开发
- 源码阅读
- 贡献代码