PyDev unittesting:如何在"捕获的输出"中捕获记录到logging.Logger的文本

gec*_*cco 52 python logging unit-testing pydev

我正在使用PyDev进行Python应用程序的开发和单元测试.至于单元测试,一切都很好,除了没有内容记录到日志框架.PyDev的"捕获输出"未捕获记录器.

我已经将所有记录到标准输出的内容转发如下:

import sys
logger = logging.getLogger()
logger.level = logging.DEBUG
logger.addHandler(logging.StreamHandler(sys.stdout))
Run Code Online (Sandbox Code Playgroud)

然而,"捕获的输出"不会显示记录到记录器的东西.

这是一个示例unittest-script:test.py

import sys
import unittest
import logging

logger = logging.getLogger()
logger.level = logging.DEBUG
logger.addHandler(logging.StreamHandler(sys.stdout))

class TestCase(unittest.TestCase):
    def testSimpleMsg(self):
        print("AA")
        logging.getLogger().info("BB")
Run Code Online (Sandbox Code Playgroud)

控制台输出是:

Finding files... done.
Importing test modules ... done.

testSimpleMsg (itf.lowlevel.tests.hl7.TestCase) ... AA
2011-09-19 16:48:00,755 - root - INFO - BB
BB
ok

----------------------------------------------------------------------
Ran 1 test in 0.001s

OK
Run Code Online (Sandbox Code Playgroud)

但是测试的CAPTURED OUTPUT是:

======================== CAPTURED OUTPUT =========================
AA
Run Code Online (Sandbox Code Playgroud)

有人知道如何捕获logging.Logger在执行此测试期间记录到的所有内容吗?

Fab*_*zny 57

问题是unittest跑步者在测试开始之前替换sys.stdout/ sys.stderr,并且StreamHandler仍在写入原始版本sys.stdout.

如果将"当前"分配给sys.stdout处理程序,它应该可以工作(请参阅下面的代码).

import sys
import unittest
import logging

logger = logging.getLogger()
logger.level = logging.DEBUG
stream_handler = logging.StreamHandler(sys.stdout)
logger.addHandler(stream_handler)

class TestCase(unittest.TestCase):
    def testSimpleMsg(self):
        stream_handler.stream = sys.stdout
        print("AA")
        logging.getLogger().info("BB")
Run Code Online (Sandbox Code Playgroud)

虽然,更好的方法是在测试期间添加/删除处理程序:

import sys
import unittest
import logging

logger = logging.getLogger()
logger.level = logging.DEBUG

class TestCase(unittest.TestCase):
    def testSimpleMsg(self):
        stream_handler = logging.StreamHandler(sys.stdout)
        logger.addHandler(stream_handler)
        try:
            print("AA")
            logging.getLogger().info("BB")
        finally:
            logger.removeHandler(stream_handler)
Run Code Online (Sandbox Code Playgroud)

  • 为了完整性:我需要对所有单元测试进行重定向.对我来说最好的解决方案是在setUp-method中添加新的处理程序,并在tearDown方法中删除它. (9认同)
  • 很好的答案,我[扩展](http://stackoverflow.com/a/15969985/321973)这是一个`__metaclass__`所以包裹的`setUp`和`tearDown`会自动包含这个 (2认同)

Tob*_*ler 18

我从小厌倦了手动添加的法比奥的伟大的代码对所有setUpS,所以我子类unittest.TestCase的一些__metaclass__ING:

class LoggedTestCase(unittest.TestCase):
    __metaclass__ = LogThisTestCase
    logger = logging.getLogger("unittestLogger")
    logger.setLevel(logging.DEBUG) # or whatever you prefer

class LogThisTestCase(type):
    def __new__(cls, name, bases, dct):
        # if the TestCase already provides setUp, wrap it
        if 'setUp' in dct:
            setUp = dct['setUp']
        else:
            setUp = lambda self: None
            print "creating setUp..."

        def wrappedSetUp(self):
            # for hdlr in self.logger.handlers:
            #    self.logger.removeHandler(hdlr)
            self.hdlr = logging.StreamHandler(sys.stdout)
            self.logger.addHandler(self.hdlr)
            setUp(self)
        dct['setUp'] = wrappedSetUp

        # same for tearDown
        if 'tearDown' in dct:
            tearDown = dct['tearDown']
        else:
            tearDown = lambda self: None

        def wrappedTearDown(self):
            tearDown(self)
            self.logger.removeHandler(self.hdlr)
        dct['tearDown'] = wrappedTearDown

        # return the class instance with the replaced setUp/tearDown
        return type.__new__(cls, name, bases, dct)
Run Code Online (Sandbox Code Playgroud)

现在你的测试用例可以简单地继承LoggedTestCase,即class TestCase(LoggedTestCase)代替class TestCase(unittest.TestCase)而你已经完成了.或者,您可以添加该__metaclass__行并logger在测试中定义或略微修改LogThisTestCase.

  • @Randy 谢谢,在阅读了[对`__metaclass__` 的精彩解释](http://stackoverflow.com/a/6581949/321973) 后,我只是_had_ 使用它... (2认同)
  • @FabioZadrozny 是的,尽管当时提到我只是 _had_ 使用元类;) (2认同)

nor*_*ius 14

有些人可能会访问此线程以找到将测试期间创建的日志转发到控制台或 PyDev 的方法。上面的答案已经提供了一些解决方案。

如果想在实际测试中捕获特定日志,我发现从 Python 3.4 开始,unittest.TestCase提供了assertLogs(),它返回一个捕获当前日志消息的上下文管理器。来自单元测试文档

with self.assertLogs('foo', level='INFO') as cm:
   logging.getLogger('foo').info('first message')
   logging.getLogger('foo.bar').error('second message')
self.assertEqual(cm.output, ['INFO:foo:first message',
                             'ERROR:foo.bar:second message'])
Run Code Online (Sandbox Code Playgroud)

消息被捕获在cm.output. 有关更详细的信息(如时间、文件、行号等),cm.records包含LogRecords.

所有这些并没有直接解决 PyDev 面临的 OP,而是提供了一种以编程方式检查创建的消息的方法。

对于熟悉pytest的人来说,可以使用--log-cli-level=LEVEL标志将格式良好的日志消息转发到控制台(例如pytest --log-cli-level=info)。


Chr*_*ers 8

我建议使用LogCapture并测试您是否记录了您希望记录的内容:

http://testfixtures.readthedocs.org/en/latest/logging.html


sta*_*son 5

在阅读了this 和其他一些相关线程中的答案后(谢谢!),这是我放在一起的上下文管理器,它将捕获记录器的输出(如果有的话)。

from io import StringIO
import logging
class CaptureLogger:
    """Context manager to capture `logging` streams

    Args:
        - logger: 'logging` logger object

    Results:
        The captured output is available via `self.out`

    """

    def __init__(self, logger):
        self.logger = logger
        self.io = StringIO()
        self.sh = logging.StreamHandler(self.io)
        self.out = ''

    def __enter__(self):
        self.logger.addHandler(self.sh)
        return self

    def __exit__(self, *exc):
        self.logger.removeHandler(self.sh)
        self.out = self.io.getvalue()

    def __repr__(self):
        return f"captured: {self.out}\n"
Run Code Online (Sandbox Code Playgroud)

用法示例:

logger = logging.getLogger()
msg = "Testing 1, 2, 3"
with CaptureLogger(logger) as cl:
    logger.error(msg)
assert cl.out, msg+"\n"
Run Code Online (Sandbox Code Playgroud)

当 OP 要求将其放入捕获的 stdout 流时,您可以将其打印到 stdout in 中__exit__,因此添加一行如下:

    def __exit__(self, *exc):
        self.logger.removeHandler(self.sh)
        self.out = self.io.getvalue()
        print(self.out)
Run Code Online (Sandbox Code Playgroud)

此解决方案的不同之处在于它将收集日志记录输出并在所有正常print()调用(如果有)之后立即将其全部转储出来。所以它可能是也可能不是 OP 所追求的,但这对我的需求很有效。