Prueba de unidad PyDev: cómo capturar texto registrado en un registro. Registrador en “Salida capturada”

Estoy usando PyDev para el desarrollo y la prueba unitaria de mi aplicación Python. En cuanto a la prueba de unidad, todo funciona muy bien, excepto el hecho de que no se registra ningún contenido en el marco de registro. El registrador no es capturado por la “Salida capturada” de PyDev.

Ya estoy enviando todo lo registrado a la salida estándar de esta manera:

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

Sin embargo, la “Salida capturada” no muestra las cosas registradas en los registradores.

Aquí hay un ejemplo de 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") 

La salida de la consola es:

 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 

Pero la SALIDA CAPTURADA para la prueba es:

 ======================== CAPTURED OUTPUT ========================= AA 

¿Alguien sabe cómo capturar todo lo que se registra en un logging.Logger durante la ejecución de esta prueba?

El problema es que el corredor unittest reemplaza sys.stdout / sys.stderr antes de que sys.stderr la prueba, y StreamHandler aún está escribiendo en el sys.stdout original.

Si asigna sys.stdout ‘actual’ al controlador, debería funcionar (consulte el código a continuación).

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

Aunque, un mejor enfoque sería agregar / eliminar el controlador durante la prueba:

 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) 

Me cansé de tener que agregar manualmente el gran código de Fabio a todas las setUp , así que hice una subclase de unittest.TestCase con algo de __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) 

Ahora su caso de prueba simplemente puede heredar de LoggedTestCase , es decir, la class TestCase(LoggedTestCase) lugar de la class TestCase(unittest.TestCase) y listo. Alternativamente, puede agregar la línea __metaclass__ y definir el logger en la prueba o en un LogThisTestCase ligeramente modificado.

Sugeriría usar un LogCapture y probar que realmente está registrando lo que espera estar registrando:

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

Me encontré con este problema también. Terminé haciendo una subclase de StreamHandler, y reemplazando el atributo de flujo con una propiedad que obtiene sys.stdout. De esa manera, el controlador usará la secuencia que unittest.TestCase ha cambiado a sys.stdout:

 class CapturableHandler(logging.StreamHandler): @property def stream(self): return sys.stdout @stream.setter def stream(self, value): pass 

Luego, puede configurar el controlador de registro antes de ejecutar pruebas de este modo (esto agregará el controlador personalizado al registrador raíz):

 def setup_capturable_logging(): if not logging.getLogger().handlers: logging.getLogger().addHandler(CapturableHandler()) 

Si, como yo, tiene sus pruebas en módulos separados, puede colocar una línea después de las importaciones de cada módulo de prueba de unidad que se asegurará de que el registro esté configurado antes de que se ejecuten las pruebas:

 import logutil logutil.setup_capturable_logging() 

Puede que este no sea el enfoque más limpio, pero es bastante simple y funcionó bien para mí.

Si tiene diferentes módulos de Initaliser para prueba, desarrollo y producción, puede deshabilitar cualquier cosa o redirigirla en el inicializador. Tengo local.py, test.py y production.py que todos heredan de common.y

common.py hace toda la configuración principal incluyendo este fragmento de código:

  LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'django.server': { '()': 'django.utils.log.ServerFormatter', 'format': '[%(server_time)s] %(message)s', }, 'verbose': { 'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s' }, 'simple': { 'format': '%(levelname)s %(message)s' }, }, 'filters': { 'require_debug_true': { '()': 'django.utils.log.RequireDebugTrue', }, }, 'handlers': { 'django.server': { 'level': 'INFO', 'class': 'logging.StreamHandler', 'formatter': 'django.server', }, 'console': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', 'formatter': 'simple' }, 'mail_admins': { 'level': 'ERROR', 'class': 'django.utils.log.AdminEmailHandler' } }, 'loggers': { 'django': { 'handlers': ['console'], 'level': 'INFO', 'propagate': True, }, 'celery.tasks': { 'handlers': ['console'], 'level': 'DEBUG', 'propagate': True, }, 'django.server': { 'handlers': ['django.server'], 'level': 'INFO', 'propagate': False, }, } 

Entonces en test.py tengo esto:

 console_logger = Common.LOGGING.get('handlers').get('console') console_logger['class'] = 'logging.FileHandler console_logger['filename'] = './unitest.log 

Esto reemplaza el controlador de la consola con un FileHandler y significa que aún se obtiene el registro pero no tengo que tocar la base de código de producción.