各位观众老爷,晚上好!今天咱们来聊聊如何用 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 插件
咱们先来个简单的例子,创建一个插件,在测试开始和结束时打印一些信息。
-
创建插件文件:
创建一个名为conftest.py
的文件。conftest.py
是 Pytest 默认的插件加载文件,Pytest 会自动扫描这个文件,并加载其中的 Hook 函数。 这个文件放在你的测试目录的根目录下。 -
编写 Hook 函数:
在conftest.py
中编写以下代码:
import pytest
def pytest_sessionstart(session):
print("n测试开始啦!")
def pytest_sessionfinish(session, exitstatus):
print("n测试结束啦!")
print(f"测试结果:{exitstatus}")
- 运行测试:
随便写一个简单的测试用例,比如:
# 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-
开头。 - 插件名称应该具有描述性,能够清晰地表达插件的功能。
更复杂的例子:自定义错误报告
接下来,我们来创建一个更复杂的插件,自定义错误报告的格式。
-
创建插件文件:
同样,创建一个conftest.py
文件。 -
编写 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`。
- 运行测试:
修改一下测试用例,故意制造一些错误,比如:
# test_example.py
def test_addition():
assert 1 + 1 == 3 # 故意写错
def test_subtraction():
assert 5 - 3 == 2
然后在命令行中运行 `pytest` 命令。 你会看到错误报告的格式发生了变化,代码被高亮显示,更容易阅读和理解。
使用 pytest_addoption
添加命令行选项
有时候,我们希望在运行测试时,通过命令行选项来控制插件的行为。 可以使用 pytest_addoption
Hook 函数来添加自定义的命令行选项。
-
创建插件文件:
创建一个conftest.py
文件。 -
编写 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,方便在测试用例中使用。
-
使用命令行选项:
在命令行中运行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 函数。 可以使用 tryfirst
和 trylast
参数来调整 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=True
的 pytest_sessionstart
函数会第一个执行,trylast=True
的 pytest_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 插件的开发。
最后的忠告
写插件一时爽,一直写一直爽。 但是,请记住,插件虽好,不要贪多。 插件越多,维护成本越高。 所以,在编写插件之前,一定要仔细评估,是否真的需要这个插件,是否能够用其他方式解决问题。
好了,今天的讲座就到这里。 感谢大家的观看! 咱们下次再见!