之前花了很多篇幅来介绍 pluggy
这个插件框架。核心原因就是因为其实 pytest
是一个完全基于 pluggy
开发的测试框架,这个也可以解释为什么说 pytest
是一个很灵活的测试框架, 支持很多插件 (https://docs.pytest.org/en/7.0.x/reference/plugin_list.html)。 其实原因就在这里,因为它的内核本来就是一个插件引擎 。
看下 pytest
的源码里面有一个 hookspec.py
的文件,这里定义了 pytest 申明的所有符合 pluggy 规范的 HookSpec
,这里例举了部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
@hookspec(firstresult=True) def pytest_cmdline_main(config: "Config") -> Optional[Union["ExitCode", int]]: """Called for performing the main command line action. The default implementation will invoke the configure hooks and runtest_mainloop.
Stops at first non-None result, see :ref:`firstresult`.
:param pytest.Config config: The pytest config object. """
def pytest_load_initial_conftests( early_config: "Config", parser: "Parser", args: List[str] ) -> None: """Called to implement the loading of initial conftest files ahead of command line option parsing.
.. note:: This hook will not be called for ``conftest.py`` files, only for setuptools plugins.
:param pytest.Config early_config: The pytest config object. :param List[str] args: Arguments passed on the command line. :param pytest.Parser parser: To add command line options. """
|
这里定义的所有 HookSpec,在 src/_pytest
的不同模块中有对应的 HookImpl 实现,并通过 PytestPluginManager
这个继承自 pluggy
- PluginManager
的核心组件,通过预先定义的一个完整测试流程,来组装不同的插件从而实现测试逻辑。
通过阅读源码来分析 PyTest
是如何通过组装这些 Hook 来实现测试流程是比较麻烦的,这里就又可以用到 pluggy
的 enable_tracing
方式,来显示记录一次执行过程中的所有 plugin 执行情况和顺序 。方式很简单,只要在运行测试的时候,设置环境变量 PYTEST_DEBUG=1
, 就会在 consoel 打印对应的插件执行过程,例如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
/Users/xxx/.pyenv/versions/pytesttest/bin/python /Users/xxx/Workspace/pytesttest/pytesttracer/pytest_trace.py pytest_plugin_registered [hook] plugin: <_pytest.config.Config object at 0x10193aac0> manager: <_pytest.config.PytestPluginManager object at 0x1010ed880> finish pytest_plugin_registered --> [] [hook] pytest_addoption [hook] parser: <_pytest.config.argparsing.Parser object at 0x10193ab80> pluginmanager: <_pytest.config.PytestPluginManager object at 0x1010ed880> finish pytest_addoption --> [] [hook] pytest_addoption [hook] parser: <_pytest.config.argparsing.Parser object at 0x10193ab80> pluginmanager: <_pytest.config.PytestPluginManager object at 0x1010ed880> finish pytest_addoption --> [] [hook] pytest_plugin_registered [hook] plugin: <module '_pytest.mark' from '/Users/markshao/.pyenv/versions/pytesttest/lib/python3.9/site-packages/_pytest/mark/__init__.py'> manager: <_pytest.config.PytestPluginManager object at 0x1010ed880> finish pytest_plugin_registered --> [] [hook] ....
|
为了方便可视化,我基于 graphviz
手撸了一个可视化的 trace 版本,并且把一些重点的 HookSpec 高亮了,后面也会给大家做进一步的实现分析。图有点大,本想弄个 FancyApp 支持 Zoom In/Out 的,可惜前端技术有限,大家可以下载到本地进行缩放查看
原文链接:
https://markshao.github.io/2022/06/04/pytest-hook-logic/