ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

logging.basicConfig(level=logging.DEBUG)

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

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

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

您是如何在运行测试时启用日志记录的?为什么不使用 django LOGGING?

u
unutbu
logging.disable(logging.CRITICAL)

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

logging.disable(logging.NOTSET)

这可能是显而易见的,但我发现有时为了其他读者的利益陈述显而易见的事情是有帮助的:您可以在您正在执行记录。
我最终将电话放在 setUp() 中,但您的观点很好。
在测试的 setUp() 方法中,或者在生成要隐藏的日志消息的实际测试中。
在您的 tearDown() 方法中:logging.disable(logging.NOTSET) 将日志记录整齐地放回原处。
将它放在 tests 模块的 init.py 中非常有用。
v
vinzee

由于您在 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 中会使它成为全球性的。
对于 Django 1.6+,请查看@alukach 答案。
有时在单元测试中,我想断言记录了一个错误,因此这种方法并不理想。不过,这是一个很好的答案。
b
bignose

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

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


“我使用的任何测试运行程序都会以使其为 True 的方式加载 Django 代码。”有趣...怎么样?
我的项目 settings.py 旁边有一个 test_settings.py 文件。它设置为加载 settings.py 并进行一些更改,例如将 TESTING_MODE 设置为 True。我的测试运行器被组织成 test_settings 是为 Django 项目设置加载的模块。有很多方法可以做到这一点。我通常会将环境变量 DJANGO_SETTINGS_MODULE 设置为 proj.test_settings
这太棒了,完全符合我的要求。在单元测试期间隐藏日志记录,直到出现故障——然后 Django Nose 拾取输出并将其与故障一起打印。完美的。将其与 this 结合以确定单元测试是否处于活动状态。
我只是浪费了几个小时试图找出我的 assertLogs 失败的原因,这是由于 logging.disable(logging.CRITICAL)。以某种方式关闭控制台输出比完全破坏日志记录要好得多。
没有内置的 TESTING_MODE(我刚刚检查过),所以需要稍微修改一下。可以将以下内容添加到 settings.py:TESTING_MODE = 'test' in sys.argv
a
alukach

我喜欢 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 的单独应用程序中有我的,它只有不从项目中其他任何地方导入的实用程序。
m
mcguip

我发现对于 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)

v
valex

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

foo.py:

import logging


logger = logging.getLogger(__name__)

def bar():
    logger.error('There is some error output here!')
    return True

测试.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 不会产生任何日志输出。


E
Eduard Mukans

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

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

K
Karim N Gorjux

有时您需要日志,有时则不需要。我的 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.

如果您想加快持续集成流程的测试速度,这将非常有帮助。


这应该在 manage.py 中,而不是在 settings.py
我非常喜欢这个答案,因为它可以让您在命令行中控制日志级别。这在禁用 CI 日志记录但保持启用以运行本地测试时很有用。非常感谢。
R
Rafael Beirigo

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

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

谢谢,这是我一直在寻找的解决方案。
R
Roger Dahl

如果您使用 pytest

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

LOGGING_CONFIG = None

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

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

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


N
Nathan Villaescusa

在我希望暂时禁止特定记录器的情况下,我编写了一个小上下文管理器,我发现它很有用:

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 完成,记录器就会重新启用(或设置回其先前状态)。


A
Aaron Lelevier

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

# tests/unit/__init__.py
import logging

logging.disable(logging.CRITICAL)

L
Leila Hadj-Chikh

我的一些测试包含有关日志输出的断言,因此更改日志级别会破坏它们。相反,我将 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',
        },
    }
}

t
the pillow

如果您不希望它在 setUp() 和 tearDown() 中反复打开/关闭 unittest (看不到原因),您可以在每个课程中执行一次:

    import unittest
    import logging

    class TestMyUnitTest(unittest.TestCase):
        @classmethod
        def setUpClass(cls):
            logging.disable(logging.CRITICAL)
        @classmethod
        def tearDownClass(cls):
            logging.disable(logging.NOTSET)

z
zushe

bh

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

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

D
Dmitrii Mikhailov

就我而言,我有一个专门为测试目的而创建的设置文件 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


m
marc_s

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


s
scorphus

如果您使用的是 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")

K
Klaas van Schelven

如果您在 2021 年或之后对此感到疑惑,您可能会问错问题

在现代版本的 Django* 上,使用开箱即用的配置,测试不应在屏幕上产生任何日志记录。因此,如果您提出这个问题,真正的答案可能是“配置错误”。这是因为(默认情况下):

将日志消息打印到控制台需要调试为 True

使用 DEBUG=False 运行测试

因此,如果您使用的记录器与您在 LOGGING['loggers'] 中定义并由 "console" 处理程序处理的记录器相匹配,则测试不应在屏幕上产生任何日志记录。

如果你在测试中看到一些东西,你要么

记录器的名称与您在 settings.LOGGING 中定义的名称不匹配

正在使用 DEBUG=True 运行测试(需要覆盖)

已从控制台处理程序的过滤器中删除“require_debug_true”。

*现代版本意味着:2.1 及以上,即不是古代。


不过,有一点... Django 记录设置 LOGGING 的方式是完全定义它并覆盖默认设置。人们需要确保他们以相同的方式重新定义了 'console' 处理程序,以便它仍然有一个过滤器来在 DEBUG=False 时停止输出。
@TimTisdall 这是正确的,也是一个很好的补充。我忘记了这一点,因为我倾向于通过对默认值进行深度复制来设置我的 LOGGING,并且只更改我想要更改的那些部分。
f
flix

我们使用 structlog,禁用它有点复杂:

from structlog import DropEvent

def disable_logging_in_tests(_, __, event_dict):
    if len(sys.argv) > 1 and sys.argv[1] == 'test':
        raise DropEvent
    return event_dict


structlog.configure(
    processors=[
        ...
        disable_logging_in_tests,
    ]
    ...

)