我可以不吃啊 发表于 2025-4-3 22:56:00

将Python第三方库转换为真正的 pytest 插件

题目配景

在使用 pytest 进行测试时,我遇到了这样的错误:
Defining 'pytest_plugins' in a non-top-level conftest is no longer supported: It affects the entire test suite instead of just below the conftest as expected.
这个错误通常出现在测试工程的结构中有多层 conftest.py 文件,并且在非顶层的 conftest 中定义了 pytest_plugins。从 pytest 7.0.0 版本开始,这种用法被废弃,因为它会影响整个测试套件而不仅仅是该 conftest.py 以下的测试。
案例中,测试工程根目录下有一个 conftest.py,此中包含:
pytest_plugins = ["my_python_lib.base.testbase.conftest"]
这里 my_python_lib 是一个自定义的 Python 第三方库,测试工程中的用例必要调用 my_python_lib.base.testbase.conftest 中的 fixture。
最佳办理方案:将库转换为真正的 pytest 插件

将我们的库转换为一个真正的 pytest 插件是最优雅和最可维护的办理方案。这样不仅办理了当前题目,还提高了代码的可复用性和可扩展性。
步调 1:重构库结构

起首,调整库结构,确保 fixture 代码位于符合的模块中:
my_python_lib/
├── __init__.py
├── base/
│   ├── __init__.py
│   ├── testbase/
│   │   ├── __init__.py
│   │   ├── fixture.py# 将 conftest.py 中的 fixture 移到这里
│   │   └── plugin.py   # 新建的插件入口点文件
步调 2:创建插件入口点文件

创建 plugin.py 文件,导入全部 fixture 并定义任何必要的 pytest 钩子:
# my_python_lib/base/testbase/plugin.py
from .fixture import *# 导入所有的 fixture

# 在这里可以定义 pytest 钩子函数
def pytest_configure(config):
    """
    pytest 配置阶段被调用的钩子
    可以在这里进行全局配置
    """
    pass
步调 3:修改库的 setup.py

在库的 setup.py 中添加 pytest 插件的入口点:
from setuptools import setup, find_packages

setup(
    name="my_python_lib",
    version="1.0.0",
    packages=find_packages(),
    description="我的测试工具库",
    author="Your Name",
    author_email="your.email@example.com",
   
    # 添加 pytest 插件入口点,这里的 pytest11 是一个固定写法,了解到这个情况的我,感觉这简直“逆天”
    entry_points={
      'pytest11': [
            'my_lib = my_python_lib.base.testbase.plugin',
      ],
    },
    # 添加依赖
    install_requires=[
      'pytest>=6.0.0',
      # 其他依赖...
    ],
)
步调 4:重新安装库

pip uninstall -y my_python_lib# 先卸载当前版本
cd /path/to/my_python_lib
pip install -e .# 以开发模式安装
步调 5:修改测试项目

删除测试项目中 conftest.py 中的 pytest_plugins 定义,因为现在插件会自动加载:
# 测试项目的 conftest.py# 删除这一行:# pytest_plugins = ["my_python_lib.base.testbase.conftest"]
# 可以添加其他测试项目特定的 fixturedef pytest_configure(config):    # 测试项目特定的配置    pass 步调 6:验证插件是否精确安装

运行以下命令验证插件是否被精确识别:
python -m pytest --trace-config
应该能看到雷同这样的输出:
pytest11 plugin registration SETUP: my_python_lib.base.testbase.plugin
代码示例

fixture.py 示例

# my_python_lib/base/testbase/fixture.py
import pytest
import os
import tempfile

@pytest.fixture
def temp_dir():
    """提供一个临时目录"""
    with tempfile.TemporaryDirectory() as temp_dir:
      yield temp_dir

@pytest.fixture
def temp_file():
    """提供一个临时文件"""
    with tempfile.NamedTemporaryFile(delete=False) as temp_file:
      file_path = temp_file.name
   
    yield file_path
   
    # 测试后清理
    if os.path.exists(file_path):
      os.remove(file_path)

@pytest.fixture
def sample_data():
    """提供示例数据"""
    return {
      "name": "test",
      "values": ,
      "metadata": {
            "version": "1.0",
            "type": "test-data"
      }
    }
plugin.py 完备示例

# my_python_lib/base/testbase/plugin.py
from .fixture import *

def pytest_configure(config):
    """配置 pytest 环境"""
    config.addinivalue_line(
      "markers", "slow: 标记执行较慢的测试"
    )

def pytest_addoption(parser):
    """添加命令行选项"""
    parser.addoption(
      "--skip-slow",
      action="store_true",
      default=False,
      help="跳过标记为 slow 的测试"
    )

def pytest_collection_modifyitems(config, items):
    """修改收集的测试项"""
    if config.getoption("--skip-slow"):
      skip_slow = pytest.mark.skip(reason="跳过慢测试 (--skip-slow 选项)")
      for item in items:
            if "slow" in item.keywords:
                item.add_marker(skip_slow)
测试示例

# 测试文件示例 test_utils.py
import pytest
import os
import json

def test_temp_dir_fixture(temp_dir):
    """测试临时目录 fixture"""
    # 在临时目录创建文件
    file_path = os.path.join(temp_dir, "test.txt")
    with open(file_path, "w") as f:
      f.write("Hello, World!")
   
    # 验证文件创建成功
    assert os.path.exists(file_path)
    with open(file_path, "r") as f:
      content = f.read()
    assert content == "Hello, World!"

@pytest.mark.slow
def test_sample_data_manipulation(sample_data, temp_file):
    """测试数据操作(标记为慢测试)"""
    # 将示例数据写入临时文件
    with open(temp_file, "w") as f:
      json.dump(sample_data, f)
   
    # 读取并验证数据
    with open(temp_file, "r") as f:
      loaded_data = json.load(f)
   
    assert loaded_data == sample_data
    assert loaded_data["metadata"]["version"] == "1.0"
    assert sum(loaded_data["values"]) == 15
使用方法

安装了这个 pytest 插件后,你可以在任何测试项目中直接使用这些 fixture,无需额外导入或配置:

[*] 安装你的库:
pip install my_python_lib

[*] 在测试文件中直接使用 fixture:
def test_file_operations(temp_dir, temp_file):
    # 自动获取临时目录和临时文件
    with open(temp_file, 'w') as f:
      f.write('测试内容')
   
    assert os.path.exists(temp_file)

[*] 使用示例数据 fixture:
def test_data_processing(sample_data):
    # sample_data 自动可用
    assert sample_data["name"] == "test"
    assert len(sample_data["values"]) == 5

[*] 跳过慢测试:
python -m pytest --skip-slow

[*] 运行测试并检察全部可用的标志:
python -m pytest --markers

这些 fixture 可以组合使用,也可以在本身的 conftest.py 中扩展它们,为它们提供自定义行为。
上风


[*]符合 pytest 最佳实践 - 使用官方保举的插件机制
[*]避免警告和错误 - 不再使用不保举的 pytest_plugins 方式
[*]更好的可发现性 - 自动注册 fixture,无需显式导入
[*]可配置性 - 可以添加命令行选项和配置项
[*]模块化 - 更容易维护和扩展
[*]可重用性 - 可以在多个项目中使用同一套测试工具
总结

通过将测试工具库转换为真正的 pytest 插件,我们不仅办理了特定的错误题目,还提高了代码质量和可维护性。这种方法固然前期工作量稍大,但从长远来看更加结实,尤其是当测试库必要在多个项目中使用时。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 将Python第三方库转换为真正的 pytest 插件