如何mock datetime.datetime.now()以快速实现超时场景单元测试
单元测试超时场景的正确mock方案
报错
TypeError: can't set attributes of built-in/extension type 'datetime.datetime'的原因是Python标准库的datetime为C语言实现的内置扩展类型,不允许直接修改类属性。
方法1:按补丁位置原则mock业务模块内的datetime
unittest.mock的patch需要作用在被测试代码实际导入使用的位置,而非标准库的原始datetime路径。
假设你的业务代码保存在app.py中,测试代码写法如下:
import datetime from unittest.mock import patch from app import test # 导入待测的test函数 def test_timeout_scenario(): # 构造时间序列:第一次返回起始时间,第二次直接返回超时时点 start = datetime.datetime.now() time_series = [ start, start + datetime.timedelta(minutes=6) # 超过配置的5分钟超时阈值 ] # 同时mock datetime和do_something_succeed,避免提前返回成功 with patch("app.datetime") as mock_datetime, \ patch("app.do_something_succeed", return_value=False): mock_datetime.datetime.now.side_effect = time_series mock_datetime.timedelta = datetime.timedelta # 保留timedelta原始逻辑 result = test() # 验证超时返回False,且now方法被调用2次 assert result is False assert mock_datetime.datetime.now.call_count == 2
方法2:封装时间获取方法后mock(推荐,可维护性更高)
在业务代码中抽离单独的时间获取函数,后续mock、改时区等需求都不需要改动核心逻辑,也不会触发内置类型的修改限制:
修改后的业务代码:
import datetime TIMEOUT = datetime.timedelta(minutes=5) # 抽离公共时间获取方法 def get_current_time(): return datetime.datetime.now() def test(): start_time = get_current_time() while get_current_time() - start_time < TIMEOUT: if do_something_succeed(): return True return False
对应的测试代码:
import datetime from unittest.mock import patch from app import test def test_timeout_scenario(): start = datetime.datetime.now() time_series = [start, start + datetime.timedelta(minutes=6)] with patch("app.get_current_time", side_effect=time_series), \ patch("app.do_something_succeed", return_value=False): result = test() assert result is False
方法3:使用第三方库freezegun简化时间mock
如果允许引入第三方依赖,freezegun是专门用于时间mock的工具,自动处理原生datetime的修改限制:
首先安装依赖:pip install freezegun
测试代码写法:
from datetime import datetime from freezegun import freeze_time from unittest.mock import patch from app import test def test_timeout_scenario(): start_time = datetime.now() # 先冻结在起始时间,再直接跳到6分钟后的超时时点 with freeze_time(start_time), \ patch("app.do_something_succeed", return_value=False): with freeze_time(start_time + datetime.timedelta(minutes=6)): result = test() assert result is False
内容的提问来源于stack exchange,提问作者LaoDa581
相关产品推荐
相关产品推荐

