2013-12-21 50 views
10

我试图编写一段Python代码的单元测试,该代码在特定条件下通过logger.warn('...')发出警告。我如何断言此警告已被记录?我注意到assertLogged直到至少Python 3.4才可用,不幸的是我在2.7。Python 2.7单元测试:Assert Logger警告抛出

回答

2

在您的单元测试设置中,添加一个记录处理程序,用于缓存记录,并在拆卸过程中将其删除。您可以将其用作基准a couple of utility classes, TestHandler and Matcher,它们是Python测试基础结构的一部分。 (链接是Python的默认分支,但这些类应该可以在其他Python版本中使用)。有关如何使用这些类的信息,请参阅this post

2

Python 3.4添加到单元测试的确切功能。请参阅TestCase.assertLogs。这个API是很容易使用:

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']) 

现在,这个问题被标记python2.7,但它会显示在搜索类似的标题python + unittest + logging。它是很容易回端口功能来Python2.7,所以这里是:

# logger_test.py 
# this file contains the base class containing the newly added method 
# assertLogs 
import collections 
import logging 
_LoggingWatcher = collections.namedtuple("_LoggingWatcher", 
             ["records", "output"]) 

class _BaseTestCaseContext(object): 

    def __init__(self, test_case): 
     self.test_case = test_case 

    def _raiseFailure(self, standardMsg): 
     msg = self.test_case._formatMessage(self.msg, standardMsg) 
     raise self.test_case.failureException(msg) 


class _CapturingHandler(logging.Handler): 
    """ 
    A logging handler capturing all (raw and formatted) logging output. 
    """ 

    def __init__(self): 
     logging.Handler.__init__(self) 
     self.watcher = _LoggingWatcher([], []) 

    def flush(self): 
     pass 

    def emit(self, record): 
     self.watcher.records.append(record) 
     msg = self.format(record) 
     self.watcher.output.append(msg) 


class _AssertLogsContext(_BaseTestCaseContext): 
    """A context manager used to implement TestCase.assertLogs().""" 

    LOGGING_FORMAT = "%(levelname)s:%(name)s:%(message)s" 

    def __init__(self, test_case, logger_name, level): 
     _BaseTestCaseContext.__init__(self, test_case) 
     self.logger_name = logger_name 
     if level: 
      self.level = logging._levelNames.get(level, level) 
     else: 
      self.level = logging.INFO 
     self.msg = None 

    def __enter__(self): 
     if isinstance(self.logger_name, logging.Logger): 
      logger = self.logger = self.logger_name 
     else: 
      logger = self.logger = logging.getLogger(self.logger_name) 
     formatter = logging.Formatter(self.LOGGING_FORMAT) 
     handler = _CapturingHandler() 
     handler.setFormatter(formatter) 
     self.watcher = handler.watcher 
     self.old_handlers = logger.handlers[:] 
     self.old_level = logger.level 
     self.old_propagate = logger.propagate 
     logger.handlers = [handler] 
     logger.setLevel(self.level) 
     logger.propagate = False 
     return handler.watcher 

    def __exit__(self, exc_type, exc_value, tb): 
     self.logger.handlers = self.old_handlers 
     self.logger.propagate = self.old_propagate 
     self.logger.setLevel(self.old_level) 
     if exc_type is not None: 
      # let unexpected exceptions pass through 
      return False 
     if len(self.watcher.records) == 0: 
      self._raiseFailure(
       "no logs of level {} or higher triggered on {}" 
       .format(logging.getLevelName(self.level), self.logger.name)) 


class LogTestCase(unittest.TestCase): 

    def assertLogs(self, logger=None, level=None): 
     """Fail unless a log message of level *level* or higher is emitted 
     on *logger_name* or its children. If omitted, *level* defaults to 
     INFO and *logger* defaults to the root logger. 

     This method must be used as a context manager, and will yield 
     a recording object with two attributes: `output` and `records`. 
     At the end of the context manager, the `output` attribute will 
     be a list of the matching formatted log messages and the 
     `records` attribute will be a list of the corresponding LogRecord 
     objects. 

     Example:: 

      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']) 
     """ 
     return _AssertLogsContext(self, logger, level) 

现在在你的单元测试模块,你可以使用这个类:

#test_my_module 
from logger_test import LogTestCase 

class TestMyModule(LogTestCase): 

    def test_some_feature(self): 
     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'])