单独收拢 pytest 目录下的 fixture、hook、plugin、mark、conftest 等进阶内容。
导航
- 总览:tools
- 相关:tools-testing
- 相关:tools-config-files
收录来源
raw/langs/pythons/tools/testers/Pytests/normal.rstraw/langs/pythons/tools/testers/Pytests/plugin.rstraw/langs/pythons/tools/testers/Pytests/hook.rstraw/langs/pythons/tools/testers/Pytests/conftest.py.rstraw/langs/pythons/tools/testers/Pytests/pytest.fixture.rstraw/langs/pythons/tools/testers/Pytests/pytest.hookimpl.rstraw/langs/pythons/tools/testers/Pytests/pytest.mark.rst
条目内容
常用
说明:
- 测试函数命名必须以 test_ 开头
- 测试函数必须返回一个布尔值,True 表示测试通过,False 表示测试失败
主要功能:
- 简洁的断言格式:无需使用特定的断言方法,直接使用 Python 的 assert 语句。
- 丰富的插件系统:通过插件可以扩展 pytest 的功能,如生成测试报告、并行执行测试等。
- 测试夹具(fixtures):可以用来设置测试前后需要的准备和清理工作,提高测试代码的可维护性和复用性。
命名规则:
类名以 Test 开头: TestXxxx
函数名以 test_ 开头: test_xxxx
文件名以 test_ 开头: test_xxxx
选项
基本(general):
-k expr:
根据表达式过滤测试,例如 -k "MyClass and not method"
-m marker:
根据标记过滤测试,例如 -m "slow"
-s(--capture=no):
允许 pytest 显示标准输出(例如,print 语句的输出)
(注:不加-s,插件里面的print还是会显示的,只是test_xxx文件里面的print不显示)
--capture=method
Per-test capturing method: one of fd|sys|no|tee-sys
# 失败重跑
--lf 或 --last-failed:
只重新运行上次失败的测试。
--ff 或 --failed-first:
先运行上次失败的测试,然后运行其他测试。
--pdb
失败(或KeyboardInterrupt)时调用Python调试器
示例:
pytest -x --pdb # 在第一次用例失败时进入PDB
pytest --pdb --maxfail=3 # 在前3次失败是进入PDB
--trace
测试开始时进入PDB
测试报告和输出(Reporting):
-v(--verbose)
启用详细模式,显示每个测试的名称和执行结果
(如:使用它会显示: FAILED,而不使用它只会显示F)
(f)ailed
(E)rror
(s)kipped
(x)failed(@pytest.mark.xfail): 符合预期
(X)passed(@pytest.mark.xfail): 不符合预期
(p)assed
(P)assed with output
(a)ll except passed(p/P)
(A)ll
-q(--quiet)
减少测试输出的详细程度
--tb=style
控制回溯报告风格
pytest --tb=auto # (默认) 第1和最后1条使用详细追溯信息,其他使用简短追溯信息
pytest --tb=long # 详尽,信息丰富的追溯信息格式
pytest --tb=short # 简短的追溯信息格式
pytest --tb=line # 每个失败信息一行
pytest --tb=native # Python标准库格式
pytest --tb=no # 不使用追溯信息
-l, --showlocals
Show locals in tracebacks (disabled by default)
--durations=num:
显示运行时间最久的 num 个测试
-r chars
(default: 'fE')
用于在测试会话结束时显示测试结果摘要,从而可以在大型测试套件中轻松获得所有失败、跳过、标记失败(xfails)等测试结果的清晰图像
Show extra test summary info as specified by chars:
(f)ailed, (E)rror, (s)kipped, (x)failed, (X)passed,
(p)assed, (P)assed with output, (a)ll except passed
(p/P), or (A)ll.
注:参见上面 -v
示例:只查看失败和跳过的用例
pytest -rfs
test session debugging and configuration:
-p name
指定加载或不加载某个插件
加载插件: pytest -p myplugin
禁用插件: pytest -p no:myplugin
--trace-config
Trace considerations of conftest.py files
用于输出详细的配置加载过程和信息。
这对于调试和了解 pytest 的配置如何被解析和加载非常有用。
打印出以下信息:
a. pytest 加载的所有配置文件
b. pytest 使用的所有插件
c. pytest 解析的所有命令行参数
d. pytest 生成的最终配置对象
其他:
--maxfail=num
在达到指定数量的失败后停止测试运行
基本用法
运行特定测试:
# 运行指定目录下的所有测试
pytest test_directory/
# 指定文件名
pytest test_file.py
# 指定函数名
pytest test_file.py::test_method
# 指定类名
pytest test_file.py::TestClass
# 指定类中的某个测试方法
pytest test_file.py::TestClass::test_method
# 通过标记表达式运行测试(执行所有带@pytest.mark.slow装饰器的用例)
pytest -m slow
# 从包中运行测试(导入pkg.testing并使用其文件系统位置来查找和运行测试)
pytest --pyargs pkg.testing
在测试文件中增加:
if __name__ == "__main__":
pytest.main([__file__, "-s"])
等同于:
在命令行执行 `pytest -s` 命令
if __name__ == "__main__":
pytest.main([__file__, "-s", "-v"])
等同于:
在命令行执行 `pytest -s -v` 命令
插件
插件加载顺序(Plugin discovery order at tool startup):
1. by scanning the command line for the `-p no:name` option
2. by loading all builtin plugins.
3. by scanning the command line for the `-p name` option
4. by loading all plugins registered through installed third-party package `entry points`
5. by loading all plugins specified through the `PYTEST_PLUGINS` environment variable
6. by loading all “initial “conftest.py files
a. determine the test paths
a.1 specified on the command line(ex: pytest <path1> <path2>)
a.2 `testpaths` if defined in conftest.py and running from the rootdir
[pytest]
testpaths = <path1> <path2>
a.3 current dir
b. for each test path
b.1 load conftest.py and test*/conftest.py relative to the directory part of the test path
b.2 Before a conftest.py file is loaded, load conftest.py files in all of its parent directories.
b.3 After a conftest.py file is loaded, recursively load all plugins specified in its pytest_plugins variable if present
1. 内置插件:从pytest的内部_pytest目录加载
2. 外部插件:通过 setuptools入口点发现的模块
3. conftest.py plugins:在测试目录中自动发现的模块
pytest_plugins = ("myapp.testsupport.myplugin",)
8 Popular Pytest Plugins:
1. pytest-cov
2. pytest-mock
3. pytest-xdist
4. pytest-timeout
5. pytest-asyncio
6. pytest-sugar
7. pytest-html
8. pytest-profiling
安装:
%pip install pytest
# 通过插件 pytest-html 可以生成 HTML 格式的测试报告:
%pip install pytest-html
# 为 Python 项目提供代码覆盖率报告
# 可以轻松集成到持续集成(CI)管道中
%pip install pytest-cov
# 提供简单但强大的模拟功能
# 用模拟对象替换部分代码,从而允许在受控环境中隔离和测试各个组件
%pip install pytest-mock
# 对 Python 项目进行并行测试
# 可以在多个 CPU 甚至多台机器上运行测试套件,从而大大减少运行大型测试套件所需的时间
%pip install pytest-xdist
# 为测试函数设置超时的简单方法
# 为每个测试指定最大时间限制,之后测试将终止并标记为失败
%pip install pytest-timeout
# 允许您轻松测试异步代码
# 它包括一个事件循环固定装置,允许您在测试中运行异步任务和协程
%pip install pytest-asyncio
# Pytest 插件,它通过提供更详细、更具视觉吸引力的测试结果表示来增强 Pytest 测试执行进度的输出
# 用丰富多彩且信息丰富的输出格式替换了 Pytest 的默认输出格式,具有进度条、详细错误消息以及测试运行结束时的测试结果摘要等功能
%pip install pytest-sugar
# 在测试执行期间轻松分析代码
# 在测试运行时收集分析数据,并生成一份报告,显示哪些函数或代码行执行时间最长
%pip install pytest-profiling
插件-pytest-mock
-
一个用于集成 pytest 和 unittest.mock 的插件,它使得在 pytest 测试中进行 mock 操作变得更加方便和直观。
-
提供 mocker fixture:使得在测试中可以轻松地使用 unittest.mock 模块的功能:
# 可以直接使用 mocker 这个 fixture def test_fetch_data_success(mocker): ... -
安装:
pip install pytest pytest-mock
常用功能(pytest-mock):
1. patch
mocker.patch 是最常用的功能,用于替换某个对象或函数。
可以使用 return_value 参数指定替换后的返回值,或使用 side_effect 指定一个函数来动态返回值。
示例:
def test_patch_example(mocker):
# 替换函数,并指定返回值
mocker.patch('path.to.module.function_name', return_value=42)
assert module.function_name() == 42
# 使用 side_effect 动态返回值
def side_effect(arg):
return arg * 2
mocker.patch('path.to.module.function_name', side_effect=side_effect)
assert module.function_name(21) == 42
2. spy
mocker.spy 用于监视某个函数的调用情况,比如调用次数、传递的参数等,但不改变其原有行为
示例:
def test_spy_example(mocker):
spy = mocker.spy(module, 'function_name')
result = module.function_name(5)
spy.assert_called_once_with(5)
assert result == 10 # 假设函数行为为返回参数的2倍
3. stub
mocker.stub 用于创建一个简单的 mock 对象,它没有任何行为,除非你显式地设置
def test_stub_example(mocker):
stub = mocker.stub(name='example_stub')
stub.return_value = 'stubbed value'
assert stub() == 'stubbed value'
stub.assert_called_once()
示例:
import requests
def fetch_data(url):
response = requests.get(url)
if response.status_code == 200:
return response.json()
return None
def test_fetch_data_success(mocker):
mock_response = mocker.Mock()
mock_response.status_code = 200
mock_response.json.return_value = {'key': 'value'}
mocker.patch('requests.get', return_value=mock_response)
url = 'http://example.com/api/data'
result = fetch_data(url)
assert result == {'key': 'value'}
插件-pytest-asyncio
- 一个用于异步代码测试的 pytest 插件,允许在测试函数中使用 async/await 语法。
- 这个插件特别适合测试需要异步处理的代码,例如基于 asyncio 的异步函数和协程。
基本使用:
# 使用 @pytest.mark.asyncio 装饰器来标记异步测试函数
import asyncio
import pytest
@pytest.mark.asyncio
async def test_example():
await asyncio.sleep(1)
assert 1 == 1
使用异步夹具:
import pytest
@pytest.fixture
async def async_fixture():
await asyncio.sleep(1)
return "async result"
@pytest.mark.asyncio
async def test_with_async_fixture(async_fixture):
assert async_fixture == "async result"
参考
- How to install and use plugins: https://docs.pytest.org/en/latest/how-to/plugins.html
- Writing plugins: https://docs.pytest.org/en/latest/how-to/writing_plugins.html
Hook
Pytest Hook 的类型:
1. Bootstrapping Hooks
2. Initialization Hooks
3. Collection Hooks
4. Test running (runtest) hooks
5. Reporting Hooks
6. Debugging/Interaction Hooks
Ordering of Pytest Hooks:
1. tryfirst=True: Execution as Early as Possible
2. trylast=True: Execution as Late as Possible
3. hookwrapper=True: Hook Wrappers
creates a hook that wraps around all others.
It can execute code both before and after the standard hooks.
示例:
@pytest.hookimpl(hookwrapper=True)
def pytest_collection_modifyitems(items):
# 在all non-wrapper hooks 运行前运行
outcome = yield
# 在all non-wrapper hooks 运行后运行
4. optionalhook=True: 这个参数表示这个钩子函数是可选的,它应该被调用,但可以忽略
pytest_collection_modifyitems()函数:
四个参数:
item:当前测试用例的对象
call:表示测试用例的调用状态,包括 setup, call, teardown 三个阶段
keyword:关键字参数,通常不需要手动传递
outcome:测试结果信息
特殊文件-conftest.py
- conftest.py 是一个特殊的文件名,它允许您定义钩子函数和共享的固定测试资源。
- 当您将钩子函数或其他 pytest 的自定义行为放置在 conftest.py 中时,pytest 会自动加载这些内容,并确保它们在运行测试时生效。
主要功能和用途:
1. Fixture 共享
可以在整个项目中共享 Fixture,不需要在每个测试文件中单独导入
2. 自定义钩子函数
3. 配置选项
4. 插件机制
5. 自定义标记
6. 全局设置和初始化
5个关键函数:
pytest_sessionstart: 测试会话开始
pytest_runtest_setup: 测试用例开始
pytest_runtest_makereport: 测试用例结束
用于在每个测试函数运行后生成测试报告
四个参数:
item:当前测试用例的对象
call:表示测试用例的调用状态,包括 setup, call, teardown 三个阶段
keyword:关键字参数,通常不需要手动传递
outcome:测试结果信息
pytest_runtest_teardown: 测试用例结束
pytest_sessionfinish: 测试会话结束
示例
指定测试路径:
[pytest]
testpaths = testing doc
等同于:
pytest testing doc
注:如果都没有指定会选择当前目录
conftest.py生效分解:
a/conftest.py:
def pytest_runtest_setup(item):
print("---sub--------++++setting up", item)
a/test_sub.py:
def test_sub():
print("-----------test_sub")
test_flat.py:
def test_flat():
print("-----------test_flat")
conftest.py:
def pytest_runtest_setup(item):
print("---main--------++++setting up", item)
执行:
$ pytest test_flat.py --capture=no
---main--------++++setting up <Function test_flat>
-----------test_flat
$ pytest a/test_sub.py --capture=no
---sub--------++++setting up <Function test_sub>
---main--------++++setting up <Function test_sub>
-----------test_sub
$ cd a
$ pytest test_sub.py --capture=no
---sub--------++++setting up <Function test_sub>
-----------test_sub
@pytest.fixture-测试夹具
- 代码复用:通过夹具,可以在多个测试函数中共享相同的初始化代码和清理代码,避免重复代码。
- 依赖注入:测试函数可以通过参数的方式获取夹具提供的资源,pytest 会自动解析并注入这些参数。
- 灵活的作用范围:夹具可以配置为函数级别、模块级别、类级别或会话级别,控制夹具的生命周期。
- 测试夹具用于在测试函数执行之前准备特定的状态或对象,并在测试函数执行之后进行清理工作。
fixture函数的发现顺序:
1. 测试类
2. 测试模块
3. conftest.py文件
4. 内置和第三方插件
参数
scope
通过 scope 参数设置夹具的作用范围:
function(默认): 每个测试函数调用一次
class: 每个测试类调用一次
module: 每个测试模块调用一次
session: 每个测试会话调用一次
package:
autouse
autouse=False(默认)
autouse=True:自动应用该夹具到指定范围内的所有测试。
使用示例:
1. 设置和清理全局环境
@pytest.fixture(scope="session", autouse=True)
def setup_environment():
# 设置全局环境变量
os.environ['API_KEY'] = 'my_api_key'
yield
# 清理全局环境变量
del os.environ['API_KEY']
def test_example():
assert os.getenv('API_KEY') == 'my_api_key'
2. 配置数据库连接
@pytest.fixture(scope="session", autouse=True)
def setup_database():
# 假设这里是连接数据库的代码
db = connect_to_database()
yield db
# 假设这里是断开数据库连接的代码
db.close()
def test_db_query():
# 这里可以使用已经建立的数据库连接
result = execute_query("SELECT 1")
assert result == 1
自带fixture
tmp_path
-
3.9版本新功能
-
mp_path 可以在临时目录中创建一个独立的临时目录以供测试调用
-
示例:
# test_tmp_path.py文件内容 import os CONTENT = u"content" def test_create_file(tmp_path): d = tmp_path / "sub" d.mkdir() p = d / "hello.txt" p.write_text(CONTENT) assert p.read_text() == CONTENT assert len(list(tmp_path.iterdir())) == 1
tmpdir_factory
-
3.8版本新功能
-
tmpdir_factory是一个session范围的fixture,可从任何其他测试用例及fixture中创建任意临时目录
-
例如,假设你的测试套件需要使用程序动态生成在本地磁盘上的一个大图片,你可以整个测试session中只生成一次以节省时间,而不是为每个用例都在自己的tmpdir中计算并生成一次:
-
示例:
# conftest.py文件内容 import pytest @pytest.fixture(scope="session") def image_file(tmpdir_factory): img = compute_expensive_image() fn = tmpdir_factory.mktemp("data").join("img.png") img.save(str(fn)) return fn # contents of test_image.py def test_histogram(image_file): img = load_image(image_file) # 计算和测试histogram
示例
# yield 语句前的代码在每个测试函数运行之前执行,并返回给测试函数使用的资源
# yield 语句后的代码在每个测试函数运行之后执行,用于清理资源(如果有需要)
@pytest.fixture
def sample_fixture():
# 准备工作
# ...
data = {"key": "value"}
yield data
# 清理工作(如果需要)
# ...
# sample_fixture 是作为 test_example 函数的参数
# 在运行测试时,pytest 框架将自动注入该固定装置的返回值作为参数传递给 test_example 函数
def test_sample(sample_fixture):
...
@pytest.hookimpl-测试钩子
@pytest.hookimpl是 pytest 测试框架中用于实现插件机制的一个关键装饰器。它允许开发者编写自定义的插件,以扩展和定制 pytest 的功能。- 【基本概念】pytest.hookimpl 是 pytest 框架的一个核心模块,它基于 Python 的装饰器和钩子概念。插件作者可以使用此装饰器将自定义函数标记为特定钩子的实现。
- 【工作原理】插件作者使用 @pytest.hookimpl 装饰器将自定义函数标记为某个钩子的实现。pytest 框架在测试运行过程中,会在特定的时机(如测试开始、测试结束等)触发相应的钩子调用。受影响的插件中的钩子实现将被调用,并执行相应的逻辑来扩展或定制 pytest 的行为。
- https://pytest-with-eric.com/hooks/pytest-hooks/
Fixtures vs Hooks
Functionality:
Hooks are used to alter the framework’s behavior and react to various testing events.
Fixtures are used for setting up and tearing down test environments and states.
Invocation:
Hooks are invoked
automatically by Pytest based on certain events in the test lifecycle.
Fixtures are invoked
explicitly by naming them as parameters in tests or other fixtures,
or by setting the autouse=True flag.
Scope and Impact:
Hooks have a more global impact, influencing the overall behaviour of the test suite.
Fixtures have a localized impact, managing the environment for
specific tests or groups of tests, controlled by the scope parameter.
pytest.mark
@pytest.mark.skip/skipif 跳过测试
- @pytest.mark.skip 是一个用于在 pytest 测试框架中跳过某些测试用例或测试类的装饰器。其主要作用是临时禁用一些不需要运行的测试
常用于以下几种情况:
1. 测试尚未完成或稳定
当某些测试用例还在开发中或者有已知问题暂时不能解决时,可以使用这个装饰器跳过它们,以便不会影响整体测试的运行。
2. 外部依赖不可用
如果某些测试依赖于外部资源(如数据库、网络服务)而这些资源暂时不可用,使用 @pytest.mark.skip 可以跳过这些测试,避免测试失败。
3. 条件性跳过
根据某些条件决定是否跳过测试,例如特定的平台或环境下运行时跳过某些测试
示例:
import pytest
@pytest.mark.skip(reason="Test is under development")
def test_example():
assert 1 == 1
# 条件性跳过测试
@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
def test_example_on_non_windows():
assert 1 == 1
@pytest.mark.xfail 标记失败
-
<使用@pytest.mark.xfail标记用例>,表示期望这个用例执行失败。
-
标记后的用例会正常执行,只是失败时不再显示堆栈信息,最终的结果有两个:用例执行失败时(XFAIL:符合预期的失败)、用例执行成功时(XPASS:不符合预期的成功)
-
结构:
pytest.mark.xfail(condition=None, *, reason=None, raises=None, run=True, strict=False) 参数: condition位置参数,默认值为None,表示只有满足条件时才标记用例; reason关键字参数,默认值为None,表示可以指定一个reason字符串,说明标记用例的原因; raises关键字参数,默认值为None 可以指定为一个异常类或者多个异常类的元组,表示我们期望用例上报指定的异常; 如果用例的失败不是因为所期望的异常导致的,pytest将会把测试结果标记为FAILED; run关键字参数,默认值为True: 当run=False时,pytest不会再执行测试用例,直接将结果标记为XFAIL; strict关键字参数,默认值为False 当strict=False时,如果用例执行失败,结果标记为XFAIL,表示符合预期的失败;如果用例执行成功,结果标记为XPASS,表示不符合预期的成功; 当strict=True时,如果用例执行成功,结果将标记为FAILED;
示例:
import pytest
@pytest.mark.xfail
def test_example():
assert 1 == 2
@pytest.mark.asyncio
- 参见: 插件-pytest-asyncio
@pytest.mark.parametrize
- allows you to run a test function multiple times with different sets of arguments.
- This is especially useful for testing functions with multiple input scenarios in a concise and readable way.
示例:
def add(a, b):
return a + b
import pytest
@pytest.mark.parametrize("a, b, expected", [
(1, 2, 3),
(4, 5, 9),
(-1, -2, -3),
(0, 0, 0),
])
def test_add(a, b, expected):
assert add(a, b) == expected
"""
结果:
normal/pytest/mark/test_mark_parametrize.py::test_add[1-2-3] PASSED
normal/pytest/mark/test_mark_parametrize.py::test_add[4-5-9] PASSED
normal/pytest/mark/test_mark_parametrize.py::test_add[-1--2--3] PASSED
normal/pytest/mark/test_mark_parametrize.py::test_add[0-0-0] PASSED
"""