Python 函数的补丁调用
我需要在测试中修补当前日期时间。我正在使用此解决方案:Python 函数的补丁调用,python,time,mocking,python-mock,Python,Time,Mocking,Python Mock,我需要在测试中修补当前日期时间。我正在使用此解决方案: 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() 然后在我的测
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=***):
...
但是今天我想到了一个主意,我可以通过修补函数utcnow
的\uu调用
而不是增加一个\uutcnow
来简化实现
这对我不起作用:
from ***.utils import utcnow
with mock.patch.object(utcnow, '__call__', return_value=***):
...
如何优雅地做到这一点?正如对这个问题的评论,由于datetime.datetime是用C编写的,Mock不能替换类上的属性(请参见Ned Batchelder)。相反,您可以使用 下面是一个例子:
import datetime
from freezegun import freeze_time
def my_now():
return datetime.datetime.utcnow()
@freeze_time('2000-01-01 12:00:01')
def test_freezegun():
assert my_now() == datetime.datetime(2000, 1, 1, 12, 00, 1)
正如您所提到的,另一种方法是跟踪导入
datetime
的每个模块并对其进行修补。这就是冰枪的本质。它使用对象模拟datetime
,遍历sys.modules
,查找导入datetime
的位置,并替换每个实例。我想你是否能在一个函数中优雅地完成这一点是有争议的。[EDIT]
也许这个问题最有趣的部分是为什么我不能修补某个函数
因为函数不使用\uuuuu调用\uuuu
的代码,而是使用函数的代码
我没有找到任何关于这方面的源代码良好的文档,但我可以证明这一点(Python2.7):
当然,f
和f.\uuu调用\uuuu
引用不会更改:
>>> f
<function f at 0x7f1576381848>
>>> f.__call__
<method-wrapper '__call__' of function object at 0x7f1576381848>
这对f
功能没有任何影响注意:在Python3中,您应该使用\uuuu code\uuu
而不是func\u code
我希望有人能给我指出解释这种行为的文档
您有一种解决方法:在utils
中,您可以定义
class Utcnow(object):
def __call__(self):
return datetime.datetime.utcnow()
utcnow = Utcnow()
现在你的补丁可以像一个符咒一样工作了
<> P>遵循原始的答案,我认为这是实现测试的最佳方式。
我有自己的黄金法则:从不修补受保护的方法。在这种情况下,情况会更顺利一些,因为保护方法只是为了测试而引入的,但我不明白为什么
这里真正的问题是,您不能直接修补datetime.datetime.utcnow
(正如您在上面的评论中所写的是C扩展)。您可以通过包装标准行为并覆盖utcnow
函数来修补datetime
:
>>> 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(***))
不需要任何其他图层,也不需要任何类型的导入,就可以完全满足您的需要
另一种解决方案是在utils
中导入datetime
,并修补***.utils.datetime
;这可以让您在不更改测试的情况下自由地更改datetime
参考实现(在这种情况下,请注意更改mock\u utcnow()
包装
参数)。当您修补函数的\u调用
时,您正在设置该实例的\uuuu调用
属性。Python实际上调用在类上定义的\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
方法
例如:
>>> 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.\u调用\u
都是毫无意义的
然而:
>>> A.__call__ = b.__call__
>>> a()
b
太长,读不下去了
a()。它调用类型(a)。\uuuu调用
链接
有一个很好的解释为什么会发生这种情况
此行为记录在的Python文档中。mock.patch('***.utils.utcnow',return\u value=***'):do\u something()
?@Rogalski它不适用于用户代码从***.utils导入utcnow
引用原始实现的情况。在这种情况下,您应该使用mock.patch('module.which.imported.utcnow')
。这就是你想要的吗?你的问题不是修补\uuuu call\uuuu
,而是关于Python名称空间、导入和名称解析机制。是的,我可以这样做,但是我应该跟踪每个这样的模块,并可能在一个测试中修补多个模块。我宁愿只修补一个地方。那么直接修补datetime.datetime.utcnow
怎么样?你为什么不这么做?我讨厌修补内部或受保护的方法。是的,我知道这个工具。但我不想使用它——对我来说,使用中间函数更明确、更简单。如果你添加信息说明为什么修补\uuuu call\uuuu
是不可能的,我会接受你的答案。我的意思是为什么我不能用mock
修补\uuuuu call\uuuuuuu
——我想这是因为FunctionType也有C实现。@warvariuc:小心,@J.F.Sebastian,谢谢你提供的信息。达格,我接受了另一个答案,因为它为我的问题提供了答案。非常感谢。这应该是公认的答案,它准确地解释了发生了什么。你知道这是每个魔术方法的标准行为还是仅仅是调用的行为吗?@Micheled'Amico看起来他们都是这样的(至少我试过了),但实际上我没有看到任何文档说明应该是这样的。@Micheled'Amico查看python源代码,我想说,中定义的类型槽实际上是始终在类型上而不是在实例上调用的事物列表。这是一个近似值,因为我懒得更深入地分析;)无论如何,对于调用一个对象,Pyhon实际上做了call=func->ob\u type->tp\u call
中的另一个问题,有更好的解释:
def mock_utcnow(return_value):
return mock.Mock(wraps=datetime.datetime,
utcnow=mock.Mock(return_value=return_value)):
mock.patch("datetime.datetime", mock_utcnow(***))
>>> 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__ = b.__call__
>>> a()
b