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

文章目录

    • 问题背景
    • [最佳解决方案:将库转换为真正的 pytest 插件](#最佳解决方案:将库转换为真正的 pytest 插件)
      • [步骤 1:重构库结构](#步骤 1:重构库结构)
      • [步骤 2:创建插件入口点文件](#步骤 2:创建插件入口点文件)
      • [步骤 3:修改库的 setup.py](#步骤 3:修改库的 setup.py)
      • [步骤 4:重新安装库](#步骤 4:重新安装库)
      • [步骤 5:修改测试项目](#步骤 5:修改测试项目)
      • [步骤 6:验证插件是否正确安装](#步骤 6:验证插件是否正确安装)
    • 代码示例
      • [`fixture.py` 示例](#fixture.py 示例)
      • [`plugin.py` 完整示例](#plugin.py 完整示例)
      • 测试示例
    • 使用方法
    • 优势
    • 总结

问题背景

在使用 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,其中包含:

python 复制代码
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 钩子:

python 复制代码
# my_python_lib/base/testbase/plugin.py
from .fixture import *  # 导入所有的 fixture

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

步骤 3:修改库的 setup.py

在库的 setup.py 中添加 pytest 插件的入口点:

python 复制代码
from setuptools import setup, find_packages

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

步骤 4:重新安装库

bash 复制代码
pip uninstall -y my_python_lib  # 先卸载当前版本
cd /path/to/my_python_lib
pip install -e .  # 以开发模式安装

步骤 5:修改测试项目

删除测试项目中 conftest.py 中的 pytest_plugins 定义,因为现在插件会自动加载:

python 复制代码
# 测试项目的 conftest.py
# 删除这一行:
# pytest_plugins = ["my_python_lib.base.testbase.conftest"]

# 可以添加其他测试项目特定的 fixture
def pytest_configure(config):
    # 测试项目特定的配置
    pass

步骤 6:验证插件是否正确安装

运行以下命令验证插件是否被正确识别:

bash 复制代码
python -m pytest --trace-config

应该能看到类似这样的输出:

bash 复制代码
pytest11 plugin registration SETUP: my_python_lib.base.testbase.plugin

代码示例

fixture.py 示例

python 复制代码
# 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": [1, 2, 3, 4, 5],
        "metadata": {
            "version": "1.0",
            "type": "test-data"
        }
    }

plugin.py 完整示例

python 复制代码
# 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)

测试示例

python 复制代码
# 测试文件示例 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,无需额外导入或配置:

  1. 安装你的库:

    bash 复制代码
    pip install my_python_lib
  2. 在测试文件中直接使用 fixture:

    python 复制代码
    def test_file_operations(temp_dir, temp_file):
        # 自动获取临时目录和临时文件
        with open(temp_file, 'w') as f:
            f.write('测试内容')
        
        assert os.path.exists(temp_file)
  3. 使用示例数据 fixture:

    python 复制代码
    def test_data_processing(sample_data):
        # sample_data 自动可用
        assert sample_data["name"] == "test"
        assert len(sample_data["values"]) == 5
  4. 跳过慢测试:

    bash 复制代码
    python -m pytest --skip-slow
  5. 运行测试并查看所有可用的标记:

    bash 复制代码
    python -m pytest --markers

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

优势

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

总结

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

相关推荐
FINE!(正在努力!)2 天前
PyTest框架学习
学习·pytest
程序员杰哥2 天前
接口自动化测试之pytest 运行方式及前置后置封装
自动化测试·软件测试·python·测试工具·职场和发展·测试用例·pytest
测试老哥3 天前
Pytest+Selenium UI自动化测试实战实例
自动化测试·软件测试·python·selenium·测试工具·ui·pytest
水银嘻嘻3 天前
07 APP 自动化- appium+pytest+allure框架封装
python·appium·自动化·pytest
天才测试猿3 天前
接口自动化测试之pytest接口关联框架封装
自动化测试·软件测试·python·测试工具·职场和发展·测试用例·pytest
not coder4 天前
Pytest Fixture 详解
数据库·pytest
not coder5 天前
pytest 常见问题解答 (FAQ)
开发语言·python·pytest
程序员的世界你不懂5 天前
(1)pytest简介和环境准备
pytest
not coder5 天前
Pytest Fixture 是什么?
数据库·oracle·pytest
Tester_孙大壮5 天前
pytest中的元类思想与实战应用
pytest