使用mock获取所有日志输出

时间:2014-03-26 10:08:57

标签: python unit-testing logging mocking

我想用mock获取所有日志输出。我搜查了,但是 只找到了显式模拟logging.info或logging.warn的方法。

我需要所有输出,无论设置了什么日志记录级别。

def test_foo():

   def my_log(...):
      logs.append(...)

   with mock.patch('logging.???', my_log):
        ...

在我们的图书馆中,我们使用:

import logging
logger=logging.getLogger(__name__)

def foo():
    logger.info(...)

4 个答案:

答案 0 :(得分:6)

pytest

如果您使用pytest编写测试,请查看一个名为caplog的整洁夹具,它将为您捕获日志记录。它捕获所有发出的日志记录,然后您可以通过caplog.records列表访问这些记录。每个元素都是logging.LogRecord的实例,因此您可以轻松访问任何LogRecords attributes。例如:

# spam.py

import logging
logger=logging.getLogger(__name__)

def foo():
    logger.info('bar')


# tests.py

import logging
from spam import foo

def test_foo(caplog):
    foo()
    assert len(caplog.records) == 1
    record = next(iter(caplog.records))
    assert record.message == 'bar'
    assert record.levelno == logging.INFO
    assert record.module == 'spam'
    # etc

安装

夹具最初是在名为pytest的{​​{1}}插件中引入的,现在已被放弃。幸运的是,最近有一个名为pytest-capturelogwhich has been merged into pytest==3.3.0的分叉。所以,如果你使用最新版本的pytest-catchlog,你已经很好了;适用于旧版pytestinstall pytest-catchlog from PyPI

文档

目前,pytest没有为pytest灯具提供任何文档(或者至少我找不到任何文件),所以你可以参考{{1 } {' s documentation

普通caplog

如果pytest-catchlog不是一个选项,我根本不会修补unittest - 您只需添加一个自定义处理程序,它将记录所有传入的日志。一个小例子:

pytest

然后,您可以扩展自定义处理程序并实现您需要的任何逻辑,例如收集将日志级别映射到记录列表的logging中的记录,或者添加# utils.py import logging class RecordsCollector(logging.Handler): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.records = [] def emit(self, record): self.records.append(record) # tests.py import logging import unittest from utils import RecordsCollector from spam import foo class SpamTests(unittest.TestCase): def setUp(self): self.collector = RecordsCollector() logging.getLogger('spam').addHandler(self.collector) def tearDown(self): logging.getLogger('spam').removeHandler(self.collector) def test_foo(self): foo() # same checks as in the example above self.assertEqual(len(self.collector.records), 1) record = next(iter(self.collector.records)) self.assertEqual(record.message, 'bar') self.assertEqual(record.levelno, logging.INFO) self.assertEqual(record.module, 'spam') if __name__ == '__main__': unittest.main() 实现,这样您就可以开始和停止捕获测试中的记录:

dict

答案 1 :(得分:1)

STDLIB

自从Python 3.4电池' unittestassertLogs。在没有loggerlevel参数的情况下使用时,它会捕获所有日志记录(抑制现有处理程序)。您可以稍后从上下文管理器的records属性访问记录的条目。文本输出字符串存储在output列表中。

import logging
import unittest


class TestLogging(unittest.TestCase):

    def test(self):
        with self.assertLogs() as ctx:
            logging.getLogger('foo').info('message from foo')
            logging.getLogger('bar').info('message from bar')
        print(ctx.records)

龙卷风

对于Python 2,我通常会使用Tornado' ExpectLog。它是自包含的,适用于普通的Python代码。它实际上是比stdlib更优雅的解决方案,因为ExpectLog不是几个类,而只是一个普通的logging.Filter(一个类,source)。但它缺少一些功能,包括访问录制的条目,所以通常我也会扩展它,如:

class ExpectLog(logging.Filter):

    def __init__(self, logger, regex, required=True, level=None):
        if isinstance(logger, basestring):
            logger = logging.getLogger(logger)
        self.logger = logger
        self.orig_level = self.logger.level
        self.level = level
        self.regex = re.compile(regex)
        self.formatter = logging.Formatter()
        self.required = required
        self.matched = []
        self.logged_stack = False

    def filter(self, record):
        if record.exc_info:
            self.logged_stack = True
        message = self.formatter.format(record)
        if self.regex.search(message):
            self.matched.append(record)
            return False
        return True

    def __enter__(self):
        self.logger.addFilter(self)
        if self.level:
            self.logger.setLevel(self.level)
        return self

    def __exit__(self, typ, value, tb):
        self.logger.removeFilter(self)
        if self.level:
            self.logger.setLevel(self.orig_level)
        if not typ and self.required and not self.matched:
            raise Exception("did not get expected log message")

然后你可以有类似的东西:

class TestLogging(unittest.TestCase):

    def testTornadoself):
        logging.basicConfig(level = logging.INFO)

        with ExpectLog('foo', '.*', required = False) as ctxFoo:
            with ExpectLog('bar', '.*', required = False) as ctxBar:
                logging.getLogger('foo').info('message from foo')
                logging.getLogger('bar').info('message from bar')
        print(ctxFoo.matched)
        print(ctxBar.matched)

但是,请注意,对于过滤器方法,当前日志记录级别很重要(可以使用level参数覆盖),并且每个感兴趣的记录器也需要一个过滤器。您可以按照这种方法制作更适合您案例的方法。

更新

另外,Python 2的unittest2后端有assertLogs

答案 2 :(得分:0)

我找到了这个解决方案:

def test_foo(self):

    logs=[]

    def my_log(self, *args, **kwargs):
        logs.append((args, kwargs))

    with mock.patch('logging.Logger._log', my_log):
        ...

答案 3 :(得分:0)

模块testfixtures有一个类来处理这个问题:

>>> import logging
>>> from testfixtures import LogCapture
>>> with LogCapture() as l:
...     logger = logging.getLogger()
...     logger.info('a message')
...     logger.error('an error')

>>> l.check(
...     ('root', 'INFO', 'a message'),
...     ('root', 'ERROR', 'another error'),
...     )
Traceback (most recent call last):
 ...
AssertionError: sequence not as expected:

same:
(('root', 'INFO', 'a message'),)

expected:
(('root', 'ERROR', 'another error'),)

actual:
(('root', 'ERROR', 'an error'),)

来源:http://testfixtures.readthedocs.io/en/latest/logging.html