it-roy-ru.com

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

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

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

logging.basicConfig(level=logging.DEBUG)

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

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

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

138
shreddd
logging.disable(logging.CRITICAL)

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

logging.disable(logging.NOTSET)
211
unutbu

Поскольку вы находитесь в 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', ...]
40
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)
20
alukach

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

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

18
Louis

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

2
valex

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

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

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
1
Eduard Mukans

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

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

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

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