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

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

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 답변을 확인하십시오.
Hassek

2
때로는 단위 테스트에서 오류가 기록되어이 방법이 이상적이지 않다고 주장하고 싶습니다. 아직도, 그것은 이다 좋은 대답은.
Sardathrion-남용에 반대

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

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

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 코드를 True로 만드는 방식으로로드합니다." 흥미로운 ... 어떻게 요?
webtweakers

나는이 test_settings.py내 프로젝트의 옆에 앉아 파일을 settings.py. 로 설정하고로 설정을 settings.py변경하도록 설정 TESTING_MODE되어 True있습니다. 내 테스트 러너는 test_settingsDjango 프로젝트 설정을 위해로드 된 모듈이 되도록 구성되어 있습니다. 이를 수행하는 방법에는 여러 가지가 있습니다. 나는 보통 환경 변수 DJANGO_SETTINGS_MODULE를 로 설정합니다 proj.test_settings.
Louis

이것은 굉장하고 내가 원하는 것을 정확하게 수행합니다. 무언가 실패 할 때까지 단위 테스트 중에 로깅을 숨기고 Django Nose는 출력을 선택하여 실패로 인쇄합니다. 완전한. 이것 과 결합하여 단위 테스트가 활성화되어 있는지 확인하십시오.
rrauenza

21

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)

많은 것을 시도한 후에 해결책을 찾았습니다. 그러나 test_runner 파일이있는 모듈을 가져올 수 없으므로 변수 TEST_RUNNER를 설정에서 설정할 수 없습니다.
토끼 토끼

가져 오기 문제처럼 들립니다. TEST_RUNNER를 실제 파이썬 모듈이 아닌 러너의 문자열 경로로 설정하고 있습니까? 또한 주자는 어디에 있습니까? 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

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 ()에서 반복적으로 켜거나 끄지 않으려면 (그 이유를 알지 못함) 클래스별로 한 번만 수행하면됩니다.

    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

이것은 장고에게 로깅 구성을 건너 뛰도록 지시합니다. LOGGING설정은 무시되고 설정에서 제거 할 수 있습니다.

이 방법을 사용하면 통과 한 테스트에 대한 로깅을 얻지 못하고 실패한 테스트에 대해 사용 가능한 모든 로깅을 얻습니다.

에 의해 설정된 로깅을 사용하여 테스트가 실행됩니다 pytest. pytest설정 에서 원하는대로 구성 할 수 있습니다 (예 :) tox.ini. 디버그 레벨 로그 메시지를 포함 시키려면 log_level = DEBUG(또는 해당 명령 행 인수)를 사용하십시오.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.