Подтвердить что ты не робот

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 во время выполнения этого теста?

4b9b3361

Ответ 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 и означает, что регистрация по-прежнему ведется, но мне не нужно трогать базу производственного кода.