【问题标题】:How to test that a custom excepthook is installed correctly?如何测试自定义异常钩子是否安装正确?
【发布时间】:2017-09-19 21:30:18
【问题描述】:

我的应用记录未处理的异常。

# app.py
import logging
import sys

logger = logging.getLogger(__name__)

def excepthook(exc_type, exc_value, traceback):
    exc_info = exc_type, exc_value, traceback
    if not issubclass(exc_type, (KeyboardInterrupt, SystemExit)):
        logger.error('Unhandled exception', exc_info=exc_info)
    sys.__excepthook__(*exc_info)

sys.excepthook = excepthook

def potato():
    logger.warning('about to die...')
    errorerrorerror

if __name__ == '__main__':
    potato()

这些测试通过了:

# test_app.py
import app
import pytest
import sys
from logging import WARNING, ERROR

def test_potato_raises():
    with pytest.raises(NameError):
        app.potato()

def test_excepthook_is_set():
    assert sys.excepthook is app.excepthook

# for caplog plugin: pip install pytest-catchlog
def test_excepthook_logs(caplog):  
    try:
        whatever
    except NameError as err:
        exc_info = type(err), err, err.__traceback__
    app.excepthook(*exc_info)
    assert caplog.record_tuples == [('app', ERROR, 'Unhandled exception')]
    [record] = caplog.records
    assert record.exc_info == exc_info

但我无法测试未处理的异常日志记录是否正常工作:

def test_unhandled_exceptions_logged(caplog):
    try:
        app.potato()
    finally:
        assert caplog.record_tuples == [
            ('app', WARNING, 'about to die...'),
            ('app', ERROR, 'Unhandled exception'),
        ]
        return  # return eats exception

这里有什么问题?我们如何才能在测试中真正触发app.excepthook

【问题讨论】:

  • “但我无法测试未处理的异常日志是否正常工作” - 好吧,直到异常真正传播到整个过程(它不会传播),它并不是真正的未处理异常,并且exceptionhook 不会触发。
  • 对,这正是问题所在(测试运行程序捕获了异常)。我正在寻找一种方法来配置 pytest 以“让 [进一步] 让路”,即允许 app.excepthook 运行,但不允许 sys.__excepthook__ 运行。但也许这是不可能的。在子流程中运行是我希望避免的一种解决方法,因为目前尚不清楚如何让覆盖率报告在这种情况下协同工作。
  • 看起来低级的thread 模块可能会让你这样做,虽然我不确定如何抑制“线程中未处理的异常由...启动”消息。跨度>

标签: python testing logging exception-handling pytest


【解决方案1】:

Python 不会调用sys.excepthook,直到异常实际上一直传播到整个堆栈并且没有更多代码有机会捕获它。这是 Python 响应异常而关闭之前发生的最后一件事。

只要您的测试代码仍在堆栈中,sys.excepthook 就不会触发。在sys.excepthook 之后实际上可以运行的小代码可能无法与您的测试框架很好地配合。例如,atexit 处理程序仍然可以运行,但测试已经结束。此外,如果您不这样做,您的测试框架可能会自行捕获异常,因此sys.excepthook 无论如何都不会触发。

如果您不想自己调用sys.excepthook,最好的办法是启动安装了excepthook 的整个子进程并验证子进程的行为。

from subprocess import Popen, PIPE

def test_app():
    proc = Popen([sys.executable, 'app.py'], stdout=PIPE, stderr=PIPE)
    stdout, stderr = proc.communicate()
    assert proc.returncode == 1
    assert stdout == b''
    assert stderr.startswith(b'about to die...\nUnhandled exception')

【讨论】:

    【解决方案2】:

    pytest 可以检查异常信息。你可以这样做:

    >>> import pytest
    >>> def foo():
    ...  raise ValueError('Unhandled Exception')
    ...
    >>> with pytest.raises(ValueError) as exc:
    ...  foo()
    ...
    >>> 'Unhandled Exception' in str(exc)
    True
    >>> str(exc)
    '<stdin>:2: ValueError: Unhandled Exception'
    >>>
    

    您实际上可以在一次测试中测试整个事物。无需多个测试功能。

    【讨论】:

    • 你试过了吗? AttributeError: 'ExceptionInfo' object has no attribute 'message'.
    • 完全是我的错。我过去使用过 pytest。这就是为什么确信异常文本会保存在exc.message 中的原因。回去检查我的项目。我发现它是 str(exc) 有错误文本。用示例更新了答案,并删除了与 exc.message 相关的另一个示例。
    • 好的,所以,这并不是真正回答问题。我不是要检查异常消息的文本。我正在尝试测试应用程序中未处理的异常是否会触发要调用的 except 挂钩,该挂钩 记录 一条消息。你对此有什么想法吗?
    • 我想告诉你的是,如果出现 'Unhandled Exception' 消息,那么这意味着你的异常钩子实际上被解雇了。没有它,消息一开始就不会被记录下来。
    • 是的,但我从来没有以“未处理的异常”作为消息引发异常。该消息仅添加到日志记录调用中。在您的示例中,您手动将“未处理的异常”明确写入错误消息中。
    猜你喜欢
    • 2016-07-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-18
    • 2022-01-13
    • 2020-07-10
    • 2022-12-21
    • 1970-01-01
    相关资源
    最近更新 更多