【问题标题】:py.test: hide stacktrace lines from unittest modulepy.test:隐藏单元测试模块中的堆栈跟踪行
【发布时间】:2014-06-25 07:17:05
【问题描述】:

py.test 堆栈跟踪现在看起来像这样:

Traceback (most recent call last):
  File "/home/foo_tbz_di476/src/djangotools/djangotools/tests/ReadonlyModelTestCommon.py", line 788, in test_stale_or_missing_content_types
    self.assertEqual([], errors, 'Stale/Missing ContentTypes: %s' % '\n'.join(errors))
  File "/usr/lib64/python2.7/unittest/case.py", line 511, in assertEqual
    assertion_func(first, second, msg=msg)
  File "/usr/lib64/python2.7/unittest/case.py", line 740, in assertListEqual
    self.assertSequenceEqual(list1, list2, msg, seq_type=list)
  File "/usr/lib64/python2.7/unittest/case.py", line 722, in assertSequenceEqual
    self.fail(msg)
  File "/usr/lib64/python2.7/unittest/case.py", line 408, in fail
    raise self.failureException(msg)

如果输出跳过unittest 模块中的行,对我的肉眼来说会容易得多。

例子:

Traceback (most recent call last):
  File "/home/foo_tbz_di476/src/djangotools/djangotools/tests/ReadonlyModelTestCommon.py", line 788, in test_stale_or_missing_content_types
    self.assertEqual([], errors, 'Stale/Missing ContentTypes: %s' % '\n'.join(errors))

我尝试了选项--tb=short,但这并没有这样做。

更新

首选没有 unix 管道的解决方案(如 py.test ...| grep)。

【问题讨论】:

  • 您能否发布一个生成此输出的示例测试以及您用来运行它的命令。 AFAIK,py.test 根本不应该输出这样的回溯。
  • 我已将此作为功能请求添加到 py.test github 页面上:github.com/pytest-dev/pytest/issues/1434

标签: python unit-testing


【解决方案1】:

看起来您正在调用 pytest,如下所示:

py.test --tb=native

这个表单将输出一个派生自traceback.format_exception的python stdlib stacktrace

使用 pytest,您可以将 conftest.py 文件添加到您的项目中。在这里您可以添加任何代码来修改 pytest 行为。

小心!在以下两种方法中都使用monkey patching,其中人们may consider evil

选项 1:字符串匹配

这是最简单的方法,但如果您要搜索的字符串出现在您不想隐藏的行中,则可能会出现问题。

这种方法修补了py包中的ReprEntryNative类,它是pytest的依赖项。

将以下代码放入你的 conftest.py

import py

def test_skip_line(line):
    """decide which lines to skip, the code below will also skip the next line if this returns true"""
    return 'unittest' in line

class PatchedReprEntryNative(py._code.code.ReprEntryNative):
    def __init__(self, tblines):
        self.lines = []
        while len(tblines) > 0:
            line = tblines.pop(0)
            if test_skip_line(line):
                # skip this line and the next
                tblines.pop(0)
            else:
                self.lines.append(line)
py._code.code.ReprEntryNative = PatchedReprEntryNative

选项 2:回溯帧检查

如果字符串匹配对您来说不够真实,我们可以在它被转储到字符串之前检查回溯,并且只输出不是来自一组模块的帧。

这种方法修补了可能会杀死小狗的 traceback.extract_tb 函数。

将以下代码放入你的 conftest.py

import inspect
import linecache
import traceback
import unittest.case
import sys    

SKIPPED_MODULES = [
    unittest.case
]

def test_skip_frame(frame):
    module = inspect.getmodule(frame)
    return module in SKIPPED_MODULES

def tb_skipper(tb):
    tbnext = tb.tb_next
    while tbnext is not None:
        if test_skip_frame(tbnext.tb_frame):
            tbnext = tbnext.tb_next
        else:
            yield tbnext
    yield None

def new_extract_tb(tb, limit = None):
    if limit is None:
        if hasattr(sys, 'tracebacklimit'):
            limit = sys.tracebacklimit
    list = []
    n = 0
    new_tb_order = tb_skipper(tb) # <-- this line added
    while tb is not None and (limit is None or n < limit):
        f = tb.tb_frame
        lineno = tb.tb_lineno
        co = f.f_code
        filename = co.co_filename
        name = co.co_name
        linecache.checkcache(filename)
        line = linecache.getline(filename, lineno, f.f_globals)
        if line: line = line.strip()
        else: line = None
        list.append((filename, lineno, name, line))
        tb = next(new_tb_order) # <-- this line modified, was tb = tb.tb_next
        n = n+1
    return list
traceback.extract_tb = new_extract_tb

【讨论】:

  • 我认为最时髦的做法是使用 __unittest =True 跳过模块
  • 非常感谢您的优质帖子!会评论我的更新,但没有足够的声誉这样做:(。您将在下面看到我对选项 1 的更新。
【解决方案2】:

尝试使用反向模式将输出通过管道传输到 grep。这将打印除与模式匹配的行之外的所有行。

python all_tests.py | grep -v "usr/lib64/python2.7/unittest"

【讨论】:

  • 每个堆栈跟踪帧都打印有两行:第一行是源文件名,下一个文件显示源代码。您的解决方案仅过滤掉显示源文件名的行。输出看起来很奇怪。
  • 将 -A 1 添加到命令行。这包括比赛后的一行。我希望使用反向过滤器可以按预期工作。
【解决方案3】:

如果您知道您使用的是什么操作系统并且不关心目录拆分,则可以删除导入 os 并将 os.sep 替换为适当的分隔符

这将删除单元测试模块所在位置及其后行的所有回溯。

import os
import sys
import unittest

class Stderr(object):
    def __init__(self):
        self.unittest_location = (os.sep).join(unittest.__file__.split(os.sep)[:-1])
        self.stderr = sys.__stderr__
        self.skip = False

    def write(self, text):
        if self.skip and text.find("\n") != -1: self.skip=False
        elif self.skip: pass
        else:
            self.skip = text.find(self.unittest_location) != -1
            if not self.skip: self.stderr.write(text)

sys.stderr = Stderr()

【讨论】:

    【解决方案4】:

    感谢https://stackoverflow.com/a/24679193/59412 我只是冒昧地将它移植到最新的 pytest(截至写作时为 6.2.3)。将此粘贴在您的conftest.py 中。 (但不适用于unittest 代码......因为它不在site-packages 中)

    选项 1:

    (默认过滤所有 pip 安装的包)

    # 5c4048fc-ccf1-44ab-a683-78a29c1a98a6
    import _pytest._code.code
    def should_skip_line(line):
        """
        decide which lines to skip
        """
        return 'site-packages' in line
    
    class PatchedReprEntryNative(_pytest._code.code.ReprEntryNative):
        def __init__(self, tblines):
            self.lines = []
            while len(tblines) > 0:
                # [...yourfilter...]/test_thing.py", line 1, in test_thing
                line = tblines.pop(0)
                if should_skip_line(line):
                    # some line of framework code you don't want to see either...
                    tblines.pop(0)
                else:
                    self.lines.append(line)
    _pytest._code.code.ReprEntryNative = PatchedReprEntryNative
    del _pytest._code.code
    

    仅适用于--tb=native。就像我移植到这个版本的答案一样。

    选项 2:

    待定

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-29
      • 2019-06-12
      • 1970-01-01
      • 2018-01-30
      • 1970-01-01
      • 2018-04-14
      相关资源
      最近更新 更多