Я использую простой тестовый бегун 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)
logging.disable
(из принятого ответа) в начало tests.py
в вашем приложении, которое выполняет протоколирование.
Поскольку вы находитесь в 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 просто тестировал нужные приложения. Вы можете сделать это, изменив тестовые метки, добавив эту строку в тестовый бегун:
if not test_labels:
test_labels = ['my_app1', 'my_app2', ...]
Мне нравится идея выбора тестового теста 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)
helpers
, в котором есть только утилиты, которые не импортируются из других мест проекта.
Существует ли простой способ отключить ведение журнала глобальным способом, чтобы при запуске тестов определенные логгеры приложений не выводили данные на консоль?
Другие ответы не позволяют "записывать данные на консоль", глобально настраивая инфраструктуру ведения журнала на игнорирование чего-либо. Это работает, но я считаю это слишком грубым подходом. Мой подход заключается в том, чтобы выполнить изменение конфигурации, которое делает только то, что необходимо для предотвращения выхода журналов на консоль. Поэтому я добавляю собственный фильтр регистрации в мои 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) будут захватывать журналы во время тестирования и выводить соответствующую часть журнала вместе с ошибкой теста. Полезно выяснить, почему тест не удался. Если регистрация полностью отключена, то ничего не может быть захвачено.
test_settings.py
который находится рядом с файлом settings.py
. Он настроен на загрузку settings.py
и внесение некоторых изменений, например, установите TESTING_MODE
в True
. Мои организаторы тестов организованы так, что test_settings
- это модуль, загружаемый для настроек проекта Django. Есть много способов сделать это. Обычно я устанавливаю переменную окружения DJANGO_SETTINGS_MODULE
proj.test_settings
.
Я обнаружил, что для тестов в 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
будут давать результатов регистрации.
Иногда вам нужны журналы, а иногда нет. У меня есть этот код в моем 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.
Это очень полезно, если вы хотите ускорить тестирование непрерывного потока интеграции.
Если у вас есть разные модули инициализации для тестирования, разработки и производства, вы можете отключить что-либо или перенаправить его в инициализаторе. У меня есть 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 и означает, что регистрация по-прежнему ведется, но мне не нужно трогать базу производственного кода.
Если вы не хотите, чтобы он неоднократно включал/выключал его в 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
.