Prueba de la unidad de Python 2.7: Probar el aviso de registrador lanzado

Estoy tratando de escribir una prueba de unidad para un fragmento de código de Python que genera una advertencia a través de logger.warn('...') bajo ciertas condiciones. ¿Cómo afirmo que esta advertencia ha sido registrada? Noté que assertLogged no está disponible hasta al menos Python 3.4, desafortunadamente estoy en 2.7.

Python 3.4 Añadido a unittest exactamente esa característica. Ver TestCase.assertLogs . La API es muy fácil de usar:

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

Ahora, esta pregunta está etiquetada como python2.7 pero se mostrará cuando se busque un título similar para python + unittest + logging . Y es bastante fácil realizar una copia de seguridad de esa función para Python2.7, así que aquí está:

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

Ahora en tus módulos de prueba de unidad puedes usar esa clase:

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

En la configuración de prueba de la unidad, agregue un controlador de registro que almacene los registros y elimínelo durante el desassembly. Puede usar como base un par de clases de utilidad, TestHandler y Matcher , que forman parte de la infraestructura de prueba de Python. (El enlace es a la twig predeterminada de Python, pero las clases deberían poder utilizarse en otras versiones de Python). Para obtener información sobre cómo usar estas clases, consulte esta publicación .