首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

pytest的内置插件盘点34. capturemanager | 在pytest运行过程中捕获python标准输入/输出

本文系《pytest源码剖析》系列内容

34. capturemanager

插件路径:_pytest.capture.CaptureManager

本插件是capture的子插件,建议同步参阅《pytest的内置插件盘点11. capture》

实现的 hook

调用的 hook

定义的 fixture

插件功能

在用例收集阶段,进行全局捕获

在用例执行阶段,进行全局捕获和 fixture 捕获

提供了一个暂停捕获的方法,供 fixture 使用

代码片段

def set_fixture(self, capture_fixture: "CaptureFixture[Any]") -> None: self._capture_fixture = capture_fixture

def unset_fixture(self) -> None: self._capture_fixture = None @contextlib.contextmanager def item_capture(self, when: str, item: Item) -> Generator[None, None, None]: self.resume_global_capture() self.activate_fixture() try: yield finally: self.deactivate_fixture() self.suspend_global_capture(in_=False)

out, err = self.read_global_capture() item.add_report_section(when, "stdout", out) item.add_report_section(when, "stderr", err)

def _get_multicapture(method: "_CaptureMethod") -> MultiCapture[str]: if method == "fd": return MultiCapture(in_=FDCapture(0), out=FDCapture(1), err=FDCapture(2)) elif method == "sys": return MultiCapture(in_=SysCapture(0), out=SysCapture(1), err=SysCapture(2)) elif method == "no": return MultiCapture(in_=None, out=None, err=None) elif method == "tee-sys": return MultiCapture( in_=None, out=SysCapture(1, tee=True), err=SysCapture(2, tee=True) ) raise ValueError(f"unknown capturing method: {method!r}")

capturemanager 允许动态设置和控制 fixture 捕获器

执行用例时先激活全局捕获,再激活 fixture 捕获,捕获结果会附加到 item 中

只有no和tee-sys没有对输入进行捕获,能够进行正常输入

简评

capture 插件在加载 conftest.py 之前,获取capture参数的值,并据此实例化和注册了 capturemanager 插件

...

capturemanager 插件对标准输入 / 输出的捕获,分为两种:

第一种是全局捕获,根据capture参数创建捕获器,

捕获器会将目标 IO 对象 mock 掉,从而得到输出的内容

第二种是 fixture 捕获,由capsys、capfd等几个 fixture 控制

fixture 被用例请求后,会创建捕获器并传递到 capturemanager 插件中

因为插件只接收一个 fixture 捕获器,所以capsys、capfd不能同时使用

...

在用例执行阶段 ,capturemanager 先激活全局捕获,再激活 fixture 捕获

假设在用例中执行了print语句

def test_print_with(capsys): print('123') assert False

那么 fixture 捕获器将得到输出内容:123\n

至于全局捕获器不会得到任何内容

但是实际结果是

_______________________________ test_print_with _______________________________

capfd = <_pytest.capture.CaptureFixture object at 0x0000010AF3AAB770>

def test_print_with(capfd): print('123')> assert FalseE assert False

test_io.py:10: AssertionError---------------------------- Captured stdout call -----------------------------123

这里的Captured stdout call很明显是全局捕获器提供给 item 的

这就很奇怪了:

fixture 捕获器拦截输出内容之后,全局捕获器到底还有没有得到输出内容?

...

修改插件内容,加入如下代码:

从 debug 结果来看,使用capfd时,输出内容存储在 fixture 捕获器中

当用例不使用任何 fixture 时,输出内容存储在全局捕获器中

...

于是逐行调试,确定了每一行代码的作用

@contextlib.contextmanagerdef item_capture(self, when: str, item: Item) -> Generator[None, None, None]: self.resume_global_capture() # 开始全局捕获 self.activate_fixture() # 开始fixture获取 try: yield # 执行用例 finally: self.deactivate_fixture() # 将fixture捕获到的内容,追加到全局捕获中 self.suspend_global_capture(in_=False) # 停止全局捕获

out, err = self.read_global_capture() # 读取全局捕获内容 item.add_report_section(when, "stdout", out) # stdout附加到用例中 item.add_report_section(when, "stderr", err) # stderr附加到用例中

进一步跟踪发现,

class CaptureFixture def close(self) -> None: if self._capture is not None: out, err = self._capture.pop_outerr_to_orig() # 全局捕获结果发送变化

这个 pop_outerr_to_orig 方法,获取当前捕获内容 并重写一份到原始 IO 中

这下就彻底搞清楚了:

fixture 捕获是货真价实的拦截、捕获

全局捕获也是货真价实的拦截、捕获

fixture 捕获成功之后,会重写一份

重写的内容被全局捕获器捕获,附加到用例中,显示在报告里

之所以这么做,很可能是为了避免 fixtures 捕获器把重要的信息屏蔽,导致用例失败时无法准确排查

可以说考虑得非常贴心,全面了,不愧是维护了十几年的老牌开源框架

  • 发表于:
  • 原文链接https://page.om.qq.com/page/OcdewvA_QSmO2-7EyXl9_TBg0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券