python - 在 Python 单元测试中模拟日志输出
问题描述
我正在尝试验证 Python 应用程序中的日志输出,该应用程序将日志消息写入具有不同级别的两个处理程序 - stdout 和一个文件。
使用 @mock.patch 来修补 sys.stdout 和 io.StringIO 我能够验证那里写入了适当的消息。我可以类似地定位日志模块使用的 TextIO 并将我自己的 StringIO 对象放在那里吗?
import io
import logging
import os
import sys
import unittest.mock
import logutils.colorize
class TestMain(unittest.TestCase):
def _setup_logger(self, stdout_level: str, file_level: str):
try:
os.unlink("test.log")
except FileNotFoundError:
pass
log_stdout_handler = logutils.colorize.ColorizingStreamHandler(sys.stdout)
log_stdout_handler.setFormatter(logging.Formatter("%(message)s"))
log_stdout_handler.setLevel(stdout_level)
log_file_handler = logging.FileHandler(filename="test.log", mode="a", encoding="utf8")
log_file_handler.setFormatter(logging.Formatter("%(message)s"))
log_file_handler.setLevel(file_level)
_root_logger = logging.getLogger()
_root_logger.addHandler(log_stdout_handler)
_root_logger.addHandler(log_file_handler)
_root_logger.setLevel(logging.DEBUG)
def _log_messages(self):
log = logging.getLogger("test")
log.debug("debug")
log.info("info")
log.warning("warning")
log.error("error")
log.critical("critical")
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
@unittest.mock.patch("?", new_callable=io.StringIO) # <--- can I target FileHandler's io.TextIOWrapper here?
def test_log_stdout_info_file_debug_then_messages_logged_appropriately(self, mock_file: io.StringIO, mock_stdout: io.StringIO):
self._setup_logger("WARNING", "DEBUG")
self._log_messages()
stdout_output = mock_stdout.getvalue()
stdout_lines = stdout_output.splitlines()
self.assertEqual(len(stdout_lines), 3) # irl i would probably regex match the messages
file_output = mock_file.getvalue()
file_lines = file_output.splitlines()
self.assertEqual(len(file_lines), 5)
步入logging\__init.py:1054
我可以看到FileHandler
的流字段是 的一个实例_io.TextIOWrapper
,但是做@unittest.mock.patch("_io.TextIOWrapper", new_callable=io.StringIO)
不起作用。也不使用io.TextIOWrapper
,logging.io.TextIOWrapper
或logging._io.TextIOWrapper
.
或者也许有更好的方法来完成我所需要的?
解决方案
我对此一无所获,所以我最终做的是模拟emit
日志处理程序的函数(以及 的_open
函数FileHandler
,因为我不希望创建任何日志文件),并对模拟进行验证。
def _assertEmitted(self, emit_mock: unittest.mock.MagicMock, messages: list[str]) -> typing.NoReturn:
self.assertEqual(emit_mock.call_count, len(messages))
for index, message in enumerate(messages):
self.assertEqual(emit_mock.call_args_list[index].args[0].msg, message)
@unittest.mock.patch.object(logutils.colorize.ColorizingStreamHandler, "emit")
@unittest.mock.patch.object(logging.FileHandler, "emit")
@unittest.mock.patch.object(logging.FileHandler, "_open")
def test_given_log_levels_when_logging_then_appropriate_messages_logged(self, _, file_emit, stdout_emit):
# setup logger
stdout_handler = logutils.colorize.ColorizingStreamHandler(sys.stdout)
stdout_handler.setFormatter(logging.Formatter("..."))
stdout_handler.setLevel("INFO")
file_handler = logging.FileHandler(filename="doesn't matter", mode="a", encoding="utf8")
file_handler.setFormatter(logging.Formatter("..."))
file_handler.setLevel("DEBUG")
root_logger = logging.getLogger()
root_logger.addHandler(log_stdout_handler)
root_logger.addHandler(log_file_handler)
root_logger.setLevel(logging.DEBUG)
# do log calls here
log = logging.getLogger("test")
log.info("info")
log.debug("debug")
self._assertEmitted(stdout_emit, ["info"])
self._assertEmitted(file_emit, ["info", "debug"])
推荐阅读
- android - 如何在 android 8.0 中使用自定义声音设置通知?
- ios - 如何获取在 RxSwift 中单击了哪个按钮
- javascript - Highcharts(JavaScript):如果满足条件,则更改列的颜色
- prestashop - prestashop后台登录问题
- python - Seaborn stripplot 删除情节
- mysql - 无论我输入什么,MySQL Shell 总是给出错误
- linux - 如何让 xunit 使用 xunit.gherkin.quick 在 Linux 上查找 dotnet 核心测试
- php - 如何在节点 js Web 应用程序中允许跨域?
- varnish - Varnish 和 WordPress,没有外部插件是否有可能实现真正的缓存?
- php - $_SERVER 变量 $_SERVER["HTTP_TE"] 是什么?