本文系《pytest源码剖析》系列内容
正在连载,欢迎关注
4. 内置插件 runner
插件路径:_pytest.runner
实现的 hook
hook | tryfirst | trylast | optionalhook | hookwrapper | wrapper |
---|---|---|---|---|---|
pytest_addoption | False | False | False | False | False |
pytest_make_collect_report | False | False | False | False | False |
pytest_report_teststatus | False | False | False | False | False |
pytest_runtest_call | False | False | False | False | False |
pytest_runtest_makereport | False | False | False | False | False |
pytest_runtest_protocol | False | False | False | False | False |
pytest_runtest_setup | False | False | False | False | False |
pytest_runtest_teardown | False | False | False | False | False |
pytest_sessionfinish | False | False | False | False | False |
pytest_sessionstart | False | False | False | False | False |
pytest_terminal_summary | False | False | False | False | False |
调用的 hook
-
pytest_runtest_logfinish
-
pytest_runtest_makereport
-
pytest_exception_interact
-
pytest_collectstart
-
pytest_runtest_logreport
-
pytest_runtest_logstart
-
pytest_make_collect_report
插件功能
-
创建参数
--durations
,显示 N 个最慢用例的耗时情况 -
创建参数
--durations-min
,显示超过 M 秒的用例的耗时情况 -
把用例分为
setp
、call
、teardown
3 个阶段执行,并生成各阶段的报告 -
支持多级夹具:
session -> package-> module -> class -> function
-
执行用例时判断参数:
-
如果收到参数
--setupshow
,显示夹具信息 -
如果收到参数
--setuponly
, 仅显示夹具信息,不执行用例
-
代码片段
def runtestprotocol(
item: Item, log: bool = True, nextitem: Optional[Item] = None
) -> List[TestReport]:
hasrequest = hasattr(item, "_request")
if hasrequest and not item._request: # type: ignore[attr-defined]
# This only happens if the item is re-run, as is done by
# pytest-rerunfailures.
item._initrequest() # type: ignore[attr-defined]
rep = call_and_report(item, "setup", log)
reports = [rep]
if rep.passed:
if item.config.getoption("setupshow", False):
show_test_item(item)
if not item.config.getoption("setuponly", False):
reports.append(call_and_report(item, "call", log))
reports.append(call_and_report(item, "teardown", log, nextitem=nextitem))
# After all teardown hooks have been called
# want funcargs and request info to go away.
if hasrequest:
item._request = False # type: ignore[attr-defined]
item.funcargs = None # type: ignore[attr-defined]
return reports
class CallInfo(Generic[TResult]):
@classmethod
def from_call(
cls,
func: "Callable[[], TResult]",
when: "Literal['collect', 'setup', 'call', 'teardown']",
reraise: Optional[
Union[Type[BaseException], Tuple[Type[BaseException], ...]]
] = None,
) -> "CallInfo[TResult]":
"""Call func, wrapping the result in a CallInfo.
:param func:
The function to call. Called without arguments.
:param when:
The phase in which the function is called.
:param reraise:
Exception or exceptions that shall propagate if raised by the
function, instead of being wrapped in the CallInfo.
"""
excinfo = None
start = timing.time()
precise_start = timing.perf_counter()
try:
result: Optional[TResult] = func()
except BaseException:
excinfo = ExceptionInfo.from_current()
if reraise is not None and isinstance(excinfo.value, reraise):
raise
result = None
# use the perf counter
precise_stop = timing.perf_counter()
duration = precise_stop - precise_start
stop = timing.time()
return cls(
start=start,
stop=stop,
duration=duration,
when=when,
result=result,
excinfo=excinfo,
_ispytest=True,
)
-
用例的报告,在执行是同步进行:
call_and_report
-
每个用例执行分为 3 个阶段,并生成 3 份报告 :
setp
、call
、teardown
-
用例执行过程非常精彩:
-
根据执行阶段,选择 hook:
pytest_runtest_xxx
-
把用例丢进 hook 中,调用 hook
-
得到调用结果:阶段、开始时间、结束时间、耗时、返回值、异常信息
-
将调用结果丢进 hook
pytest_runtest_makereport
中,生成报告 -
继续执行下一个阶段
-
所有阶段执行完毕后,执行下一个用例
-
简评
在介绍 main 插件的时候给你们说过,他把【用例收集】和【用例执行】的工作委托给了 session 插件。
其实 session 自己只干了用例收集的活儿,用例执行又被转包给了 runner 了。
当然这么做也有合理性,一方面 session 插件现在只干各用例收集的事就已经 400 + 行代码了,如果再包揽用例执行的活,代码量奔 1k 去了,着实不利于维护。
还有一点,session 插件本身是在 main 插件内创建的,加上 main 插件的内容,一个文件内会挤进去 1400 多行,写代码的人和看代码的人,都会吐。。。
不过下一个插件 fixture,义无反顾的在一个文件里写了 1600 多行代码。。。敬请期待
首发于公众号:测试开发研习社
原创不易,喜欢请星标+点赞+在看