Проверка юнитов PyDev: Как захватить текст, записанный в журнал. Журнал в «Захваченном выводе» - PullRequest
48 голосов
/ 19 сентября 2011

Я использую 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 для теста:

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

Кто-нибудь знает, как захватить все, что записано в logging.Logger во время выполнения этого теста?

Ответы [ 5 ]

55 голосов
/ 20 сентября 2011

Проблема в том, что бегун 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)
18 голосов
/ 12 апреля 2013

Я устал от необходимости вручную добавлять отличный код Фабио ко всем setUp с, поэтому я подклассифицировал 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.

8 голосов
/ 08 января 2015

Я бы предложил использовать LogCapture и протестировать, что вы действительно регистрируете то, что ожидаете регистрировать:

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

1 голос
/ 22 октября 2015

Я тоже сталкивался с этой проблемой. В итоге я создал подкласс 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())

Если, как и я, у вас есть свои тесты в отдельных модулях, вы можете просто поставить строку после импорта каждого модуля модульного тестирования, которая будет проверять регистрацию перед запуском тестов:

import logutil

logutil.setup_capturable_logging()

Возможно, это не самый чистый подход, но он довольно прост и хорошо работает для меня.

0 голосов
/ 27 марта 2019

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

Затем в тесте.py У меня есть это:

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

Это заменяет обработчик консоли FileHandler и означает, что по-прежнему получать журналы, но мне не нужно трогать базу производственного кода.

...