Ответ 1
logging.disable(logging.CRITICAL)
отключит все вызовы журналов с уровнями, меньшими или равными CRITICAL
. Ведение журнала можно повторно включить с помощью
logging.disable(logging.NOTSET)
Я использую простой тестовый бегун unit test для тестирования моего приложения Django.
Мое приложение настроено на использование основного регистратора в settings.py, используя:
logging.basicConfig(level=logging.DEBUG)
И в моем коде приложения, используя:
logger = logging.getLogger(__name__)
logger.setLevel(getattr(settings, 'LOG_LEVEL', logging.DEBUG))
Однако при запуске unittests я хотел бы отключить ведение журнала, чтобы он не загромождал результат теста. Есть ли простой способ отключить ведение журнала глобальным способом, чтобы специфические для регистрации приложения не записывали информацию на консоль при выполнении тестов?
logging.disable(logging.CRITICAL)
отключит все вызовы журналов с уровнями, меньшими или равными CRITICAL
. Ведение журнала можно повторно включить с помощью
logging.disable(logging.NOTSET)
Поскольку вы находитесь в 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', ...]
Существует ли простой способ отключить ведение журнала глобальным способом, чтобы при запуске тестов определенные логгеры приложений не выводили данные на консоль?
Другие ответы не позволяют "записывать данные на консоль", глобально настраивая инфраструктуру ведения журнала на игнорирование чего-либо. Это работает, но я считаю это слишком грубым подходом. Мой подход заключается в том, чтобы выполнить изменение конфигурации, которое делает только то, что необходимо для предотвращения выхода журналов на консоль. Поэтому я добавляю собственный фильтр регистрации в мои 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) будут захватывать журналы во время тестирования и выводить соответствующую часть журнала вместе с ошибкой теста. Полезно выяснить, почему тест не удался. Если регистрация полностью отключена, то ничего не может быть захвачено.
Мне нравится идея выбора тестового теста 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)
Я обнаружил, что для тестов в 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)
Существует несколько симпатичных и чистых методов для приостановки регистрации в тестах с 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
будут давать результатов регистрации.
Я использую простой метод декоратор, чтобы отключить ведение журнала только в конкретном методе теста.
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
Иногда вам нужны журналы, а иногда нет. У меня есть этот код в моем 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.
Это очень полезно, если вы хотите ускорить тестирование непрерывного потока интеграции.
Если вы не хотите, чтобы он неоднократно включал/выключал его в 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)
В моем случае у меня есть файл настроек 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
.
Если у вас есть разные модули инициализации для тестирования, разработки и производства, вы можете отключить что-либо или перенаправить его в инициализаторе. У меня есть 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 и означает, что регистрация по-прежнему ведется, но мне не нужно трогать базу производственного кода.
Если вы используете pytest
:
Поскольку pytest перехватывает сообщения журнала и отображает их только для неудачных тестов, обычно не требуется отключать ведение журнала. Вместо этого используйте отдельный файл settings.py
для тестов (например, test_settings.py
) и добавьте к нему:
LOGGING_CONFIG = None
Это говорит Django, чтобы вообще пропустить настройку регистрации. Настройка LOGGING
будет игнорироваться и может быть удалена из настроек.
При таком подходе вы не получаете никаких журналов для пройденных тестов и получаете все доступные журналы для неудачных тестов.
Тесты будут выполняться с использованием регистрации, установленной pytest
. Его можно настроить по своему вкусу в настройках pytest
(например, tox.ini
). Чтобы включить сообщения журнала уровня отладки, используйте log_level = DEBUG
(или соответствующий аргумент командной строки).
В тех случаях, когда я хочу временно отключить определенный регистратор, я написал небольшой менеджер контекста, который мне показался полезным:
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
.