4.pytest 启动入口
pytest 的启动方式有三种:
- 命令行
pytest
2. 命令行
python -m pytest
3. python 代码
import pytest
pytest.main()
这三种方式都将执行_pytest.config.main
函数, 其源码如下
def main(
args: Optional[Union[List[str], "os.PathLike[str]"]] = None,
plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None,
) -> Union[int, ExitCode]:
"""Perform an in-process test run.
:param args: List of command line arguments.
:param plugins: List of plugin objects to be auto-registered during initialization.
:returns: An exit code.
"""
try:
try:
config = _prepareconfig(args, plugins)
except ConftestImportFailure as e:
exc_info = ExceptionInfo.from_exc_info(e.excinfo)
tw = TerminalWriter(sys.stderr)
tw.line(f"ImportError while loading conftest '{e.path}'.", red=True)
exc_info.traceback = exc_info.traceback.filter(
filter_traceback_for_conftest_import_failure
)
exc_repr = (
exc_info.getrepr(style="short", chain=False)
if exc_info.traceback
else exc_info.exconly()
)
formatted_tb = str(exc_repr)
for line in formatted_tb.splitlines():
tw.line(line.rstrip(), red=True)
return ExitCode.USAGE_ERROR
else:
try:
ret: Union[ExitCode, int] = config.hook.pytest_cmdline_main(
config=config
)
try:
return ExitCode(ret)
except ValueError:
return ret
finally:
config._ensure_unconfigure()
except UsageError as e:
tw = TerminalWriter(sys.stderr)
for msg in e.args:
tw.line(f"ERROR: {msg}\n", red=True)
return ExitCode.USAGE_ERROR
从源码可以了解到,main 函数主要做了这么几件事情:
- 加载配置(15 行)
- 调用插件 hook(34 行)
- 返回结束代码(31、38、47 行)
由此可知,pytest 启动后,便是通过调用插件的方式完成各项作业。
5. 小结
通过对 pytest 源码的初步分析,现在我对pytest有了全新的认识:
pytest 内部使用了大量插件,
甚至可以说 :pytest 本身就是由 N 个插件的组合而来!
这么多内部插件,还有更多的第三方开发插件,都是通过 pluggy 相互交织在一起,实现出各种各样的功能,使pytest生态庞大起来
我现在也有了对pytest 的剖析基本思路:
- 掌握插件系统 pluggy 的运行机制
- 掌握 pytest 内容定义 hook 及触发时机
- 掌握 pytest 内置插件功能及具体实现方式
- 掌握pytest常见功能的运行流程和细节,比如配置文件、conftest、收集用例、捕获输入等
下节预告:拿下 pluggy!
本文系《pytest源码剖析》系列内容,首发于公众号
正在连载,欢迎关注