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

150

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

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

logging.basicConfig(level=logging.DEBUG)

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

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

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

  • 0
    Как вы включили ведение журнала во время выполнения тестов? и почему вы не используете Django LOGGING?
Теги:
unit-testing
logging

10 ответов

216
Лучший ответ
logging.disable(logging.CRITICAL)

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

logging.disable(logging.NOTSET)
  • 28
    Это может быть очевидным, но я считаю полезным иногда указывать очевидное в интересах других читателей: вы должны поместить вызов logging.disable (из принятого ответа) в начало tests.py в вашем приложении, которое выполняет протоколирование.
  • 6
    Я закончил тем, что поместил вызов в setUp (), но ваша точка зрения хорошо принята.
Показать ещё 4 комментария
38

Поскольку вы находитесь в 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', ...]
  • 0
    Конечно, если поместить его в settings.py, он станет глобальным.
  • 6
    для Django 1.6+ проверьте ответ @alukach.
Показать ещё 1 комментарий
19

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

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

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

  • 0
    «Любой бегущий на тесте, который я использую, загружает код Django так, что это делает его истинным». Интересно ... Как?
  • 0
    У меня есть файл test_settings.py который находится рядом с файлом settings.py . Он настроен на загрузку settings.py и внесение некоторых изменений, например, установите TESTING_MODE в True . Мои организаторы тестов организованы так, что test_settings - это модуль, загружаемый для настроек проекта Django. Есть много способов сделать это. Обычно я устанавливаю переменную окружения DJANGO_SETTINGS_MODULE proj.test_settings .
Показать ещё 1 комментарий
2

Я обнаружил, что для тестов в 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)
2

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

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.

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

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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню