本文主要是一些理论知识,以及一些案例,函数之间的对比
适合像我一样没有测试基础的人看,我就是先学一手
pytest是Python的一个第三方单元测试框架,相比Python自带的unittest框架,它更加简洁和高效,同时兼容unittest框架。
测试实践
环境 | 用途 | 测试活动 |
---|---|---|
开发环境 | 开发者编写和调试代码 | 本地运行单元测试、手动测试 |
测试环境 | 模拟生产环境的独立环境 | 运行自动化测试(单元测试、集成测试、端到端测试) |
预生产环境 | 与生产环境几乎一致的镜像环境 | 性能测试、安全扫描、最终验收测试 |
生产环境 | 用户实际使用的线上环境 | 不运行测试代码,仅通过监控、日志、告警确保应用健康 |
单元测试(验证函数逻辑)
1 | # src/math_utils.py |
集成测试(验证多模块协作)
1 | # tests/test_api.py |
端到端测试(模拟用户操作)
1 | # tests/e2e/test_checkout.py |
运行pytest的方式
使用pytest命令行运行
也就是pip install pytest后,直接使用pytest命令就可以运行
python项目目录简化后,基本是这么一个结构:
1 | C:. |
其他命令行可用参数
- -s:显示测试执行过程中的标准输出和错误输出,包括print打印的信息。
- -v:显示更详细的测试执行信息,包括测试用例的名称、执行状态和执行时间等。
- -q:显示简略的测试执行信息。
- -k:只执行包含指定关键字的测试用例。关键字可以是文件名、函数名或类名的一部分。
- -m:只执行被特定标记(marker)装饰的测试用例。例如,
pytest -m slow
只执行被@pytest.mark.slow
装饰的测试用例。 - -x:一旦有任何一个测试用例执行失败,就停止当前线程的测试执行。
--maxfail=num
:允许指定失败测试用例的最大数量,超过该数量后停止执行。--reruns=num
:对失败的测试用例进行重跑指定次数。需要安装pytest-rerunfailures
插件模块。--collect-only
:只收集将要执行的测试用例,但不会实际执行它们。这可以用于查看哪些测试用例将被执行。-lf
或-last-failed
:只执行上次执行失败的测试。-ff
或-failed-first
:先执行上次失败的测试,然后再执行其他测试。-html=报告文件
:生成HTML格式的测试报告。需要安装pytest-html
插件模块。-junitxml=报告文件
:生成XML格式的测试报告。需要安装pytest-xunitxml
插件模块。-json=报告文件
:生成JSON格式的测试报告。需要安装pytest-json
插件模块。-cov=模块
:生成指定模块的覆盖率报告。需要安装pytest-cov
插件模块。
使用pytest.ini配置文件运行
命令运行最后还是会读取pytest.ini文件
所以最好的方法还是使用配置文件,这样每次运行就不需要指定参数了
但命令行的优先级更高,运行时会覆盖ini中的配置
1 | [pytest] |
字段 | 作用描述 | 示例值 |
---|---|---|
‘addopts’ | 定义默认命令行选项(自动附加到 ‘pytest’ 命令后) | ‘-v –tb=short –color=yes’ |
‘testpaths’ | 指定 pytest 搜索测试文件的目录(默认所有目录) | “unit_tests 测试 |
‘norecursedirs’ | 排除搜索的目录(避免遍历不必要的路径) | ‘.venv .git node_modules’ |
‘python_files’ | 定义测试文件的命名模式(默认匹配 ‘test_*.py’ 或 ‘*_test.py’) | ‘test_*.py check_*.py’ |
‘python_classes’ | 定义测试类的命名模式(默认匹配 ‘Test*’) | ‘测试套件‘ |
‘python_functions’ | 定义测试函数的命名模式(默认匹配 ‘test_*’) | ‘test_* check_* |
‘markers’ | 注册自定义标记(避免 ‘Unknown pytest.mark’ 警告) | ‘slow:将测试标记为慢’ |
‘filterwarnings’ | 控制 Python 警告的过滤行为 | ‘ignore:.*deprecated.*:UserWarning’ |
‘log_cli’ | 启用实时日志输出到控制台 | ‘真’ |
‘timeoue’ | 设置测试超时时间(需安装 ‘pytest-timeout’ 插件) | 300 (单位:秒) |
conftest.py配置文件
功能 | 描述 |
---|---|
共享 Fixtures | 定义全局可用的夹具(如数据库连接、临时目录),供多个测试文件复用。 |
加载插件 | 配置 pytest 插件(如 pytest-mock 、pytest-cov )或自定义插件。 |
定义钩子函数 | 自定义 pytest 行为(如修改测试收集逻辑、添加报告输出)。 |
配置作用域 | 不同层级的 conftest.py 控制不同范围的测试(目录级继承机制)。 |
加载机制
- 文件名固定:必须命名为
conftest.py
。 - 作用域:
- 项目根目录的
conftest.py
→ 对所有测试生效。 - 子目录中的
conftest.py
→ 仅对该目录及子目录下的测试生效。 - 自动发现:pytest 运行时会自动加载所有符合作用域的
conftest.py
。
层级覆盖机制
在pytest中,conftest.py文件的设计理念与Helm工程中父级Values文件的层级覆盖机制非常相似,都是为了实现配置的集中管理和灵活的层级覆盖,但是有着些许不同:
conftest.py中,是子文件优先级高于全局配置
- 跨模块共享,多个测试文件可复用同一配置
- 集中修改,只需改动一个文件
- 目录层级继承,子目录可覆盖父级配置
- 可通过不同
conftest.py
定义环境差异
断言机制assert
也就是assert关键字
1 | def test_add(): |
- 若
result == 3
→ 断言通过,测试继续执行。 - 若
result ≠ 3
→ 断言失败,抛出AssertionError
并终止当前测试。
fixtrue装饰器(夹具)
fixture
是 pytest
中的一个核心概念,用于定义测试运行前的准备工作和测试完成后的清理工作。fixture
可以是一个函数、一个类或者一个方法,并通过 @pytest.fixture
装饰器进行标记。
定义一个简单fixture
使用 @pytest.fixture
装饰器定义一个 fixture
函数。这个函数可以包含测试运行前的设置代码和测试完成后的清理代码。
1 |
|
fixture作用范围-scope参数
scope参数用来指定fixture的作用范围
function级
默认。每个测试函数调用时,都会创建一个新的fixture实例
在函数执行之前,function级fixture就会执行,并在该函数执行结束后销毁
1 | import pytest |
class级
在每个测试类调用时,会创建一个fixture实例
如在下面这个例子中,fixture实例只会运行一次
如果创建了不同class,则会运行多次
1 | import pytest |
module级
在每个测试模块调用时,会创建一个fixture实例
在模块的所有测试开始之前创建一次,并在所有测试完成之后销毁一次(包括多个class的情况)
session级
在整个测试会话期间,只会创建一个fixture实例
范围是最大的,也被用于作为全局fixture
fixtrue自动调用-autouse参数
默认关闭
如果开启了这个参数,定义的fixture就会自动应用在当前目录与递归目录下的作用范围内
比如下面这个例子开启了autouse
1 | import pytest |
如果不开启,就需要在test_example中手动调用
1 | def test_example(setup_and_teardown): |
fixture参数化-params
当我们需要用不同的输入数据来测试同一个功能的时候,可以通过params传递多个参数,每个参数会生成一个测试用例。这样就不需要写多个测试函数,或者用循环来处理了。
用于测试同一功能在不同输入下的行为是否符合预期,减少重复代码。
- 在
@pytest.fixture
中指定params
参数(接受一个列表/元组) - 在 Fixture 函数中通过
request.param
获取参数值 - 测试函数通过 Fixture 名称自动遍历所有参数
1 | import pytest |
fixture参数别名-ids
为 params
中的每个参数提供一个 可读性更强的名称(默认显示参数值,复杂参数可读性差)。在测试报告中更清晰地标识不同用例。
1 | import pytest |
用插件就能看到
fixture函数别名-name
- 避免命名冲突:当 Fixture 函数名与测试函数名相同时,用
name
区分 - 提高可读性:将底层工具函数命名为
_create_db
,但暴露为database
Fixture - 动态生成 Fixture 名称:通过工厂函数批量创建 Fixture 时,用
name
参数赋予有意义的名称
1 | import pytest |
跳过测试用例skip、skipif
功能 | @pytest.mark.skip | @pytest.mark.skipif |
---|---|---|
跳过类型 | 无条件跳过 | 有条件跳过 |
常用场景 | 1. 尚未完成的测试2. 已知问题测试 | 1. 版本限制2. 环境依赖检测 |
参数要求 | 不需要条件参数 | 必须提供 condition 布尔表达式 |
标记位置 | 函数/类上方或代码内部 | 函数/类上方或代码内部 |
报告显示 | 显示为 skipped |
显示为 skipped 并附带条件说明 |
使用pytest.mark.skip装饰器
- 带mark的就是非动态的“标记”
- 在装饰器参数中添加,用于跳过整个测试函数或类
- 用于未实现功能或长期禁用的测试
- 适合静态条件跳过
1 | import pytest |
用pytest.skip动态跳过
- 用于测试函数内部逻辑
- 可以在函数内任意位置使用
- 用于运行时环境监测或动态依赖检查
- 适合动态条件跳过
1 | def test_database_connection(): |
使用pytest.mark.skipif条件跳过
1 | import sys |
使用pytest.skipif多条件判断
在下面这个代码,需要同时满足这两个条件
使用其他逻辑运算符可以构建诸如or等复杂条件判断
1 | import os |
将跳过标记赋值给变量
- 进行代码复用
- 统一管理相同条件的跳过描述
1 | # 定义通用跳过标记 |
缺少模块跳过
pytest.importorskip() 是处理 模块依赖管理 的核心工具
避免隐式依赖、提前检测
1 | import pytest |
自定义标记mark
- 注册标记:
在pytest.ini中进行添加,以避免可能出现的warning
[pytest]
markers =
regular: 常规测试用例
model: 模型相关测试
- 标记继承性:
当类被标记时,类中的所有测试方法都会继承该标记
标记的核心作用:选择性执行测试
当测试用例数量很多时,可以通过标记将测试分类,然后选择性地运行特定类别的测试。
1 | # 标记为 regular 类别的测试 |
pytest标记参数化生成测试用例
@pytest.mark.parametrize
是 pytest 中用于 批量生成测试用例 的核心装饰器,它的核心价值在于 用一组数据驱动多个测试场景
1. 避免重复代码
当多个测试用例逻辑相同,仅输入/输出不同时,无需重复编写多个函数。
2. 集中管理测试数据
测试数据与测试逻辑分离,便于维护和扩展。
3. 提高可读性
明确展示所有测试场景的输入和预期输出。
parametrize与fixture的区别
特性 | @pytest.mark.parametrize | @pytest.fixture(params=…) |
---|---|---|
主要用途 | 直接为测试函数提供多组输入参数 | 为多个测试函数共享同一组预处理数据 |
参数可见性 | 参数直接暴露在测试函数签名中 | 参数隐藏在 fixture 内部,测试函数只看到结果 |
参数传递方式 | 显式传递参数到测试函数 | 通过 fixture 返回值隐式传递 |
复用性 | 参数仅作用于当前测试函数 | 参数化后的 fixture 可被多个测试函数复用 |
数据预处理能力 | 只能传递原始数据 | 可在 fixture 内对参数进行复杂初始化处理 |
适用场景 | 简单的输入/输出组合验证 | 需要共享参数化资源(如数据库连接、配置文件等) |
与测试逻辑的耦合度 | 测试逻辑与参数数据强耦合 | 测试逻辑与参数数据解耦 |
参数组合方式 | 支持笛卡尔积(叠加多个 parametrize 装饰器) | 只能线性遍历参数 |
1 | import pytest |
可以看出 parametrize 能更直观展示参数内容,而 fixture 参数化需要结合 ids 参数才能优化显示
pytest标记失败@pytest.mark.xfail
核心用途
- 预期失败的测试:标记已知但尚未修复的问题
- 条件性跳过:当某些条件不满足时主动失败
- 阶段性开发:标记未完成的测试用例
和skip的区别
特性 | xfail | skip |
---|---|---|
执行测试 | ✔️ 会执行测试体 | ❌ 完全不执行测试 |
失败处理 | 预期失败不算测试失败 | 直接标记为跳过 |
通过反馈 | 意外通过会标记为 XPASS | 无反馈 |
结果统计 | 单独统计 XFAIL 和 XPASS | 统计为 SKIPPED |
适用场景 | 已知问题/未实现功能 | 环境不满足/临时禁用 |
1 | import pytest |
失败重试机制pytest-rerunfailures
pip install pytest-rerunfailures
因素 | 建议 | 示例 |
---|---|---|
失败是否具有偶发性 | ✅ 是 → 使用重试 | 网络超时 |
失败是否可自我修复 | ✅ 是 → 使用重试 | 文件锁释放 |
是否涉及外部系统 | ✅ 是 → 使用重试 | 数据库连接 |
是否是逻辑错误 | ❌ 否 → 使用 xfail | 算法错误 |
测试执行时间敏感 | ❌ 否 → 谨慎使用重试 | 实时系统测试 |
重试参数
1 | [pytest] |
适用重试的动态测试
- 特点:依赖外部 API 的稳定性
- 重试价值:网络抖动可能导致偶发失败
- 最佳实践:重试次数建议 3-5 次,间隔 1-3 秒
1 | 网络相关测试 |
执行耗时参数durations
获取最慢的10个用例的执行耗时
1 | pytest --durations=10 |
获取生成allure报告
环境是windows10
pytest会自己生成报告,但是都是json格式的,可读性比较差
1 | # 安装java |