函数的补丁 __call__
Patch __call__ of a function
我需要在测试中修补当前日期时间。我正在使用这个解决方案:
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
的 __call__
而不是额外的 _utcnow
.
来简化实现
这对我不起作用:
from ***.utils import utcnow
with mock.patch.object(utcnow, '__call__', return_value=***):
...
如何优雅地做到这一点?
正如对问题的评论,由于 datetime.datetime 是用 C 语言编写的,Mock 无法替换 class 上的属性(参见 Mocking datetime.today by Ned Batchelder). Instead you can use freezegun.
$ pip install freezegun
这是一个例子:
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
并修补它们。这本质上就是 freezegun 所做的。它需要一个对象 mocking datetime
,遍历 sys.modules
以找到 datetime
被导入的位置并替换每个实例。我想你是否可以在一个函数中优雅地做到这一点是有争议的。
[编辑]
也许这个问题最有趣的部分是为什么我不能修补 somefunction.__call__
?
因为函数不使用 __call__
的代码,但是 __call__
(方法包装对象)使用函数的代码。
我没有找到任何关于此的来源良好的文档,但我可以证明这一点 (Python2.7):
>>> def f():
... return "f"
...
>>> def g():
... return "g"
...
>>> f
<function f at 0x7f1576381848>
>>> f.__call__
<method-wrapper '__call__' of function object at 0x7f1576381848>
>>> g
<function g at 0x7f15763817d0>
>>> g.__call__
<method-wrapper '__call__' of function object at 0x7f15763817d0>
用g
的代码替换f
的代码:
>>> f.func_code = g.func_code
>>> f()
'g'
>>> f.__call__()
'g'
当然 f
和 f.__call__
引用没有改变:
>>> f
<function f at 0x7f1576381848>
>>> f.__call__
<method-wrapper '__call__' of function object at 0x7f1576381848>
恢复原始实现并复制 __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 扩展名)。您可以做的是通过包装标准行为并覆盖 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_utcnow()
wraps
参数)。
当您修补函数的 __call__
时,您正在设置该 实例 的 __call__
属性。 Python 实际上调用了 class.
上定义的 __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
TLDR;
a()
不调用 a.__call__
。它调用 type(a).__call__(a)
.
链接
.
中可以很好地解释为什么会发生这种情况
此行为记录在 Special method lookup 的 Python 文档中。
我需要在测试中修补当前日期时间。我正在使用这个解决方案:
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
的 __call__
而不是额外的 _utcnow
.
这对我不起作用:
from ***.utils import utcnow
with mock.patch.object(utcnow, '__call__', return_value=***):
...
如何优雅地做到这一点?
正如对问题的评论,由于 datetime.datetime 是用 C 语言编写的,Mock 无法替换 class 上的属性(参见 Mocking datetime.today by Ned Batchelder). Instead you can use freezegun.
$ pip install freezegun
这是一个例子:
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
并修补它们。这本质上就是 freezegun 所做的。它需要一个对象 mocking datetime
,遍历 sys.modules
以找到 datetime
被导入的位置并替换每个实例。我想你是否可以在一个函数中优雅地做到这一点是有争议的。
[编辑]
也许这个问题最有趣的部分是为什么我不能修补 somefunction.__call__
?
因为函数不使用 __call__
的代码,但是 __call__
(方法包装对象)使用函数的代码。
我没有找到任何关于此的来源良好的文档,但我可以证明这一点 (Python2.7):
>>> def f():
... return "f"
...
>>> def g():
... return "g"
...
>>> f
<function f at 0x7f1576381848>
>>> f.__call__
<method-wrapper '__call__' of function object at 0x7f1576381848>
>>> g
<function g at 0x7f15763817d0>
>>> g.__call__
<method-wrapper '__call__' of function object at 0x7f15763817d0>
用g
的代码替换f
的代码:
>>> f.func_code = g.func_code
>>> f()
'g'
>>> f.__call__()
'g'
当然 f
和 f.__call__
引用没有改变:
>>> f
<function f at 0x7f1576381848>
>>> f.__call__
<method-wrapper '__call__' of function object at 0x7f1576381848>
恢复原始实现并复制 __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 扩展名)。您可以做的是通过包装标准行为并覆盖 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_utcnow()
wraps
参数)。
当您修补函数的 __call__
时,您正在设置该 实例 的 __call__
属性。 Python 实际上调用了 class.
__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
TLDR;
a()
不调用 a.__call__
。它调用 type(a).__call__(a)
.
链接
此行为记录在 Special method lookup 的 Python 文档中。