You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.12 13:45:02