如何用Mock/Patches替代文件操作测试Python的create_logger函数
问题描述
我正尝试为自定义的create_logger函数编写单元测试,原函数代码如下:
def create_logger(path, file_name): logger = logging.getLogger() logger.setLevel(logging.INFO) formatter = logging.Formatter( fmt="%(asctime)s %(levelname)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S", ) fileHandler = logging.FileHandler( f"{path}/{file_name}.log", "w" ) streamHandler = logging.StreamHandler() fileHandler.setFormatter(formatter) streamHandler.setFormatter(formatter) logger.addHandler(fileHandler) logger.addHandler(streamHandler) return logger
我已编写的单元测试会实际创建目录和日志文件,代码如下:
import os import shutil import unittest from logger import create_logger os.makedirs("unit_test", exist_ok=True) class TestCreateLogger(unittest.TestCase): @classmethod def setUpClass(cls): cls.logger = create_logger("unit_test", "logs") @classmethod def tearDownClass(cls): if os.path.isdir("unit_test"): shutil.rmtree("unit_test") else: raise Exception("The directory does not exist") del cls.logger def test_log_level(self): self.logger.info("First message") self.logger.debug("Second message") with open("unit_test/logs.log", "r") as fd: log_msg = fd.read() self.assertIn("First message", log_msg) self.assertNotIn("Second message", log_msg) def test_log_level_change(self): self.logger.setLevel("DEBUG") self.logger.info("First message") self.logger.debug("Second message") with open("unit_test/logs.log", "r") as fd: log_msg = fd.read() self.assertIn("First message", log_msg) self.assertIn("Second message", log_msg)
现在想去掉实际的文件操作,改用Mock/Patches实现相同测试逻辑,该怎么做?
解决方案
可以通过unittest.mock模块的patch来模拟logging.FileHandler和相关文件操作,同时捕获日志输出,不需要实际创建文件。具体实现如下:
改造后的测试代码
import unittest from unittest.mock import patch, MagicMock import logging from logger import create_logger class TestCreateLogger(unittest.TestCase): def setUp(self): # 每次测试前重置全局logger的handler,避免不同用例互相干扰 self.root_logger = logging.getLogger() self.original_handlers = self.root_logger.handlers.copy() self.root_logger.handlers.clear() def tearDown(self): # 测试结束后恢复全局logger的原始handler self.root_logger.handlers = self.original_handlers @patch('logging.FileHandler') def test_log_level(self, mock_file_handler): # 创建FileHandler的mock实例,捕获emit方法的调用 mock_handler_instance = MagicMock() mock_file_handler.return_value = mock_handler_instance # 调用目标函数创建logger logger = create_logger("unit_test", "logs") # 验证logger初始级别为INFO self.assertEqual(logger.level, logging.INFO) # 发送测试日志 logger.info("First message") logger.debug("Second message") # 验证只有INFO消息被处理 self.assertEqual(mock_handler_instance.emit.call_count, 1) log_record = mock_handler_instance.emit.call_args[0][0] self.assertIn("First message", log_record.getMessage()) # 验证DEBUG消息未被处理 debug_calls = [call for call in mock_handler_instance.emit.call_args_list if "Second message" in call[0][0].getMessage()] self.assertEqual(len(debug_calls), 0) @patch('logging.FileHandler') def test_log_level_change(self, mock_file_handler): mock_handler_instance = MagicMock() mock_file_handler.return_value = mock_handler_instance logger = create_logger("unit_test", "logs") # 修改日志级别为DEBUG logger.setLevel(logging.DEBUG) # 发送测试日志 logger.info("First message") logger.debug("Second message") # 验证两条消息都被处理 self.assertEqual(mock_handler_instance.emit.call_count, 2) log_messages = [call[0][0].getMessage() for call in mock_handler_instance.emit.call_args_list] self.assertIn("First message", log_messages) self.assertIn("Second message", log_messages)
关键说明
- Mock文件操作:通过
@patch('logging.FileHandler')替换标准库中的文件处理器,避免实际创建文件和目录 - 捕获日志内容:模拟
FileHandler的emit方法,获取日志记录对象后,调用getMessage()即可得到实际日志文本 - 隔离测试环境:每次测试前后重置并恢复全局logger的handler,防止不同测试用例的日志配置互相影响
- 完全无文件操作:整个测试过程不会生成任何实际文件,所有文件相关逻辑都被Mock替代
内容的提问来源于stack exchange,提问作者anaktha
相关产品推荐
相关产品推荐

