How to remove the effects of a decorator while testing in python?
我在python的一些代码中使用retry装饰器。但是我希望通过消除它的效果来加速我的测试。
我的代码是:
1 2 3 4
| @retry(subprocess.CalledProcessError, tries=5, delay=1, backoff=2, logger=logger)
def _sftp_command_with_retries(command, pem_path, user_at_host):
# connect to sftp, blah blah blah
pass |
如何在测试时删除装饰器的效果?我无法创建未修饰的版本,因为我正在测试使用它的更高级别的函数。
由于retry使用time.sleep来退出,理想情况下我可以修补time.sleep,但由于这是在装饰器中我不认为这是可能的。
有什么办法可以加快测试使用这个功能的代码吗?
更新
我基本上试图测试使用它的高级函数,以确保它们捕获_sftp_command_with_retries抛出的任何异常。由于retry装饰器将传播它们,我需要一个更复杂的模拟。
所以从这里我可以看到如何模拟装饰器。但现在我需要知道如何编写一个本身就是装饰器的模拟器。它需要调用_sftp_command_with_retries并且如果它引发异常,则传播它,否则返回返回值。
导入我的函数后添加它不起作用:
1
| _sftp_command_with_retries = _sftp_command_with_retries.__wrapped__ |
-
为什么不完全修补retry?它可以很简单:lambda *args, **kwargs: lambda func: func,然后你可以直接测试装饰函数。
-
如何在应用之前对其进行修补?这就是装饰器的问题 - 当你导入包含装饰函数的文件时,它立即应用于函数AFAIK。因此,在导入之前,您实际上没有机会模拟它,就像使用其他函数一样。如果我错了,请随意写一个答案,说明如何嘲笑它。我不打算在这里测试retry的行为。
-
如果这是问题的一部分,你就会被困在那里,那些副本应该覆盖你。如果没有,请更新问题以澄清。
-
如果安装了retry装饰器,则使用decorator包。这意味着您可以在这种情况下使用_sftp_command_with_retries.__wrapped__来访问原始文件。
-
@jonrsharpe所以事实证明这不是一个确切的欺骗,因为我需要重试提供的行为,因为它将传递其中捕获的任何异常。这就是我正在测试的。但它确实有帮助。我现在需要编写一个会传播异常的模拟器......
-
@jbrown然后你可以请相应地编辑问题
-
@MartijnPieters我试过了,但没有任何区别。在我的文件顶部导入我的函数后,我添加了_sftp_command_with_retries = _sftp_command_with_retries.__wrapped__,但它们没有更快。我也安装了decorator包。
-
@jbrown:你真的在测试中调用了解包的版本,还是在某个地方使用了间接引用?
-
仅供记录......我已经在stackoverflow.com/a/30016312/4101725回答了这个问题
您正在使用的retry装饰器构建在decorator.decorator实用程序装饰器之上,如果未安装该包,则具有更简单的后备。
结果具有__wrapped__属性,使您可以访问原始函数:
1
| orig = _sftp_command_with_retries.__wrapped__ |
如果未安装decorator且您在3.2之前使用的是Python版本,则该属性将不存在;你必须手动进入装饰器闭包:
1
| orig = _sftp_command_with_retries.__closure__[1].cell_contents |
(索引0处的闭包是在调用retry()本身时产生的retry_decorator)。
请注意,decorator在retry包元数据中列为依赖项,如果使用pip安装decorator,则会自动安装decorator包。
您可以使用try...except支持这两种可能性:
1 2 3 4 5
| try:
orig = _sftp_command_with_retries.__wrapped__
except AttributeError:
# decorator.decorator not available and not Python 3.2 or newer.
orig = _sftp_command_with_retries.__closure__[1].cell_contents |
请注意,您始终可以使用模拟修补time.sleep()。装饰器代码将使用mock,因为它引用模块源代码中的"global"time模块。
或者,您可以使用以下方法修补retry.api.__retry_internal:
1 2 3 4 5 6
| import retry.api
def dontretry(f, *args, **kw):
return f()
with mock.patch.object(retry.api, '__retry_internal', dontretry):
# use your decorated method |
这暂时替换了直接调用原始函数的实际重试功能。
-
很好的答案。 谢谢。 修补retry.api.__retry_internal有效。
-
很好的答案......但这个问题是stackoverflow.com/questions/29996592/&hellip的明确副本;。 我们的答案有点不同(你的表现更好,但我不鼓励使用某种黑客并鼓励改变设计)。 我们该怎么办? 将其标记为重复或全部保留原样?
-
@ Micheled'Amico:确实,它是重复的。 我现在已经关闭了这个。 如果您看到像这样的副本,请随时投票以关闭它!:-)