Как отключить ведение журнала во время выполнения модульных тестов в Python Django?

Я использую простой тестовый бегун unit test для тестирования моего приложения Django.

Мое приложение настроено на использование основного регистратора в settings.py, используя:

logging.basicConfig(level=logging.DEBUG)

И в моем коде приложения, используя:

logger = logging.getLogger(__name__)
logger.setLevel(getattr(settings, 'LOG_LEVEL', logging.DEBUG))

Однако при запуске unittests я хотел бы отключить ведение журнала, чтобы он не загромождал результат теста. Есть ли простой способ отключить ведение журнала глобальным способом, чтобы специфические для регистрации приложения не записывали информацию на консоль при выполнении тестов?

Ответ 1

logging.disable(logging.CRITICAL)

отключит все вызовы журналов с уровнями, меньшими или равными CRITICAL. Ведение журнала можно повторно включить с помощью

logging.disable(logging.NOTSET)

Ответ 2

Поскольку вы находитесь в Django, вы можете добавить эти строки в ваши settings.py:

import sys
import logging

if len(sys.argv) > 1 and sys.argv[1] == 'test':
    logging.disable(logging.CRITICAL)

Таким образом, вам не нужно добавлять эту строку в каждый setUp() в ваших тестах.

Таким образом, вы также можете внести несколько удобных изменений в свои тестовые потребности.

Существует еще один "более приятный" или "более чистый" способ добавить конкретику в ваши тесты, а именно создать собственного тестового бегуна.

Просто создайте такой класс:

import logging

from django.test.simple import DjangoTestSuiteRunner
from django.conf import settings

class MyOwnTestRunner(DjangoTestSuiteRunner):
    def run_tests(self, test_labels, extra_tests=None, **kwargs):

        # Don't show logging messages while testing
        logging.disable(logging.CRITICAL)

        return super(MyOwnTestRunner, self).run_tests(test_labels, extra_tests, **kwargs)

А теперь добавьте в свой файл settings.py:

TEST_RUNNER = "PATH.TO.PYFILE.MyOwnTestRunner"
#(for example, 'utils.mytest_runner.MyOwnTestRunner')

Это позволяет вам сделать одну действительно удобную модификацию, которой не придерживается другой подход, а именно заставить Django просто протестировать нужные приложения. Вы можете сделать это, изменив test_labels, добавив эту строку в бегущий тест:

if not test_labels:
    test_labels = ['my_app1', 'my_app2', ...]

Ответ 3

Существует ли простой способ отключить ведение журнала глобальным способом, чтобы при запуске тестов определенные логгеры приложений не выводили данные на консоль?

Другие ответы не позволяют "записывать данные на консоль", глобально настраивая инфраструктуру ведения журнала на игнорирование чего-либо. Это работает, но я считаю это слишком грубым подходом. Мой подход заключается в том, чтобы выполнить изменение конфигурации, которое делает только то, что необходимо для предотвращения выхода журналов на консоль. Поэтому я добавляю собственный фильтр регистрации в мои settings.py:

from logging import Filter

class NotInTestingFilter(Filter):

    def filter(self, record):
        # Although I normally just put this class in the settings.py
        # file, I have my reasons to load settings here. In many
        # cases, you could skip the import and just read the setting
        # from the local symbol space.
        from django.conf import settings

        # TESTING_MODE is some settings variable that tells my code
        # whether the code is running in a testing environment or
        # not. Any test runner I use will load the Django code in a
        # way that makes it True.
        return not settings.TESTING_MODE

И я настраиваю логирование Django для использования фильтра:

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'filters': {
        'testing': {
            '()': NotInTestingFilter
        }
    },
    'formatters': {
        'verbose': {
            'format': ('%(levelname)s %(asctime)s %(module)s '
                       '%(process)d %(thread)d %(message)s')
        },
    },
    'handlers': {
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'filters': ['testing'],
            'formatter': 'verbose'
        },
    },
    'loggers': {
        'foo': {
            'handlers': ['console'],
            'level': 'DEBUG',
            'propagate': True,
        },
    }
}

Конечный результат: когда я тестирую, ничего не выходит на консоль, но все остальное остается прежним.

Зачем это делать?

Я разрабатываю код, который содержит инструкции по протоколированию, которые запускаются только при определенных обстоятельствах и которые должны выводить точные данные, необходимые для диагностики, если что-то пойдет не так. Поэтому я проверяю, что они делают то, что должны, и, таким образом, полностью отключают ведение журнала, не является жизнеспособным для меня. Я не хочу найти, когда программное обеспечение будет запущено в производство, что то, что я думал, будет зарегистрировано, не зарегистрировано.

Более того, некоторые тестеры (например, Nose) будут захватывать журналы во время тестирования и выводить соответствующую часть журнала вместе с ошибкой теста. Полезно выяснить, почему тест не удался. Если регистрация полностью отключена, то ничего не может быть захвачено.

Ответ 4

Мне нравится идея выбора тестового теста Hassek. Следует отметить, что DjangoTestSuiteRunner больше не является тестовым бегуном по умолчанию в Django 1.6+, он был заменен на DiscoverRunner. Для поведения по умолчанию тестовый бегун должен быть больше похож:

import logging

from django.test.runner import DiscoverRunner

class NoLoggingTestRunner(DiscoverRunner):
    def run_tests(self, test_labels, extra_tests=None, **kwargs):

        # disable logging below CRITICAL while testing
        logging.disable(logging.CRITICAL)

        return super(NoLoggingTestRunner, self).run_tests(test_labels, extra_tests, **kwargs)

Ответ 5

Я обнаружил, что для тестов в unittest или аналогичной среде наиболее эффективным способом безопасного отключения нежелательной регистрации в модульных тестах является включение/отключение в setUp/tearDown конкретного тестового примера. Это позволяет одной цели, где журналы должны быть отключены. Вы также можете сделать это явно на регистраторе класса, который вы тестируете.

import unittest
import logging

class TestMyUnitTest(unittest.TestCase):
    def setUp(self):
        logging.disable(logging.CRITICAL)

    def tearDown(self):
        logging.disable(logging.NOTSET)

Ответ 6

Существует несколько симпатичных и чистых методов для приостановки регистрации в тестах с unittest.mock.patch метода unittest.mock.patch.

foo.py:

import logging


logger = logging.getLogger(__name__)

def bar():
    logger.error('There is some error output here!')
    return True

tests.py:

from unittest import mock, TestCase
from foo import bar


class FooBarTestCase(TestCase):
    @mock.patch('foo.logger', mock.Mock())
    def test_bar(self):
        self.assertTrue(bar())

И python3 -m unittest tests будут давать результатов регистрации.

Ответ 7

Я использую простой метод декоратор, чтобы отключить ведение журнала только в конкретном методе теста.

def disable_logging(f):

    def wrapper(*args):
        logging.disable(logging.CRITICAL)
        result = f(*args)
        logging.disable(logging.NOTSET)

        return result

    return wrapper

А затем я использую его, как в следующем примере:

class ScenarioTestCase(TestCase):

    @disable_logging
    test_scenario(self):
        pass

Ответ 8

Иногда вам нужны журналы, а иногда нет. У меня есть этот код в моем settings.py

import sys

if '--no-logs' in sys.argv:
    print('> Disabling logging levels of CRITICAL and below.')
    sys.argv.remove('--no-logs')
    logging.disable(logging.CRITICAL)

Итак, если вы запустите свой тест с параметрами --no-logs, вы получите только журналы critical:

$ python ./manage.py tests --no-logs
> Disabling logging levels of CRITICAL and below.

Это очень полезно, если вы хотите ускорить тестирование непрерывного потока интеграции.

Ответ 9

Если вы не хотите, чтобы он неоднократно включал/выключал его в setUp() и tearDown() для unittest (не вижу причины для этого), вы можете просто сделать это один раз для каждого класса:

    import unittest
    import logging

    class TestMyUnitTest(unittest.TestCase):
        @classmethod
        def setUpClass(cls):
            logging.disable(logging.CRITICAL)
        @classmethod
        def tearDownClass(cls):
            logging.disable(logging.NOTSET)

Ответ 10

В моем случае у меня есть файл настроек settings/test.py, созданный специально для целей тестирования, вот что он выглядит:

from .base import *

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': 'test_db'
    }
}

PASSWORD_HASHERS = (
    'django.contrib.auth.hashers.MD5PasswordHasher',
)

LOGGING = {}

Я помещал переменную окружения DJANGO_SETTINGS_MODULE=settings.test в /etc/environment.

Ответ 11

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

Ответ 12

Если вы используете pytest:

Поскольку pytest перехватывает сообщения журнала и отображает их только для неудачных тестов, обычно не требуется отключать ведение журнала. Вместо этого используйте отдельный файл settings.py для тестов (например, test_settings.py) и добавьте к нему:

LOGGING_CONFIG = None

Это говорит Django, чтобы вообще пропустить настройку регистрации. Настройка LOGGING будет игнорироваться и может быть удалена из настроек.

При таком подходе вы не получаете никаких журналов для пройденных тестов и получаете все доступные журналы для неудачных тестов.

Тесты будут выполняться с использованием регистрации, установленной pytest. Его можно настроить по своему вкусу в настройках pytest (например, tox.ini). Чтобы включить сообщения журнала уровня отладки, используйте log_level = DEBUG (или соответствующий аргумент командной строки).

Ответ 13

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

from contextlib import contextmanager
import logging

@contextmanager
def disable_logger(name):
    """Temporarily disable a specific logger."""
    logger = logging.getLogger(name)
    old_value = logger.disabled
    logger.disabled = True
    try:
        yield
    finally:
        logger.disabled = old_value

Затем вы используете это как:

class MyTestCase(TestCase):
    def test_something(self):
        with disable_logger('<logger name>'):
            # code that causes the logger to fire

Преимущество этого заключается в том, что регистратор повторно включается (или возвращается в прежнее состояние) после завершения with.