Programing

Python Django에서 단위 테스트를 실행하는 동안 로깅을 비활성화하려면 어떻게합니까?

lottogame 2020. 6. 5. 08:02
반응형

Python Django에서 단위 테스트를 실행하는 동안 로깅을 비활성화하려면 어떻게합니까?


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)

장고에 있기 때문에 설정에 다음 줄을 추가 할 수 있습니다.

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.

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

그리고 필터를 사용 하도록 장고 로깅구성합니다 .

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)는 테스트 중에 로그를 캡처하고 테스트 실패와 함께 로그의 관련 부분을 출력합니다. 테스트가 실패한 이유를 알아내는 데 유용합니다. 로깅이 완전히 해제 된 경우 캡처 할 수있는 것이 없습니다.


Hassek의 맞춤형 테스트 러너 아이디어가 마음에 듭니다. 그것은 그 관찰되지해야 DjangoTestSuiteRunner더 이상 장고 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)

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있습니다.

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로깅 출력을 생성하지 않습니다.


특정 메소드에서 로깅을 비활성화하기 위해 간단한 메소드 데코레이터를 사용하고 있습니다.

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

때로는 로그를 원하지만 때로는 원하지 않습니다. 나는이 코드를 가지고있다.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.

지속적인 통합 흐름에서 테스트 속도를 높이려는 경우 매우 유용합니다.


단위 테스트를 위해 setUp () 및 tearDown ()에서 반복적으로 켜거나 끄지 않으려면 (그 이유를 알지 못함) 클래스 당 한 번만 수행하면됩니다.

    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.


If you have different initaliser modules for test, dev and production then you can disable anything or redirect it in the initialser. I have local.py, test.py and production.py that all inherit from common.y

common.py does all the main config including this snippet :

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,
    },
}

Then in test.py I have this:

console_logger = Common.LOGGING.get('handlers').get('console')
console_logger['class'] = 'logging.FileHandler
console_logger['filename'] = './unitest.log

This replaces the console handler with a FileHandler and means still get logging but I do not have to touch the production code base.


If you're using pytest:

Since pytest captures log messages and only displays them for failed tests, you typically don't want to disable any logging. Instead, use a separate settings.py file for tests (e.g., test_settings.py), and add to it:

LOGGING_CONFIG = None

This tells Django to skip configuring the logging altogether. The LOGGING setting will be ignored and can be removed from the settings.

With this approach, you don't get any logging for passed tests, and you get all available logging for failed tests.

The tests will run using the logging that was set up by pytest. It can be configured to your liking in the pytest settings (e.g., tox.ini). To include debug level log messages, use log_level = DEBUG (or the corresponding command line argument).


In cases where I wish to temporarily suppress a specific logger, I've written a little context manager that I've found useful:

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

You then use it like:

class MyTestCase(TestCase):
    def test_something(self):
        with disable_logger('<logger name>'):
            # code that causes the logger to fire

This has the advantage that the logger is re-enabled (or set back to its prior state) once the with completes.

참고URL : https://stackoverflow.com/questions/5255657/how-can-i-disable-logging-while-running-unit-tests-in-python-django

반응형