pytest_collection_modifyitems 是 pytest 的一个钩子函数,用于在测试用例收集完成后修改或操作测试用例列表。它允许你在运行测试之前对收集到的测试用例进行排序、过滤、动态添加标记等操作。
下面是这个钩子函数的详细解释和常见用法:
1. pytest_collection_modifyitems 的基本定义
钩子函数的签名如下:
def pytest_collection_modifyitems(config, items):
"""
pytest_collection_modifyitems 是 pytest 在收集到所有测试用例后调用的钩子。
:param config: pytest 的配置信息对象,可以访问命令行参数等
:param items: 收集到的测试用例列表(pytest.Item 对象)
"""
config:pytest 的配置信息对象,可以通过它访问命令行参数(如config.getoption)或配置文件。items:pytest 收集到的测试用例列表,包含所有待运行的测试用例。你可以对该列表执行操作(如排序、筛选、添加标记等)。
2. 常见用法
2.1 修改测试用例的执行顺序
可以通过对 items 列表进行排序来改变测试用例的执行顺序。
示例:按测试用例名称排序
def pytest_collection_modifyitems(config, items):
# 按测试用例名称排序
items.sort(key=lambda item: item.name)
示例:按自定义标记排序
如果你为测试用例添加了自定义标记(如 @pytest.mark.order),可以根据标记的值对测试用例进行排序:
def pytest_collection_modifyitems(config, items):
# 按标记顺序排序
items.sort(key=lambda item: item.get_closest_marker("order").args[0] if item.get_closest_marker("order") else 0)
在测试代码中:
import pytest
@pytest.mark.order(2)
def test_case_2():
assert True
@pytest.mark.order(1)
def test_case_1():
assert True
@pytest.mark.order(3)
def test_case_3():
assert True
运行后,测试用例会按照 order 标记的顺序执行。
2.2 动态添加标记
可以根据测试用例的名称、参数或其他条件动态为测试用例添加标记。
示例:为特定名称的用例添加标记
def pytest_collection_modifyitems(config, items):
for item in items:
if "slow" in item.name: # 如果用例名称中包含 "slow"
item.add_marker(pytest.mark.slow)
运行时,你可以使用 pytest -m slow 只运行这些标记的用例。
2.3 过滤测试用例
如果需要在运行前动态过滤掉某些测试用例,可以直接从 items 列表中移除它们。
示例:过滤掉所有名称中包含 "skip" 的测试用例
def pytest_collection_modifyitems(config, items):
items[:] = [item for item in items if "skip" not in item.name]
运行时,这些包含 skip 的测试用例将不会被执行。
2.4 根据命令行参数动态筛选用例
可以通过 config.getoption 读取命令行参数来动态筛选测试用例。
示例:只运行包含某关键字的用例
def pytest_addoption(parser):
parser.addoption("--keyword", action="store", default=None, help="只运行包含指定关键字的测试用例")
def pytest_collection_modifyitems(config, items):
keyword = config.getoption("--keyword")
if keyword:
# 过滤出名称包含关键字的用例
items[:] = [item for item in items if keyword in item.name]
运行时指定关键字:
pytest --keyword test
2.5 动态标记参数化测试用例
对于参数化的测试用例,可以在收集时根据参数动态添加标记。
示例:为测试用例添加参数相关的标记
def pytest_collection_modifyitems(config, items):
for item in items:
if hasattr(item, "callspec"):
params = item.callspec.params
# 根据参数值动态添加标记
if "username" in params and params["username"] == "admin":
item.add_marker(pytest.mark.admin)
在测试代码中:
import pytest
@pytest.mark.parametrize("username", ["user", "admin"])
def test_login(username):
assert username in ["user", "admin"]
运行时,pytest -m admin 会只运行参数为 "admin" 的测试用例。
2.6 为测试用例添加自定义元数据
可以动态为测试用例添加元数据,方便后续分析或扩展。
示例:为用例添加描述信息
def pytest_collection_modifyitems(config, items):
for item in items:
if "login" in item.name:
item.user_properties.append(("description", "Login related test"))
elif "payment" in item.name:
item.user_properties.append(("description", "Payment related test"))
在测试代码中,可以通过 item.user_properties 访问这些元数据。
2.7 按 YML 文件中的字段动态标记用例
如果测试用例来源于 YML 文件,可以在收集时根据 YML 文件中的字段动态为用例添加标记。
示例:基于 YML 文件的 marks 字段添加标记
假设 YML 文件如下:
test_cases:
- name: "test_login_success"
marks: ["smoke", "regression"]
- name: "test_login_failure"
marks: ["regression"]
测试代码:
import pytest
def pytest_collection_modifyitems(config, items):
# 加载 YML 文件
import yaml
with open("test_cases.yml", "r") as f:
test_cases = yaml.safe_load(f)["test_cases"]
# 为用例动态添加标记
for item in items:
for case in test_cases:
if case["name"] == item.name:
for mark in case.get("marks", []):
item.add_marker(pytest.mark.__getattr__(mark))
然后可以用 pytest -m smoke 或 pytest -m regression 来选择性运行用例。
3. 注意事项
-
items是可变列表: 对items列表的修改会直接影响 pytest 收集到的测试用例。可以通过以下方式操作:items.sort():排序items[:] = [...]:替换整个列表items.remove(item):移除特定用例
-
避免误操作:
- 不要删除 pytest 本身的系统关键字或对非测试用例的对象操作。
-
与其他插件的兼容性: 如果使用了其他插件(如
pytest-order等),确保pytest_collection_modifyitems的逻辑不会与插件冲突。
4. 总结
pytest_collection_modifyitems 是非常强大的钩子,可以帮助你在测试用例收集阶段动态调整用例的行为。它的常见用法包括:
- 修改测试用例执行顺序。
- 动态添加标记(Mark)。
- 过滤或筛选测试用例。
- 根据参数化动态标记用例。
- 根据外部数据文件(如 YML)动态操作用例。
通过合理使用这个钩子,可以使测试用例的管理更加灵活和智能化!