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

时间:2011-03-10 04:59:55

标签: 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时,我想禁用日志记录,以免它使我的测试结果输出混乱。是否有一种以全局方式关闭日志记录的简单方法,以便特定于应用程序的记录器在运行测试时不会将内容写入控制台?

18 个答案:

答案 0 :(得分:221)

logging.disable(logging.CRITICAL)

将禁用级别低于或等于CRITICAL的所有日志记录调用。可以使用

重新启用日志记录
logging.disable(logging.NOTSET)

答案 1 :(得分:41)

由于你在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', ...]

答案 2 :(得分:20)

我喜欢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)

答案 3 :(得分:19)

  

是否有一种以全局方式关闭日志记录的简单方法,以便特定于应用程序的记录器在运行测试时不会将内容写入控制台?

其他答案通过全局设置日志记录基础结构以忽略任何内容来阻止“将内容写入控制台”。这有效,但我发现这种做法过于直率。我的方法是执行配置更改,该配置更改仅执行阻止日志在控制台上运行所需的内容。所以我向settings.py添加了custom logging filter

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

configure the Django logging使用过滤器:

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)将在测试期间捕获日志并输出日志的相关部分以及测试失败。它有助于确定测试失败的原因。如果完全关闭日志记录,则无法捕获任何内容。

答案 4 :(得分: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将不会产生日志输出。

答案 5 :(得分:3)

我发现对于unittest或类似框架内的测试,在单元测试中安全禁用不需要的日志记录的最有效方法是在setUp / {{1}中启用/禁用特定测试用例的方法。这使得一个目标特别适用于禁用日志的位置。您也可以在正在测试的类的记录器上显式执行此操作。

tearDown

答案 6 :(得分: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.

如果您希望加速持续集成流程的测试,那将非常有用。

答案 7 :(得分:1)

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

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

答案 8 :(得分: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完成后,记录器将重新启用(或重新设置为以前的状态)。

答案 9 :(得分:1)

您可以将其放在单元测试__init__.py文件的顶级目录中。这将禁用单元测试套件中的全局日志记录。

# tests/unit/__init__.py
import logging

logging.disable(logging.CRITICAL)

答案 10 :(得分:0)

b.h.

对我来说很有效 - 在 conftest.py 中:

 import confing
 # disable logging for tests
 [logging.disable(level) for level in [logging.DEBUG,
                                       logging.INFO,
                                       logging.ERROR,
                                       logging.CRITICAL]]

答案 11 :(得分:0)

禁用整个模块的日志记录:

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

答案 12 :(得分:0)

如果您使用的是 pytest,您可以安装超级有用的 pytest-mock 插件并定义一个自动使用的、会话范围的装置,该装置可以由 env var 触发:

# conftest.py

import os
import pytest


@pytest.fixture(autouse=True, scope="session")
def _shut_logger(session_mocker):
    if os.getenv("SHUT_LOGGER", None):
        return session_mocker.patch("foo.logger")

答案 13 :(得分:0)

我的一些测试包含有关日志输出的断言,因此更改日志级别会破坏它们。相反,我在运行测试时将Django LOGGING设置更改为使用NullHandler

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

答案 14 :(得分:0)

如果您使用的是pytest

由于pytest捕获日志消息并仅在失败的测试中显示它们,因此您通常不想禁用任何日志记录。相反,请使用单独的settings.py文件进行测试(例如test_settings.py),然后添加到文件中:

LOGGING_CONFIG = None

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

使用这种方法,对于通过的测试,您不会获得任何日志记录,对于失败的测试,您将获得所有可用的日志记录。

将使用pytest设置的日志记录运行测试。您可以根据自己的喜好在pytest设置(例如tox.ini)中进行配置。要包含调试级别的日志消息,请使用log_level = DEBUG(或相应的命令行参数)。

答案 15 :(得分: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替换了控制台处理程序,意味着仍然可以记录日志,但是我不必接触生产代码库。

答案 16 :(得分:0)

如果您不希望它在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)

答案 17 :(得分: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