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


168

我正在使用一个基于单元测试的简单测试运行器来测试我的Django应用程序。

我的应用程序本身配置为在settings.py中使用基本记录器,方法是:

logging.basicConfig(level=logging.DEBUG)

在我的应用程序代码中使用:

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

但是,在运行单元测试时,我想禁用日志记录,以免混乱我的测试结果输出。有没有一种简单的方法可以以全局方式关闭日志记录,以便在运行测试时,特定于应用程序的记录器不会将内容写到控制台上?


在运行测试时如何启用日志记录?为什么不使用Django LOGGING?
达洛尔

Answers:


249
logging.disable(logging.CRITICAL)

将禁用所有级别不低于或等于的日志记录调用CRITICAL。可以通过以下方式重新启用日志记录

logging.disable(logging.NOTSET)

42
这可能很明显,但是我发现有时为了其他读者的利益而陈述明显的观点是有帮助的:您可以将调用logging.disable(从接受的答案)放在tests.py正在执行日志记录的应用程序的顶部。
CJ Gaconnet

7
我最终将呼叫放入setUp()中,但您的观点是正确的。
shreddd 2011年

在您的测试的setUp()方法中,或在实际的生成您要隐藏的日志消息的测试中。
qris 2013年

10
在您的tearDown()方法中:logging.disable(logging.NOTSET)将日志整齐地放回原处。
mlissner 2013年

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中将使其全局。
2011年

7
对于Django 1.6+,请检查@alukach答案。
哈塞克2014年

2
有时在单元测试中,我想断言记录了一个错误,因此此方法并不理想。不过,这一个很好的答案。
Sardathrion-反对SE滥用2016年

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

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

最终结果:当我进行测试时,没有任何内容进入控制台,但其他一切保持不变。

为什么这样做?

我设计的代码包含仅在特定情况下触发的日志记录指令,如果出现问题,该指令应输出我诊断所需的确切数据。因此,我测试了他们执行了应做的事情,因此完全禁用日志记录对我而言不可行。我不想在软件投入生产后发现我认为要记录的内容没有记录下来。

此外,一些测试运行程序(例如,Nose)将在测试过程中捕获日志,并输出日志的相关部分以及测试失败。在弄清楚测试失败的原因时很有用。如果日志记录已完全关闭,则无法捕获任何内容。


“我使用的任何测试运行程序都将以使其正确的方式加载Django代码。” 有趣的...如何?
webtweakers

我的test_settings.py文件位于项目的旁边settings.py。它设置为加载settings.py并进行一些更改,例如设置TESTING_MODETrue。我的测试执行者井井有条,因此test_settings是为Django项目设置加载的模块。有很多方法可以做到这一点。我通常将环境变量设置DJANGO_SETTINGS_MODULEproj.test_settings
路易斯

这太棒了,正是我想要的。在单元测试期间隐藏日志记录,直到出现故障为止-然后Django Nose拾取输出并输出失败的输出。完善。将其与结合 以确定单元测试是否处于活动状态。
rrauenza

21

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

经过很多尝试,我找到了您的解决方案。但是我不能在设置中设置变量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

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

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

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.