如何让 python unittest 仅在失败的测试中显示日志消息
How to get python unittest to show log messages only on failed tests
问题
我一直在尝试使用 unittest --buffer 标志来抑制成功测试的日志并显示失败测试的日志。但它似乎无论如何都显示日志输出。这是日志记录模块的怪癖吗?如何仅在测试失败时获取日志输出?记录器上是否需要特殊配置?我发现的其他问题和答案采用蛮力方法在测试期间禁用所有日志记录。
示例代码
import logging
import unittest
import sys
logger = logging.getLogger('abc')
logging.basicConfig(
format = '%(asctime)s %(module)s %(levelname)s: %(message)s',
level = logging.INFO,
stream = sys.stdout)
class TestABC(unittest.TestCase):
def test_abc_pass(self):
logger.info('log abc in pass')
print('print abc in pass')
self.assertTrue(True)
def test_abc_fail(self):
logger.info('log abc in fail')
print('print abc in fail')
self.assertTrue(False)
测试输出
$ python -m unittest --buffer
2021-09-15 17:38:48,462 test INFO: log abc in fail
F
Stdout:
print abc in fail
2021-09-15 17:38:48,463 test INFO: log abc in pass
.
======================================================================
FAIL: test_abc_fail (test.TestABC)
----------------------------------------------------------------------
Traceback (most recent call last):
File ".../test.py", line 22, in test_abc_fail
self.assertTrue(False)
AssertionError: False is not true
Stdout:
print abc in fail
----------------------------------------------------------------------
Ran 2 tests in 3.401s
FAILED (failures=1)
因此缓冲区确实成功抑制了通过测试中 print
语句的输出。但它不会抑制日志输出。
示例代码的解决方案
就在测试运行之前,我们需要更新日志处理程序上的流以指向缓冲区 unittest 已设置用于捕获测试输出。
import logging
import unittest
import sys
logger = logging.getLogger('abc')
logging.basicConfig(
format = '%(asctime)s %(module)s %(levelname)s: %(message)s',
level = logging.INFO,
stream = sys.stdout)
class LoggerRedirector:
# Keep a reference to the real streams so we can revert
_real_stdout = sys.stdout
_real_stderr = sys.stderr
@staticmethod
def all_loggers():
loggers = [logging.getLogger()]
loggers += [logging.getLogger(name) for name in logging.root.manager.loggerDict]
return loggers
@classmethod
def redirect_loggers(cls, fake_stdout=None, fake_stderr=None):
if ((not fake_stdout or fake_stdout is cls._real_stdout)
and (not fake_stderr or fake_stderr is cls._real_stderr)):
return
for logger in cls.all_loggers():
for handler in logger.handlers:
if hasattr(handler, 'stream'):
if handler.stream is cls._real_stdout:
handler.setStream(fake_stdout)
if handler.stream is cls._real_stderr:
handler.setStream(fake_stderr)
@classmethod
def reset_loggers(cls, fake_stdout=None, fake_stderr=None):
if ((not fake_stdout or fake_stdout is cls._real_stdout)
and (not fake_stderr or fake_stderr is cls._real_stderr)):
return
for logger in cls.all_loggers():
for handler in logger.handlers:
if hasattr(handler, 'stream'):
if handler.stream is fake_stdout:
handler.setStream(cls._real_stdout)
if handler.stream is fake_stderr:
handler.setStream(cls._real_stderr)
class TestABC(unittest.TestCase):
def setUp(self):
# unittest has reassigned sys.stdout and sys.stderr by this point
LoggerRedirector.redirect_loggers(fake_stdout=sys.stdout, fake_stderr=sys.stderr)
def tearDown(self):
LoggerRedirector.reset_loggers(fake_stdout=sys.stdout, fake_stderr=sys.stderr)
# unittest will revert sys.stdout and sys.stderr after this
def test_abc_pass(self):
logger.info('log abc in pass')
print('print abc in pass')
self.assertTrue(True)
def test_abc_fail(self):
logger.info('log abc in fail')
print('print abc in fail')
self.assertTrue(False)
如何以及为什么
这个问题是 unittest
如何为测试捕获 stdout
和 stderr
以及 logging
通常如何设置的副作用。通常 logging
在程序执行的早期设置,这意味着日志处理程序将在其实例 (code link). However, just before the test runs, unittest
creates a io.StringIO()
buffer for both streams and reassigns sys.stdout
and sys.stderr
to the new buffers (code link) 中存储对 sys.stdout
和 sys.stderr
的引用。
所以就在测试运行之前,为了让 unittest
捕获日志输出,我们需要告诉日志处理程序将它们的流指向 unittest
设置的缓冲区.测试完成后,流恢复正常。但是,unittest
为每个测试创建一个新缓冲区,因此我们需要在每次测试前后更新日志处理程序。
由于日志处理程序指向 unittest
设置的缓冲区,如果测试失败,则在使用 --buffer
选项时将显示该测试的所有日志。
上面解决方案中的 LoggerRedirector
class 只是提供了方便的方法来将所有可能指向 sys.stdout
或 sys.stderr
的处理程序重新分配给新的缓冲区unittest
已经设置好了,然后是恢复它们的简单方法。因为在 setUp()
运行时,unittest
已经重新分配了 sys.stdout
和 sys.stderr
我们正在使用它们来引用 unittest
已经设置的新缓冲区。
问题
我一直在尝试使用 unittest --buffer 标志来抑制成功测试的日志并显示失败测试的日志。但它似乎无论如何都显示日志输出。这是日志记录模块的怪癖吗?如何仅在测试失败时获取日志输出?记录器上是否需要特殊配置?我发现的其他问题和答案采用蛮力方法在测试期间禁用所有日志记录。
示例代码
import logging
import unittest
import sys
logger = logging.getLogger('abc')
logging.basicConfig(
format = '%(asctime)s %(module)s %(levelname)s: %(message)s',
level = logging.INFO,
stream = sys.stdout)
class TestABC(unittest.TestCase):
def test_abc_pass(self):
logger.info('log abc in pass')
print('print abc in pass')
self.assertTrue(True)
def test_abc_fail(self):
logger.info('log abc in fail')
print('print abc in fail')
self.assertTrue(False)
测试输出
$ python -m unittest --buffer
2021-09-15 17:38:48,462 test INFO: log abc in fail
F
Stdout:
print abc in fail
2021-09-15 17:38:48,463 test INFO: log abc in pass
.
======================================================================
FAIL: test_abc_fail (test.TestABC)
----------------------------------------------------------------------
Traceback (most recent call last):
File ".../test.py", line 22, in test_abc_fail
self.assertTrue(False)
AssertionError: False is not true
Stdout:
print abc in fail
----------------------------------------------------------------------
Ran 2 tests in 3.401s
FAILED (failures=1)
因此缓冲区确实成功抑制了通过测试中 print
语句的输出。但它不会抑制日志输出。
示例代码的解决方案
就在测试运行之前,我们需要更新日志处理程序上的流以指向缓冲区 unittest 已设置用于捕获测试输出。
import logging
import unittest
import sys
logger = logging.getLogger('abc')
logging.basicConfig(
format = '%(asctime)s %(module)s %(levelname)s: %(message)s',
level = logging.INFO,
stream = sys.stdout)
class LoggerRedirector:
# Keep a reference to the real streams so we can revert
_real_stdout = sys.stdout
_real_stderr = sys.stderr
@staticmethod
def all_loggers():
loggers = [logging.getLogger()]
loggers += [logging.getLogger(name) for name in logging.root.manager.loggerDict]
return loggers
@classmethod
def redirect_loggers(cls, fake_stdout=None, fake_stderr=None):
if ((not fake_stdout or fake_stdout is cls._real_stdout)
and (not fake_stderr or fake_stderr is cls._real_stderr)):
return
for logger in cls.all_loggers():
for handler in logger.handlers:
if hasattr(handler, 'stream'):
if handler.stream is cls._real_stdout:
handler.setStream(fake_stdout)
if handler.stream is cls._real_stderr:
handler.setStream(fake_stderr)
@classmethod
def reset_loggers(cls, fake_stdout=None, fake_stderr=None):
if ((not fake_stdout or fake_stdout is cls._real_stdout)
and (not fake_stderr or fake_stderr is cls._real_stderr)):
return
for logger in cls.all_loggers():
for handler in logger.handlers:
if hasattr(handler, 'stream'):
if handler.stream is fake_stdout:
handler.setStream(cls._real_stdout)
if handler.stream is fake_stderr:
handler.setStream(cls._real_stderr)
class TestABC(unittest.TestCase):
def setUp(self):
# unittest has reassigned sys.stdout and sys.stderr by this point
LoggerRedirector.redirect_loggers(fake_stdout=sys.stdout, fake_stderr=sys.stderr)
def tearDown(self):
LoggerRedirector.reset_loggers(fake_stdout=sys.stdout, fake_stderr=sys.stderr)
# unittest will revert sys.stdout and sys.stderr after this
def test_abc_pass(self):
logger.info('log abc in pass')
print('print abc in pass')
self.assertTrue(True)
def test_abc_fail(self):
logger.info('log abc in fail')
print('print abc in fail')
self.assertTrue(False)
如何以及为什么
这个问题是 unittest
如何为测试捕获 stdout
和 stderr
以及 logging
通常如何设置的副作用。通常 logging
在程序执行的早期设置,这意味着日志处理程序将在其实例 (code link). However, just before the test runs, unittest
creates a io.StringIO()
buffer for both streams and reassigns sys.stdout
and sys.stderr
to the new buffers (code link) 中存储对 sys.stdout
和 sys.stderr
的引用。
所以就在测试运行之前,为了让 unittest
捕获日志输出,我们需要告诉日志处理程序将它们的流指向 unittest
设置的缓冲区.测试完成后,流恢复正常。但是,unittest
为每个测试创建一个新缓冲区,因此我们需要在每次测试前后更新日志处理程序。
由于日志处理程序指向 unittest
设置的缓冲区,如果测试失败,则在使用 --buffer
选项时将显示该测试的所有日志。
上面解决方案中的 LoggerRedirector
class 只是提供了方便的方法来将所有可能指向 sys.stdout
或 sys.stderr
的处理程序重新分配给新的缓冲区unittest
已经设置好了,然后是恢复它们的简单方法。因为在 setUp()
运行时,unittest
已经重新分配了 sys.stdout
和 sys.stderr
我们正在使用它们来引用 unittest
已经设置的新缓冲区。