Python高级技术之:如何编写`Pytest`插件,扩展`Pytest`的功能。

各位观众老爷,晚上好!今天咱们来聊聊如何用 Python 打造自己的 Pytest 插件,让你的测试框架变得更智能、更个性化,甚至还能偷懒!

Pytest 插件:就像乐高积木一样,让测试更灵活

Pytest 本身已经很强大了,但有时候,我们需要一些定制化的功能,比如:

  • 自定义错误报告格式
  • 与特定的 CI/CD 工具集成
  • 自动生成测试数据
  • 控制测试用例的执行顺序
  • 等等…

这时候,Pytest 插件就派上用场了。它可以让你像搭乐高积木一样,把各种功能模块组合起来,打造一个专属的测试王国。

插件的本质:Hook 函数

Pytest 插件的核心是 Hook 函数。可以把 Hook 函数想象成 Pytest 预留的一些“钩子”,你可以在这些“钩子”上挂上自己的代码,让 Pytest 在特定的时机执行你的逻辑。

Pytest 提供了一系列的 Hook 函数,覆盖了测试过程的各个阶段,比如:

  • pytest_configure(config): 在 Pytest 初始化时调用,可以用来配置 Pytest 的行为。
  • pytest_sessionstart(session): 在测试会话开始时调用。
  • pytest_collection_modifyitems(config, items): 在收集完测试用例后调用,可以用来修改测试用例列表。
  • pytest_itemcollected(item): 收集到测试用例时调用。
  • pytest_runtest_setup(item): 在执行测试用例之前调用,可以用来做一些准备工作。
  • pytest_runtest_call(item): 执行测试用例时调用。
  • pytest_runtest_teardown(item): 在执行测试用例之后调用,可以用来做一些清理工作。
  • pytest_runtest_makereport(item, call): 在测试用例执行完毕后调用,可以用来生成测试报告。
  • pytest_sessionfinish(session, exitstatus): 在测试会话结束时调用。

等等等等, 还有很多很多, 具体可以看官方文档。

编写你的第一个 Pytest 插件

咱们先来个简单的例子,创建一个插件,在测试开始和结束时打印一些信息。

  1. 创建插件文件:
    创建一个名为 conftest.py 的文件。 conftest.py 是 Pytest 默认的插件加载文件,Pytest 会自动扫描这个文件,并加载其中的 Hook 函数。 这个文件放在你的测试目录的根目录下。

  2. 编写 Hook 函数:
    conftest.py 中编写以下代码:

import pytest

def pytest_sessionstart(session):
    print("n测试开始啦!")

def pytest_sessionfinish(session, exitstatus):
    print("n测试结束啦!")
    print(f"测试结果:{exitstatus}")
  1. 运行测试:
    随便写一个简单的测试用例,比如:
# test_example.py
def test_addition():
    assert 1 + 1 == 2

def test_subtraction():
    assert 5 - 3 == 2
然后在命令行中运行 `pytest` 命令。 你会看到在测试开始和结束时,分别打印了 "测试开始啦!" 和 "测试结束啦!"。

插件的安装与使用

Pytest 插件的安装方式主要有两种:

  • 本地插件: 就像上面的例子,把 conftest.py 文件放在测试目录的根目录下,Pytest 会自动加载。
  • 外部插件: 通过 pip 安装,比如 pip install pytest-cov。 安装完成后,Pytest 会自动识别并加载这些插件。

插件的命名规范

为了避免插件名称冲突,建议遵循以下命名规范:

  • 插件名称以 pytest- 开头。
  • 插件名称应该具有描述性,能够清晰地表达插件的功能。

更复杂的例子:自定义错误报告

接下来,我们来创建一个更复杂的插件,自定义错误报告的格式。

  1. 创建插件文件:
    同样,创建一个 conftest.py 文件。

  2. 编写 Hook 函数:
    conftest.py 中编写以下代码:

import pytest
from pygments import highlight
from pygments.lexers import PythonLexer
from pygments.formatters import Terminal256Formatter

def pytest_terminal_summary(terminalreporter, exitstatus, config):
    """在测试结束后,自定义错误报告的格式。"""
    if exitstatus == pytest.ExitCode.TESTS_FAILED:
        terminalreporter.write_sep("=", "自定义错误报告")
        for item in terminalreporter.stats.get("failed", []):
            # 获取测试用例的名称
            test_name = item.nodeid

            # 获取测试用例的错误信息
            error_message = item.longreprtext

            # 使用 pygments 高亮显示代码
            highlighted_code = highlight(
                error_message, PythonLexer(), Terminal256Formatter()
            )

            # 打印测试用例的名称和错误信息
            terminalreporter.write(f"n{test_name}n", bold=True)
            terminalreporter.write(highlighted_code)
这个插件使用了 `pytest_terminal_summary` Hook 函数,在测试结束后,遍历所有失败的测试用例,获取测试用例的名称和错误信息,并使用 `pygments` 库高亮显示代码,然后打印到终端。

**注意:**  需要先安装 `pygments` 库:`pip install pygments`。
  1. 运行测试:
    修改一下测试用例,故意制造一些错误,比如:
# test_example.py
def test_addition():
    assert 1 + 1 == 3  # 故意写错

def test_subtraction():
    assert 5 - 3 == 2
然后在命令行中运行 `pytest` 命令。 你会看到错误报告的格式发生了变化,代码被高亮显示,更容易阅读和理解。

使用 pytest_addoption 添加命令行选项

有时候,我们希望在运行测试时,通过命令行选项来控制插件的行为。 可以使用 pytest_addoption Hook 函数来添加自定义的命令行选项。

  1. 创建插件文件:
    创建一个 conftest.py 文件。

  2. 编写 Hook 函数:
    conftest.py 中编写以下代码:

import pytest

def pytest_addoption(parser):
    parser.addoption(
        "--env", action="store", default="dev", help="运行环境:dev/test/prod"
    )

@pytest.fixture
def env(request):
    return request.config.getoption("--env")

def pytest_sessionstart(session):
    env = session.config.getoption("--env")
    print(f"n当前运行环境:{env}")
这个插件添加了一个名为 `--env` 的命令行选项,用于指定运行环境。  `pytest_addoption` 函数用于添加命令行选项,`request.config.getoption` 函数用于获取命令行选项的值。

同时,我们创建了一个名为 `env` 的 fixture,方便在测试用例中使用。
  1. 使用命令行选项:
    在命令行中运行 pytest --env=test 命令。 你会看到在测试开始时,打印了 "当前运行环境:test"。

    在测试用例中,可以使用 env fixture 来获取运行环境:

# test_example.py
def test_addition(env):
    if env == "dev":
        assert 1 + 1 == 2
    elif env == "test":
        assert 1 + 1 == 2
    elif env == "prod":
        assert 1 + 1 == 2
    else:
        raise ValueError("Invalid environment")

Hook 函数的执行顺序

当多个插件都实现了同一个 Hook 函数时,Pytest 会按照一定的顺序执行这些 Hook 函数。 默认情况下,Pytest 会按照插件的加载顺序执行 Hook 函数。 可以使用 tryfirsttrylast 参数来调整 Hook 函数的执行顺序。

  • tryfirst=True: 将 Hook 函数放在第一个执行。
  • trylast=True: 将 Hook 函数放在最后一个执行。

例如:

import pytest

@pytest.hookimpl(tryfirst=True)
def pytest_sessionstart(session):
    print("n第一个执行的 sessionstart")

@pytest.hookimpl(trylast=True)
def pytest_sessionfinish(session, exitstatus):
    print("n最后一个执行的 sessionfinish")

def pytest_sessionstart(session):
    print("n中间执行的 sessionstart")

def pytest_sessionfinish(session, exitstatus):
    print("n中间执行的 sessionfinish")

在这个例子中,tryfirst=Truepytest_sessionstart 函数会第一个执行,trylast=Truepytest_sessionfinish 函数会最后一个执行。

插件的调试

调试 Pytest 插件可能会比较棘手,可以使用以下方法:

  • 使用 print 语句: 在 Hook 函数中添加 print 语句,输出一些调试信息。
  • 使用 pdb 调试器: 在 Hook 函数中添加 import pdb; pdb.set_trace() 语句,进入 pdb 调试器。
  • 使用 PyCharm 等 IDE 的调试功能: 配置 PyCharm 等 IDE,可以方便地调试 Pytest 插件。

常用的 Pytest 插件

Pytest 社区有很多优秀的插件,可以帮助你提高测试效率。 以下是一些常用的 Pytest 插件:

插件名称 功能
pytest-cov 代码覆盖率测试。
pytest-xdist 分布式测试,可以并行执行测试用例,加快测试速度。
pytest-mock 提供 Mock 对象,方便进行单元测试。
pytest-sugar 改进 Pytest 的输出格式,使其更易于阅读。
pytest-django 方便在 Django 项目中使用 Pytest 进行测试。
pytest-flask 方便在 Flask 项目中使用 Pytest 进行测试。
pytest-randomly 随机化测试用例的执行顺序,可以发现一些隐藏的 Bug。
pytest-rerunfailures 允许失败的测试用例自动重新运行,可以解决一些偶发性的问题。
pytest-html 生成 HTML 格式的测试报告。
pytest-ordering 控制测试用例的执行顺序。

总结

Pytest 插件是扩展 Pytest 功能的强大工具。 通过编写 Hook 函数,你可以自定义 Pytest 的行为,满足各种测试需求。 希望今天的讲座能够帮助你入门 Pytest 插件的开发。

最后的忠告

写插件一时爽,一直写一直爽。 但是,请记住,插件虽好,不要贪多。 插件越多,维护成本越高。 所以,在编写插件之前,一定要仔细评估,是否真的需要这个插件,是否能够用其他方式解决问题。

好了,今天的讲座就到这里。 感谢大家的观看! 咱们下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注