4. 内置插件 runner
插件路径:_pytest.runner
实现的 hook
调用的 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、teardown3 个阶段执行,并生成各阶段的报告
支持多级夹具: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
得到调用结果:阶段、开始时间、结束时间、耗时、返回值、异常信息
将调用结果丢进 hookpytest_runtest_makereport中,生成报告
继续执行下一个阶段
所有阶段执行完毕后,执行下一个用例
简评
在介绍 main 插件的时候给你们说过,他把【用例收集】和【用例执行】的工作委托给了 session 插件。
其实 session 自己只干了用例收集的活儿,用例执行又被转包给了 runner 了。
当然这么做也有合理性,一方面 session 插件现在只干各用例收集的事就已经 400 + 行代码了,如果再包揽用例执行的活,代码量奔 1k 去了,着实不利于维护。
还有一点,session 插件本身是在 main 插件内创建的,加上 main 插件的内容,一个文件内会挤进去 1400 多行,写代码的人和看代码的人,都会吐。。。
不过下一个插件 fixture,义无反顾的在一个文件里写了 1600 多行代码。。。敬请期待
领取专属 10元无门槛券
私享最新 技术干货