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

168

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

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

logging.basicConfig(level=logging.DEBUG)

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

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

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

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

Ответы:

249
logging.disable(logging.CRITICAL)

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

logging.disable(logging.NOTSET)
unutbu
источник
42
Это может быть очевидным, но я считаю полезным иногда указывать очевидное для пользы других читателей: вы бы поставили вызов logging.disable(из принятого ответа) вверху tests.pyприложения, в котором ведется ведение журнала.
CJ Gaconnet
7
Я закончил тем, что поместил вызов в setUp (), но ваша точка зрения хорошо принята.
Шредд
в методе setUp () вашего теста или в реальном тесте, который генерирует сообщения журнала, которые вы хотите скрыть.
'15
10
И в вашем tearDown()методе: logging.disable(logging.NOTSET)аккуратно помещает регистрацию на место.
mlissner
34
Поместить его в init .py testsмодуля очень полезно.
Toabi
46

Поскольку вы находитесь в 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', ...]
Hassek
источник
Конечно, размещение в settings.py сделает его глобальным.
Шредд
7
для Django 1.6+ проверьте ответ @alukach.
Хассек
2
Иногда в модульных тестах я хочу утверждать, что ошибка была зарегистрирована, таким образом, этот метод не идеален. Тем не менее, это является хорошим ответом.
Сардатрион - против злоупотребления SE
23

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

Другие ответы не позволяют «записывать данные на консоль», глобально настраивая инфраструктуру ведения журналов, чтобы она ничего не игнорировала. Это работает, но я считаю это слишком грубым подходом. Мой подход заключается в том, чтобы выполнить изменение конфигурации, которое делает только то, что необходимо для предотвращения выхода журналов на консоль. Поэтому я добавляю собственный фильтр журналирования к своему 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) собирают журналы во время тестирования и выводят соответствующую часть журнала вместе с ошибкой теста. Полезно выяснить, почему тест не удался. Если ведение журнала полностью отключено, то ничего не может быть захвачено.

Луис
источник
«Любой бегущий на тесте, который я использую, загружает код Django так, что это делает его истинным». Интересно ... Как?
webtweakers
У меня есть test_settings.pyфайл, который находится рядом с моим проектом settings.py. Он настроен для загрузки settings.pyи внесения некоторых изменений, например, TESTING_MODEв True. Мои организаторы тестов организованы таким образом, test_settingsчтобы модуль загружался для настроек проекта Django. Есть много способов сделать это. Я обычно иду с установкой переменной среды DJANGO_SETTINGS_MODULEв proj.test_settings.
Луи
Это круто и делает именно то, что я хочу. Скрывает регистрацию во время юнит-тестов, пока что-то не выходит из строя - тогда Django Nose берет вывод и печатает его с ошибкой. Отлично. Объедините его с этим, чтобы определить, активно ли юнит-тестирование.
rrauenza
21

Мне нравится идея Хасека по тестированию бегуна. Следует отметить, что 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)
alukach
источник
Я нашел ваше решение после многих попыток. Однако я не могу установить переменную TEST_RUNNER в настройках, поскольку она не может импортировать модуль, где находится файл test_runner.
Кролик Банни
Похоже, проблема импорта. Вы устанавливаете TEST_RUNNER для строкового пути к бегуну (не фактический модуль Python)? Кроме того, где находится ваш бегун? У меня есть мое имя в отдельном приложении helpers, в котором есть только утилиты, которые не импортируются из других мест проекта.
Алукач
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)
mcguip
источник
4

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

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
Эдуард Муканс
источник
3

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

Валлекс
источник
1

Иногда вы хотите журналы, а иногда нет. У меня есть этот код в моем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.

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

Карим Н Горюкс
источник
1

Если вы не хотите, чтобы он повторно включал / выключал его в 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)
подушка
источник
1

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

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завершения.

Натан Вильяэскуса
источник
1

Вы можете поместить это в каталог верхнего уровня для __init__.pyфайла модульных тестов . Это отключит глобальное ведение журнала в модульном тесте.

# tests/unit/__init__.py
import logging

logging.disable(logging.CRITICAL)
Аарон Лелевье
источник
0

В моем случае у меня есть файл настроек, 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.

Дмитрий Михайлов
источник
0

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

Кристофер Бродерик
источник
0

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

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

LOGGING_CONFIG = None

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

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

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

Роджер Даль
источник