PyDev unittesting: как захватить текст, зарегистрированный в logging.Logger в "Captured Output"
Я использую PyDev для разработки и модульного тестирования моего приложения Python. Что касается модульного тестирования, все работает отлично, за исключением того факта, что контент не регистрируется в каркасе журналирования. Регистратор не захвачен "Захваченным выводом" PyDev.
Я уже пересылаю все зарегистрированные на стандартный вывод, как это:
import sys
logger = logging.getLogger()
logger.level = logging.DEBUG
logger.addHandler(logging.StreamHandler(sys.stdout))
Тем не менее, "Захваченный вывод" не отображает материал, зарегистрированный в регистраторах.
Вот пример unittest-скрипта: 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")
Консольный вывод:
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
Но ЗАХВАТЫВАЕМЫЙ ВЫХОД для теста:
======================== CAPTURED OUTPUT =========================
AA
Кто-нибудь знает, как захватить все, что зарегистрировано в logging.Logger
во время выполнения этого теста?
Ответы
Ответ 1
Проблема заключается в том, что бегун unittest
заменяет sys.stdout
/sys.stderr
до начала тестирования, а StreamHandler
все еще записывает оригинал sys.stdout
.
Если вы назначаете "current" sys.stdout
обработчику, он должен работать (см. код ниже).
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")
Хотя лучше всего было бы добавить/удалить обработчик во время теста:
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)
Ответ 2
Я устал от необходимости вручную добавлять Fabio отличный код ко всем setUp
s, поэтому я подклассифицировал unittest.TestCase
с помощью __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)
Теперь ваш тестовый пример может просто наследовать от LoggedTestCase
, т.е. class TestCase(LoggedTestCase)
вместо class TestCase(unittest.TestCase)
, и все готово. В качестве альтернативы вы можете добавить строку __metaclass__
и определить logger
либо в тесте, либо в слегка измененном LogThisTestCase
.
Ответ 3
Я бы предложил использовать LogCapture и протестировать, что вы действительно регистрируете то, что вы ожидаете регистрировать:
http://testfixtures.readthedocs.org/en/latest/logging.html
Ответ 4
Я столкнулся с этой проблемой. Я закончил подклассификацию StreamHandler и переопределив атрибут stream с свойством sys.stdout. Таким образом, обработчик будет использовать поток, который unittest.TestCase поменял на sys.stdout:
class CapturableHandler(logging.StreamHandler):
@property
def stream(self):
return sys.stdout
@stream.setter
def stream(self, value):
pass
Затем вы можете настроить обработчик ведения журнала до запуска таких тестов (это добавит пользовательский обработчик в корневой журнал):
def setup_capturable_logging():
if not logging.getLogger().handlers:
logging.getLogger().addHandler(CapturableHandler())
Если, как и я, у вас есть свои тесты в отдельных модулях, вы можете просто поместить строку после импорта каждого модуля unit test, который будет проверять, что ведение журнала настроено до запуска тестов:
import logutil
logutil.setup_capturable_logging()
Это может быть не самый чистый подход, но он довольно прост и хорошо работает для меня.
Ответ 5
Если у вас есть разные модули инициализатора для тестирования, разработки и производства, вы можете отключить что-либо или перенаправить в инициализаторе.
У меня есть local.py, test.py и production.py, которые все наследуются от common.y
common.py выполняет все основные настройки, включая этот фрагмент:
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,
},
}
Тогда в test.py у меня есть это:
console_logger = Common.LOGGING.get('handlers').get('console')
console_logger['class'] = 'logging.FileHandler
console_logger['filename'] = './unitest.log
Это заменяет обработчик консоли FileHandler и означает, что регистрация по-прежнему ведется, но мне не нужно трогать базу производственного кода.