2013-12-21 13 views
10

Ich versuche, einen Unit Test für ein Stück Python-Code zu schreiben, der unter bestimmten Bedingungen eine Warnung über logger.warn('...') auslöst. Wie bestätige ich, dass diese Warnung protokolliert wurde? Mir ist aufgefallen, dass assertLogged erst ab Python 3.4 verfügbar ist, leider bin ich in 2.7.Python 2.7 Komponententest: Assert Logger Warnung ausgelöst

Antwort

2

Fügen Sie in der Konfiguration des Einheitentests einen Protokollierungshandler hinzu, der Datensätze puffert und während des Teardowns entfernt. Sie können als Basis a couple of utility classes, TestHandler and Matcher verwenden, die Teil der Python-Testinfrastruktur sind. (Der Link ist zu Pythons Standardzweig, aber die Klassen sollten in anderen Python-Versionen verwendbar sein). Informationen zur Verwendung dieser Klassen finden Sie unter this post.

2

Python 3.4 Hinzugefügt zu Unittest genau diese Funktion. Siehe TestCase.assertLogs. Die API ist wirklich einfach zu bedienen:

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

Nun ist diese Frage python2.7 markiert, aber es wird sich zeigen, wenn Suche nach ähnlichen Titel für python + unittest + logging. Und es ist ziemlich einfach zu-Port zurück, die python2.7 verfügen, so ist es hier:

# 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) 

Jetzt in Ihrem Komponententests Module Sie diese Klasse verwenden können:

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