我需要在测试中修补当前的日期时间.我正在使用这个解决方案:
def _utcnow(): return datetime.datetime.utcnow() def utcnow(): """A proxy which can be patched in tests. """ # another level of indirection, because some modules import utcnow return _utcnow()
然后在我的测试中我做了类似的事情:
with mock.patch('***.utils._utcnow', return_value=***): ...
但今天我想到了一个想法,我可以通过修补__call__
功能utcnow
而不是额外的功能来简化实现_utcnow
.
这对我不起作用:
from ***.utils import utcnow with mock.patch.object(utcnow, '__call__', return_value=***): ...
如何优雅地做到这一点?
[编辑]
也许这个问题最有趣的部分是为什么我无法修补somefunction.__call__
?
因为函数不使用__call__
代码而是__call__
(方法包装器对象)使用函数的代码.
我没有找到任何有关这方面的资料来源,但我可以证明它(Python2.7):
>>> def f(): ... return "f" ... >>> def g(): ... return "g" ... >>> f>>> f.__call__ >>> g >>> g.__call__
用f
代码替换g
代码:
>>> f.func_code = g.func_code >>> f() 'g' >>> f.__call__() 'g'
当然f
,f.__call__
参考文献没有改变:
>>> f>>> f.__call__
__call__
改为恢复原始实现和复制引用:
>>> def f(): ... return "f" ... >>> f() 'f' >>> f.__call__ = g.__call__ >>> f() 'f' >>> f.__call__() 'g'
这对f
功能没有任何影响.注意:在Python 3中,您应该使用__code__
而不是func_code
.
我希望有人能指出我解释这种行为的文档.
你有办法解决这个问题:utils
你可以定义
class Utcnow(object): def __call__(self): return datetime.datetime.utcnow() utcnow = Utcnow()
现在你的补丁可以像魅力一样工作.
按照我认为是实现测试的最佳方式的原始答案.
我有自己的黄金法则:从不修补受保护的方法.在这种情况下,事情有点平滑,因为保护方法只是为了测试而引入,但我不明白为什么.
这里真正的问题是你不能datetime.datetime.utcnow
直接修补(正如你在上面的评论中写的那样是C扩展).你可以做的是datetime
通过包装标准行为和覆盖utcnow
功能来修补:
>>> with mock.patch("datetime.datetime", mock.Mock(wraps=datetime.datetime, utcnow=mock.Mock(return_value=3))): ... print(datetime.datetime.utcnow()) ... 3
好吧,这不是很清楚,但你可以介绍自己的功能,如
def mock_utcnow(return_value): return mock.Mock(wraps=datetime.datetime, utcnow=mock.Mock(return_value=return_value)):
现在
mock.patch("datetime.datetime", mock_utcnow(***))
在没有任何其他图层的情况下完成所需的操作,并进行各种导入.
另一种解决方案可以是进口datetime
中utils
和补丁***.utils.datetime
; 这可以让你有一些自由来改变datetime
参考实现,而无需改变你的测试(在这种情况下也要注意改变mock_utcnow()
wraps
参数).
修补__call__
函数时,您正在设置该实例的__call__
属性.Python实际上调用了类上定义的方法.__call__
例如:
>>> class A(object): ... def __call__(self): ... print 'a' ... >>> a = A() >>> a() a >>> def b(): print 'b' ... >>> b() b >>> a.__call__ = b >>> a() a >>> a.__call__ = b.__call__ >>> a() a
分配任何东西a.__call__
都是毫无意义的.
然而:
>>> A.__call__ = b.__call__ >>> a() b
a()
不打电话a.__call__
.它叫type(a).__call__(a)
.
有一个很好的解释为什么会出现这种情况来回答"为什么type(x).__enter__(x)
而不是x.__enter__()
Python标准contextlib?" .
有关特殊方法查找的 Python文档中记录了此行为.