关于unittest.mock.patch与导入结构的关系、生效差异问题及替代方案咨询
嘿,我来帮你拆解这个问题——你遇到的其实是unittest.mock.patch最容易踩的两个坑:补丁路径的匹配逻辑和装饰器与参数的顺序对应关系,咱们一步步理清楚:
先搞懂为什么有的补丁生效,有的不生效
你的测试里,boto3.client和ASOUsecaseTools.get_by_name能被mock成MagicMock,但date和asodates2datetime不行,核心原因分两点:
1. 补丁路径是对的,但你搞反了装饰器和参数的顺序!
unittest.mock.patch装饰器的参数顺序是倒过来的:最上面的装饰器对应函数的最后一个参数,最下面的装饰器对应第一个参数。看你的代码:
@mock.patch("src.aoi.usecases.date") # 第1个装饰器 → 对应最后一个参数 @mock.patch("src.aoi.usecases.ASOUsecaseTools.get_by_name") # 第2个 → 第三个参数 @mock.patch("src.aoi.usecases.asodates2datetime") # 第3个 → 第二个参数 @mock.patch("src.aoi.usecases.boto3.client") # 第4个 → 第一个参数 def test_foo_gets_bar(self, m_d, m_g, m_a, m_c):
你把m_d当成了date的mock,但实际上m_d对应的是最下面的boto3.client补丁!真正的date mock是最后一个参数m_c,你之前给m_d.today赋值,完全是给错了对象,自然看不到date被mock的效果。
2. 导入方式决定了补丁必须打在「被测试代码的命名空间」里
你的被测试代码里用了两种导入方式:
import boto3:这种方式下,代码里调用的是boto3.client,本质是访问src.aoi.usecases模块下的boto3对象的client属性,所以你patchsrc.aoi.usecases.boto3.client是对的。from datetime import date/from common.functions import asodates2datetime:这种方式下,date和asodates2datetime直接成为了src.aoi.usecases模块的属性,所以你必须patchsrc.aoi.usecases.date和src.aoi.usecases.asodates2datetime——这个路径你是对的,但因为参数顺序错了,你根本没给这些mock对象做正确配置。
怎么解决?
第一步:修正装饰器与参数的对应顺序
把测试函数改成这样,参数顺序和装饰器顺序反过来:
@mock.patch("src.aoi.usecases.date") @mock.patch("src.aoi.usecases.ASOUsecaseTools.get_by_name") @mock.patch("src.aoi.usecases.asodates2datetime") @mock.patch("src.aoi.usecases.boto3.client") def test_foo_gets_bar(self, mock_boto3_client, mock_asodates, mock_get_by_name, mock_date): # 现在mock_date才是date对象的MagicMock,给它设置正确的返回值 mock_date.today.return_value = <你的测试日期对象,比如date(2024, 1, 1)> # 如果需要让date()构造函数正常工作,设置side_effect为真实的date类 mock_date.side_effect = date # 给其他mock做配置 mock_asodates.return_value = <你的测试datetime对象> # 因为get_by_name是装饰器,需要返回原函数(或者合适的mock) mock_get_by_name.return_value = lambda func: func mock_boto3_client.return_value = <你的S3客户端mock> # 执行测试逻辑...
第二步:验证补丁是否生效
在调试时,直接打印src.aoi.usecases.date和src.aoi.usecases.asodates2datetime,如果它们是MagicMock,说明补丁生效了;如果还是原对象,再检查路径是否拼写正确。
关于date的替代方案(不用第三方包)
如果不想用freeze包,只要按照上面的方法正确patchsrc.aoi.usecases.date,并设置好today()的返回值和构造函数的side_effect,就能完全用内置的unittest.mock解决问题。
最后再强调一个关键点
记住:补丁要打在被测试代码实际使用对象的地方,而不是对象的源头。比如你从datetime导入date到自己的模块,那补丁必须打在你的模块里的date,而不是datetime.date——因为你的代码已经把date复制到自己的命名空间了,修改源头不会影响已经复制的引用。
备注:内容来源于stack exchange,提问作者R T

