我知道如何断言生成了日志消息,但我似乎无法弄清楚如何断言没有生成日志消息。这是我现在进行的单元测试(消毒)。请注意,XYZ类将记录器作为参数, test_check_unexpected_keys_found 按预期传递。
import unittest
import logging
class TestXYZ(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.test_logger = logging.getLogger('test_logger')
cls.test_logger.addHandler(logging.NullHandler())
def test_check_unexpected_keys_found(self):
test_dict = {
'unexpected': 0,
'expected1': 1,
'expected2': 2,
}
xyz = XYZ(self.test_logger)
with self.assertLogs('test_logger', level='WARNING'):
xyz._check_unexpected_keys(test_dict)
def test_check_unexpected_keys_none(self):
test_dict = {
'expected1': 1,
'expected2': 2,
}
xyz = XYZ(self.test_logger)
xyz._check_unexpected_keys(test_dict)
# assert that 'test_logger' was not called ??
我尝试使用unittest.patch,如下所示:
with patch('TestXYZ.test_logger.warning') as mock_logwarn:
xyz._check_unexpected_keys(test_dict)
self.assertFalse(mock_logwarn.called)
但是我得到了
ImportError: No module named 'TestXYZ'我也尝试了一些变种,但无处可去。
任何人都知道如何处理这个问题?
答案 0 :(得分:4)
新的assertNoLogs
方法是on route to be added to a future version of Python。
在此之前,这里是一种解决方法:添加一个虚拟日志,然后断言它是唯一的日志。
with self.assertLogs(logger, logging.WARN) as cm:
# We want to assert there are no warnings, but the 'assertLogs' method does not support that.
# Therefore, we are adding a dummy warning, and then we will assert it is the only warning.
logger.warn("Dummy warning")
# DO STUFF
self.assertEqual(
["Dummy warning"],
cm.output,
)
如果您需要多次执行此操作,则为避免重复,可以执行以下操作。假设您有一个继承所有测试类的基类,请按如下所示重写该类中的assertLogs
:
class TestBase(TestCase):
def assertLogs(self, logger_to_watch=None, level=None) -> 'CustomAssertLogsContext':
"""
This method overrides the one in `unittest.case.TestCase`, and has the same behavior, except for not causing a failure when there are no log messages.
The point is to allow asserting there are no logs.
Get rid of this once this is resolved: https://github.com/python/cpython/pull/18067
"""
return CustomAssertLogsContext(self, logger_to_watch, level)
class CustomAssertLogsContext(_AssertLogsContext):
def __exit__(self, exc_type, exc_val, exc_tb) -> Optional[bool]:
# Fool the original exit method to think there is at least one record, to avoid causing a failure
self.watcher.records.append("DUMMY")
result = super().__exit__(exc_type, exc_val, exc_tb)
self.watcher.records.pop()
return result
答案 1 :(得分:1)
基于Joe的回答,这是一个assertNoLogs(...)例程的实现,作为mixin类,可以在Python 3.10中发布正式版本之前使用它:
import logging
import unittest
def assertNoLogs(self, logger, level):
""" functions as a context manager. To be introduced in python 3.10
"""
class AssertNoLogsContext(unittest.TestCase):
def __init__(self, logger, level):
self.logger = logger
self.level = level
self.context = self.assertLogs(logger, level)
def __enter__(self):
""" enter self.assertLogs as context manager, and log something
"""
self.initial_logmsg = "sole message"
self.cm = self.context.__enter__()
self.logger.log(self.level, self.initial_logmsg)
return self.cm
def __exit__(self, exc_type, exc_val, exc_tb):
""" cleanup logs, and then check nothing extra was logged """
# assertLogs.__exit__ should never fail because of initial msg
self.context.__exit__(exc_type, exc_val, exc_tb)
if len(self.cm.output) > 1:
""" override any exception passed to __exit__ """
self.context._raiseFailure(
"logs of level {} or higher triggered on : {}"
.format(logging.getLevelName(self.level),
self.logger.name, self.cm.output[1:]))
return AssertNoLogsContext(logger, level)
要使用它,只需使用以下命令开始测试用例
class Testxxx(unittest.TestCase, AssertNoLog):
...
以下测试案例展示了其工作原理:
import unittest
import logging
class TestAssertNL(unittest.TestCase, AssertNoLog):
def test_assert_no_logs(self):
""" check it works"""
log = logging.getLogger()
with self.assertNoLogs(log, logging.INFO):
_a = 1
log.debug("not an info message")
@unittest.expectedFailure
def test2_assert_no_logs(self):
""" check it records failures """
log = logging.getLogger()
with self.assertNoLogs(log, logging.INFO):
_a = 1
log.info("an info message")
def test3_assert_no_logs_exception_handling(self):
log = logging.getLogger()
with self.assertRaises(TypeError):
with self.assertNoLogs(log, logging.INFO):
raise TypeError('this is not unexpected')
def test4_assert_no_logs_exception_handling(self):
""" the exception gets reported as the failure.
This matches the behaviour of assertLogs(...) """
log = logging.getLogger()
with self.assertRaises(AssertionError):
with self.assertNoLogs(log, logging.INFO):
log.info("an info message")
raise TypeError('this is not unexpected')