我可以修补 Python 的断言以获得 py.test 提供的输出吗?

2023-11-21

Pytest 失败断言的输出比 Python 中的默认输出更丰富、更有用。我想在正常运行 Python 程序时利用这一点,而不仅仅是在执行测试时。有没有办法从我的脚本中覆盖Python的assert使用 pytest 打印堆栈跟踪的行为,同时仍然运行我的程序python script/pytest_assert.py?

示例程序

def test_foo():
  foo = 12
  bar = 42
  assert foo == bar

if __name__ == '__main__':
  test_foo()

$ python script/pytest_assert.py

Traceback (most recent call last):
  File "script/pytest_assert.py", line 8, in <module>
    test_foo()
  File "script/pytest_assert.py", line 4, in test_foo
    assert foo == bar
AssertionError

$ pytest script/pytest_assert.py

======================== test session starts ========================
platform linux -- Python 3.5.3, pytest-3.3.2, py-1.5.2, pluggy-0.6.0
rootdir: /usr/local/google/home/danijar, inifile:
collected 1 item                                                    

script/pytest_assert.py F                                     [100%]

============================= FAILURES ==============================
_____________________________ test_foo ______________________________

    def test_foo():
      foo = 12
      bar = 42
>     assert foo == bar
E     assert 12 == 42

script/pytest_assert.py:4: AssertionError
===================== 1 failed in 0.02 seconds =====================

期望的结果

$ python script/pytest_assert.py

Traceback (most recent call last):
  File "script/pytest_assert.py", line 8, in <module>
    test_foo()

    def test_foo():
      foo = 12
      bar = 42
>     assert foo == bar
E     assert 12 == 42

script/pytest_assert.py:4: AssertionError

进度更新

我得到的最接近的是这个,但它只适用于该函数中的断言,并且会发送垃圾邮件跟踪:

import ast
import inspect

from _pytest import assertion


def test_foo():
  foo = []
  foo.append(13)
  foo = foo[-1]
  bar = 42
  assert foo == bar, 'message'


if __name__ == '__main__':
  tree = ast.parse(inspect.getsource(test_foo))
  assertion.rewrite.rewrite_asserts(tree)
  code = compile(tree, '<name>', 'exec')
  ns = {}
  exec(code, ns)
  ns[test_foo.__name__]()

$ python script/pytest_assert.py
Traceback (most recent call last):
  File "script/pytest_assert.py", line 21, in <module>
    ns[test_foo.__name__]()
  File "<name>", line 6, in test_foo
AssertionError: message
assert 13 == 42

免责声明

虽然肯定有办法重用pytest以所需格式打印回溯的代码,您需要使用的东西不是公共 API 的一部分,因此生成的解决方案将太脆弱,需要调用不相关的pytest代码(用于初始化目的)并且可能在包更新时中断。最好的选择是重写关键部分,使用pytest代码为例。

Notes

基本上,下面的概念验证代码做了三件事:

  1. 替换默认值sys.excepthook使用自定义的:这是改变默认回溯格式所必需的。例子:

    import sys
    
    orig_hook = sys.excepthook
    
    def myhook(*args):
        orig_hook(*args)
        print('hello world')
    
    if __name__ == '__main__':
        sys.excepthook = myhook
        raise ValueError()
    

    将输出:

    Traceback (most recent call last):
      File "example.py", line 11, in <module>
        raise ValueError()
    ValueError
    hello world
    
  2. 代替hello world,将打印格式化的异常信息。我们用ExceptionInfo.getrepr()为了那个原因。

  3. 要访问断言中的附加信息,pytest重写了assert语句(你可以得到一些关于它们重写后的样子的粗略信息这篇旧文章)。为了实现这一目标,pytest注册一个自定义导入钩子,如指定的PEP 302。钩子是最有问题的部分,因为它与Config对象,我还注意到一些模块导入会导致问题(我猜它不会失败pytest只是因为在注册钩子时模块已经导入;将尝试编写一个测试来重现该问题pytest运行并创建一个新问题)。因此,我建议编写一个自定义导入钩子来调用AssertionRewriter。这个 AST 树遍历器类是断言重写中的重要部分,而AssertionRewritingHook没那么重要。

Code

so-51839452
├── hooks.py
├── main.py
└── pytest_assert.py

hooks.py

import sys

from pluggy import PluginManager
import _pytest.assertion.rewrite
from _pytest._code.code import ExceptionInfo
from _pytest.config import Config, PytestPluginManager


orig_excepthook = sys.excepthook

def _custom_excepthook(type, value, tb):
    orig_excepthook(type, value, tb)  # this is the original traceback printed
    # preparations for creation of pytest's exception info
    tb = tb.tb_next  # Skip *this* frame
    sys.last_type = type
    sys.last_value = value
    sys.last_traceback = tb

    info = ExceptionInfo(tup=(type, value, tb, ))

    # some of these params are configurable via pytest.ini
    # different params combination generates different output
    # e.g. style can be one of long|short|no|native
    params = {'funcargs': True, 'abspath': False, 'showlocals': False,
              'style': 'long', 'tbfilter': False, 'truncate_locals': True}
    print('------------------------------------')
    print(info.getrepr(**params))  # this is the exception info formatted
    del type, value, tb  # get rid of these in this frame


def _install_excepthook():
    sys.excepthook = _custom_excepthook


def _install_pytest_assertion_rewrite():
    # create minimal config stub so AssertionRewritingHook is happy
    pluginmanager = PytestPluginManager()
    config = Config(pluginmanager)
    config._parser._inidict['python_files'] = ('', '', [''])
    config._inicache = {'python_files': None, 'python_functions': None}
    config.inicfg = {}

    # these modules _have_ to be imported, or AssertionRewritingHook will complain
    import py._builtin
    import py._path.local
    import py._io.saferepr

    # call hook registration
    _pytest.assertion.install_importhook(config)

# convenience function
def install_hooks():
    _install_excepthook()
    _install_pytest_assertion_rewrite()

main.py

打电话后hooks.install_hooks(), main.py将修改回溯打印。之后导入的每个模块install_hooks()调用将在导入时重写断言。

from hooks import install_hooks

install_hooks()

import pytest_assert


if __name__ == '__main__':
    pytest_assert.test_foo()

pytest_assert.py

def test_foo():
    foo = 12
    bar = 42
    assert foo == bar

输出示例

$ python main.py
Traceback (most recent call last):
  File "main.py", line 9, in <module>
    pytest_assert.test_foo()
  File "/Users/hoefling/projects/private/stackoverflow/so-51839452/pytest_assert.py", line 4, in test_foo
    assert foo == bar
AssertionError
------------------------------------
def test_foo():
        foo = 12
        bar = 42
>       assert foo == bar
E       AssertionError

pytest_assert.py:4: AssertionError

总结

我会写一个自己的版本AssertionRewritingHook,没有全部不相关的pytest东西。这AssertionRewriter然而看起来几乎可以重复使用;虽然它需要一个Config例如,它仅用于警告打印,可以留作None.

一旦你有了这个,编写你自己的函数来正确格式化异常,替换sys.excepthook你就完成了。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

我可以修补 Python 的断言以获得 py.test 提供的输出吗? 的相关文章

  • DreamPie 不适用于 Python 3.2

    我最喜欢的 Python shell 是DreamPie http dreampie sourceforge net 我想将它与 Python 3 2 一起使用 我使用了 添加解释器 DreamPie 应用程序并添加了 Python 3 2
  • 导入错误:没有名为 _ssl 的模块

    带 Python 2 7 的 Ubuntu Maverick 我不知道如何解决以下导入错误 gt gt gt import ssl Traceback most recent call last File
  • 如何使用包含代码的“asyncio.sleep()”进行单元测试?

    我在编写 asyncio sleep 包含的单元测试时遇到问题 我要等待实际的睡眠时间吗 I used freezegun到嘲笑时间 当我尝试使用普通可调用对象运行测试时 这个库非常有用 但我找不到运行包含 asyncio sleep 的测
  • 打破嵌套循环[重复]

    这个问题在这里已经有答案了 有没有比抛出异常更简单的方法来打破嵌套循环 在Perl https en wikipedia org wiki Perl 您可以为每个循环指定标签 并且至少继续一个外循环 for x in range 10 fo
  • __del__ 真的是析构函数吗?

    我主要用 C 做事情 其中 析构函数方法实际上是为了销毁所获取的资源 最近我开始使用python 这真的很有趣而且很棒 我开始了解到它有像java一样的GC 因此 没有过分强调对象所有权 构造和销毁 据我所知 init 方法对我来说在 py
  • 安装后 Anaconda 提示损坏

    我刚刚安装张量流GPU创建单独的后环境按照以下指示here https github com antoniosehk keras tensorflow windows installation 但是 安装后当我关闭提示窗口并打开新航站楼弹出
  • 如何使用装饰器禁用某些功能的中间件?

    我想模仿的行为csrf exempt see here https docs djangoproject com en 1 11 ref csrf django views decorators csrf csrf exempt and h
  • 运行多个 scrapy 蜘蛛的正确方法

    我只是尝试使用在同一进程中运行多个蜘蛛新的 scrapy 文档 http doc scrapy org en 1 0 topics practices html但我得到 AttributeError CrawlerProcess objec
  • IRichBolt 在storm-1.0.0 和 pyleus-0.3.0 上运行拓扑时出错

    我正在运行风暴拓扑 pyleus verbose local xyz topology jar using storm 1 0 0 pyleus 0 3 0 centos 6 6并得到错误 线程 main java lang NoClass
  • python 集合可以包含的值的数量是否有限制?

    我正在尝试使用 python 设置作为 mysql 表中 ids 的过滤器 python集存储了所有要过滤的id 现在大约有30000个 这个数字会随着时间的推移慢慢增长 我担心python集的最大容量 它可以包含的元素数量有限制吗 您最大
  • Pandas Dataframe 中 bool 值的条件前向填充

    问题 如何转发 fill boolTruepandas 数据框中的值 如果是当天的第一个条目 True 到一天结束时 请参阅以下示例和所需的输出 Data import pandas as pd import numpy as np df
  • 当玩家触摸屏幕一侧时,如何让 pygame 发出警告?

    我使用 pygame 创建了一个游戏 当玩家触摸屏幕一侧时 我想让 pygame 给出类似 你不能触摸屏幕两侧 的错误 我尝试在互联网上搜索 但没有找到任何好的结果 我想过在屏幕外添加一个方块 当玩家触摸该方块时 它会发出警告 但这花了很长
  • Python 3 中“map”类型的对象没有 len()

    我在使用 Python 3 时遇到问题 我得到了 Python 2 7 代码 目前我正在尝试更新它 我收到错误 类型错误 map 类型的对象没有 len 在这部分 str len seed candidates 在我像这样初始化它之前 se
  • 如何在 Django 中使用并发进程记录到单个文件而不使用独占锁

    给定一个在多个服务器上同时执行的 Django 应用程序 该应用程序如何记录到单个共享日志文件 在网络共享中 而不保持该文件以独占模式永久打开 当您想要利用日志流时 这种情况适用于 Windows Azure 网站上托管的 Django 应
  • 类型错误:只能使用标量值执行操作

    如果您能让我知道如何为所提供的表格绘制一些信息丰富的图表 我将不胜感激here https www iasplus com en resources ifrs topics use of ifrs 例如 我需要一个名为 国内非上市公司 非上
  • 如何从没有结尾的管道中读取 python 中的 stdin

    当管道来自 打开 时 不知道正确的名称 我无法从 python 中的标准输入或管道读取数据 文件 我有作为例子管道测试 py import sys import time k 0 try for line in sys stdin k k
  • glpk.LPX 向后兼容性?

    较新版本的glpk没有LPXapi 旧包需要它 我如何使用旧包 例如COBRA http opencobra sourceforge net openCOBRA Welcome html 与较新版本的glpk 注意COBRA适用于 MATL
  • 用于运行可执行文件的python多线程进程

    我正在尝试将一个在 Windows 上运行可执行文件并管理文本输出文件的 python 脚本升级到使用多线程进程的版本 以便我可以利用多个核心 我有四个独立版本的可执行文件 每个线程都知道要访问它们 这部分工作正常 我遇到问题的地方是当它们
  • 在 Python 类中动态定义实例字段

    我是 Python 新手 主要从事 Java 编程 我目前正在思考Python中的类是如何实例化的 我明白那个 init 就像Java中的构造函数 然而 有时 python 类没有 init 方法 在这种情况下我假设有一个默认构造函数 就像
  • 使用 Makefile 项目在 Visual Studio 中自定义调试命令

    我在 Visual Studio 2010 中有一个由 Makefile 支持的项目 实际上使用 NAnt 但这不是重点 构建过程的输出是一个 elf 文件 我有一个单独的非 VStudio 调试器 可以在该 elf 文件上运行来调试它 构

随机推荐