Як я можу відключити ведення журналу під час запуску одиничних тестів у Python Django?


168

Я використовую простий тестовий бігун на базі тестування моєї програми Django.

Сам мій додаток налаштовано на використання базового реєстратора в settings.py, використовуючи:

logging.basicConfig(level=logging.DEBUG)

І в моєму додатку код використовує:

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

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


Як увімкнули ведення журналу під час запуску тестів? а чому ви не використовуєте django LOGGING?
dalore

Відповіді:


249
logging.disable(logging.CRITICAL)

вимкне всі дзвінки в журнал із рівнями менш суворими або рівними CRITICAL. Журнал можна знову ввімкнути за допомогою

logging.disable(logging.NOTSET)

42
Це може бути очевидним, але я вважаю корисним інколи констатувати очевидне на користь інших читачів: Ви б поклали дзвінок logging.disable(з прийнятої відповіді) у верхній частині tests.pyпрограми, яка веде журнал.
CJ Gaconnet

7
Я в кінцевому підсумку поставив дзвінок у setUp (), але ваша думка добре прийнята.
shreddd

у методі setUp () вашого тесту або у фактичному тесті, який генерує повідомлення журналу, які ви хочете приховати.
qris

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', ...]

Впевнений, якщо розмістити його в settings.py, це зробить глобальним.
shreddd

7
для Django 1.6+ перевірте відповідь @alukach.
Хассек

2
Іноді в одиничних тестах я хочу стверджувати, що помилка була зафіксована таким чином, цей метод не є ідеальним. Тим НЕ менше, це є хорошим відповіддю.
Сардатріон - проти зловживання SE

23

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

Інші відповіді заважають "виписувати речі на консоль", встановлюючи глобальну інфраструктуру для того, щоб ігнорувати що-небудь. Це працює, але я вважаю це занадто тупим підходом. Мій підхід полягає в зміні конфігурації, яка робить лише те, що потрібно, щоб журнали не виходили на консоль. Так що я додати користувальницький фільтр протоколювання до My 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,
        },
    }
}

Кінцевий результат: коли я тестую, на консоль нічого не йде, але все інше залишається таким же.

Навіщо це робити?

Я розробляю код, який містить інструкції реєстрації, які спрацьовують лише в конкретних обставинах і повинні виводити точні дані, які мені потрібні для діагностики, якщо все піде не так. Тому я перевіряю, що вони роблять те, що мають робити, і, таким чином, повністю відключити ведення журналу, для мене не під силу. Я не хочу знайти, якби програмне забезпечення вийшло у виробництво, те, що я думав , буде зареєстровано, не реєструється.

Більше того, деякі тестові бігуни (наприклад, Ніс) захоплюватимуть журнали під час тестування та видаватимуть відповідну частину журналу разом із тестом. Корисно з’ясувати, чому тест не вдався. Якщо ведення журналу повністю вимкнено, то нічого не можна зробити.


"Будь-який тестовий бігун, який я використовую, завантажить код 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)

Я знайшов ваше рішення, перепробувавши багато речей. Однак я не в змозі встановити змінну TEST_RUNNER у налаштуваннях, оскільки вона не в змозі імпортувати модуль, де знаходиться файл test_runner.
Кролик зайчика

Здається, що проблема з імпортом. Ви встановлюєте TEST_RUNNER на шлях рядка до бігуна (не власне модуля Python)? Також, де знаходиться ваш бігун? У мене є окреме додаток з назвою helpers, яке містить лише утиліти, які не імпортують з іншого місця в рамках проекту.
alukach

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)

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

test.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

Якщо у вас є різні модулі initaliser для тестування, розробки та виробництва, то ви можете відключити що завгодно або перенаправити його в ініціалізатор. У мене є 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 взагалі пропустити налаштування журналу. TheLOGGINGНалаштування будуть ігноруватися і можуть бути видалені з налаштувань.

При такому підході ви не отримуєте жодного журналу для пройдених тестів, і ви отримуєте всі наявні журнали для невдалих тестів.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.