在PythonDjango中运行单元测试时如何禁用日志记录?

在PythonDjango中运行单元测试时如何禁用日志记录?,python,django,unit-testing,logging,Python,Django,Unit Testing,Logging,我使用一个简单的基于单元测试的测试运行程序来测试我的Django应用程序 我的应用程序本身配置为使用settings.py中的基本记录器,使用: logging.basicConfig(level=logging.DEBUG) 在我的应用程序代码中使用: logger = logging.getLogger(__name__) logger.setLevel(getattr(settings, 'LOG_LEVEL', logging.DEBUG)) 但是,在运行unittests时,我想禁

我使用一个简单的基于单元测试的测试运行程序来测试我的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)
将禁用所有级别低于或等于
严重级别的日志记录调用。可以使用重新启用日志记录

logging.disable(logging.NOTSET)

由于您在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\u标签
将此行添加到测试运行程序中来实现这一点:

if not test_labels:
    test_labels = ['my_app1', 'my_app2', ...]

我喜欢Hassek的定制测试跑步者想法。应该注意的是,
DjangoTestSuiteRunner
不再是Django 1.6+中的默认测试运行程序,它已被
发现运行程序
取代。对于默认行为,测试运行程序应该更像:

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)
有没有一种简单的方法可以以全局方式关闭日志记录,这样特定于应用程序的日志记录程序在我运行测试时就不会将内容写入控制台

logging.disable(logging.CRITICAL)
其他答案通过全局设置日志记录基础设施来忽略任何内容,从而防止“将内容写入控制台”。这是可行的,但我觉得这是一种过于生硬的方法。我的方法是执行一个配置更改,该更改只执行防止日志在控制台上流出所需的操作。因此,我向我的
设置.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)将在测试期间捕获日志,并将日志的相关部分与测试失败一起输出。它有助于找出测试失败的原因。如果日志记录完全关闭,则无法捕获任何内容。

在我的例子中,我有一个专门为测试目的创建的设置文件
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\u SETTINGS\u MODULE=SETTINGS.test
放入
/etc/environment
有时需要日志,有时不需要。我的
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
选项运行测试,您将只获得
关键的
日志:

$ python ./manage.py tests --no-logs
> Disabling logging levels of CRITICAL and below.

如果您想加速连续集成流上的测试,这将非常有用。

有一些漂亮而干净的方法可以使用
unittest.mock.patch
方法暂停登录测试

foo.py

测试.py


python3-m单元测试测试
将不会产生日志输出。

我发现,对于
unittest
或类似框架中的测试,安全禁用单元测试中不需要的日志记录的最有效方法是在特定测试用例的
设置
/
拆卸
方法中启用/禁用。这使一个目标明确地指向应该禁用日志的位置。您还可以在正在测试的类的记录器上显式执行此操作

import unittest
import logging

class TestMyUnitTest(unittest.TestCase):
    def setUp(self):
        logging.disable(logging.CRITICAL)

    def tearDown(self):
        logging.disable(logging.NOTSET)

如果您不想在unittest的setUp()和tearDown()中重复打开/关闭它(不知道原因),您可以只在每个类中执行一次:

导入单元测试
导入日志记录
类TestMyUnitTest(unittest.TestCase):
@类方法
def设置等级(cls):
logging.disable(logging.CRITICAL)
@类方法
def拆卸类(cls):
logging.disable(logging.NOTSET)

如果您有不同的initaliser模块用于测试、开发和生产,那么您可以在initialser中禁用任何东西或重定向它。我有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替换控制台处理程序,这意味着仍然可以获取日志记录,但我不必接触生产代码库。

我正在使用一个简单的方法装饰器来禁用特定测试方法中的日志记录

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

如果您使用的是
pytest

由于pytest捕获日志消息并仅显示失败测试的日志消息,因此通常不希望禁用任何日志记录。相反,使用单独的
settings.py
文件进行测试(例如,
test\u settings.py
),并将以下内容添加到其中:

LOGGING_CONFIG = None
这告诉Django完全跳过配置日志记录。
日志记录
设置将被忽略,并且可以从设置中删除

使用这种方法,您不会为通过的测试获取任何日志记录,而为失败的测试获取所有可用的日志记录

测试将使用
pytest
LOGGING_CONFIG = None
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
if 'test' in sys.argv:
    _LOG_HANDLERS = ['null']
else:
    _LOG_HANDLERS = ['console']
    
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'simple': {
            'format': '%(levelname)s %(message)s'
        },
    },
    'handlers': {
        'null': {
            'level': 'DEBUG',
            'class': 'logging.NullHandler',
        },
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'formatter': 'simple',
        },
    },
    'loggers': {
        'django': {
            'handlers': _LOG_HANDLERS,
            'propagate': True,
            'level': 'INFO',
        },
    }
}
import logging


def setUpModule():
    """Disable logging while doing these tests."""
    logging.disable()


def tearDownModule():
    """Re-enable logging after doing these tests."""
    logging.disable(logging.NOTSET)


class TestFoo(unittest.TestCase):

    def test_foo(self):
        pass