在包装函数之前,我可以修补Python装饰器吗?

我有一个装饰器的function,我试图在Python 模拟库的帮助下进行testing。 我想使用mock.patch来replace真正的装饰器,只是调用函数的模拟“旁路”装饰器。 我无法弄清楚的是如何在真正的装饰器封装函数之前应用这个补丁。 我已经尝试了修补程序目标上的一些不同的变体,并重新sorting修补程序和导入语句,但没有成功。 有任何想法吗?

装饰器在function定义时间被应用。 对于大多数function,这是模块加载的时候。 (在其他函数中定义的函数每次调用封闭函数时都会应用装饰器)。

所以,如果你想猴子修饰装饰者,你需要做的是:

  1. 导入包含它的模块
  2. 定义模拟装饰器function
  3. 设置例如 module.decorator = mymockdecorator
  4. 导入使用装饰器的模块,或者在你自己的模块中使用它

如果包含装饰器的模块也包含使用它的函数,那么当你看到它们的时候就已经装饰了,而你可能是SOL

编辑以反映对Python的更改,因为我最初编写的内容是:如果装饰器使用functools.wraps()并且Python的版本足够新,则可以使用__wrapped__ attritube挖掘原始函数并重新装饰它,但这绝不是保证,你想要replace的装饰器也可能不是唯一的装饰器。

应该指出的是,这里的几个答案将修补整个testing会话的装饰器而不是单个testing实例; 这可能是不希望的。 以下是如何修补仅通过单个testing持续存在的装饰器。

我们的单位要与不受欢迎的装饰者进行testing:

 # app/uut.py from app.decorators import func_decor @func_decor def unit_to_be_tested(): # Do stuff pass 

从装饰模块:

 # app/decorators.py def func_decor(func): def inner(*args, **kwargs): print "Do stuff we don't want in our test" return func(*args, **kwargs) return inner 

在testing运行期间收集testing的时候,不需要的修饰器已经被应用到我们的testing中(因为这在import时发生)。 为了摆脱这一点,我们需要手动replace修饰器模块中的修饰器,然后重新导入包含我们UUT的模块。

我们的testing模块:

 # test_uut.py from unittest import TestCase from app import uut # Module with our thing to test from app import decorators # Module with the decorator we need to replace import imp # Library to help us reload our UUT module from mock import patch class TestUUT(TestCase): def setUp(self): # Do cleanup first so it is ready if an exception is raised def kill_patches(): # Create a cleanup callback that undoes our patches patch.stopall() # Stops all patches started with start() imp.reload(uut) # Reload our UUT module which restores the original decorator self.addCleanup(kill_patches) # We want to make sure this is run so we do this in addCleanup instead of tearDown # Now patch the decorator where the decorator is being imported from patch('app.decorators.func_decor', lambda x: x).start() # The lambda makes our decorator into a pass-thru. Also, don't forget to call start() # HINT: if you're patching a decor with params use something like: # lambda *x, **y: lambda f: f imp.reload(uut) # Reloads the uut.py module which applies our patched decorator 

清理callback函数kill_patches恢复原始的装饰器,并将其重新应用到我们正在testing的单元。 这样,我们的补丁只能通过一次testing而不是整个会话 – 这正是其他补丁应该如何运行的。 另外,由于清理调用了patch.stopall(),我们可以在我们需要的setUp()中启动任何其他的修补程序,并将它们清理到一个地方。

了解这种方法的重要之处在于重装将如何影响事物。 如果一个模块花费的时间太长或逻辑运行在导入上,那么你可能只需要耸耸肩,把这个装饰器作为单元的一部分来testing。 :(希望你的代码比这更好写,对吧?

如果用户不关心补丁是否应用于整个testing会话 ,那么最简单的方法就是在testing文件的顶部:

 # test_uut.py from mock import patch patch('app.decorators.func_decor', lambda x: x).start() # MUST BE BEFORE THE UUT GETS IMPORTED ANYWHERE! from app import uut 

确保使用装饰器修补文件而不是UUT的本地范围,并在装饰器导入装置之前启动修补程序。

有趣的是,即使补丁已经停止,所有已经导入的文件仍然会将补丁应用到装饰器,这与我们开始的情况相反。 请注意,这种方法会修补之后导入的testing运行中的任何其他文件 – 即使它们本身没有声明修补程序。

当我第一次遇到这个问题的时候,我用了几个小时的脑筋。 我发现了一个更简单的方法来处理这个问题。

这将完全绕过装饰者,就像目标甚至没有装饰在第一位。

这分为两部分。 我build议阅读下面的文章。

http://alexmarandon.com/articles/python_mock_gotchas/

我不断遇到的两个问题:

1.)在导入你的函数/模块之前,模拟装饰器。

装饰器和function是在模块加载的时候定义的。 如果你在import之前不嘲笑,那么就会不顾这个模仿。 加载后,你必须做一个奇怪的mock.patch.object,这更令人沮丧。

2.)确保你正在嘲笑修饰器的正确path。

请记住,您正在嘲笑的修饰器的修补程序是基于您的模块如何加载修饰器,而不是您的testing如何加载修饰器。 这就是为什么我build议总是使用完整pathimport。 这使得testing更容易。

脚步:

1.)模拟function:

 from functools import wraps def mock_decorator(*args, **kwargs): def decorator(f): @wraps(f) def decorated_function(*args, **kwargs): return f(*args, **kwargs) return decorated_function return decorator 

2.)嘲笑装饰者:

2a。)里面的path。

 with mock.patch('path.to.my.decorator`, mock_decorator): from mymodule import myfunction 

2b)在文件顶部或TestCase.setUp中进行修补

 mock.patch('path.to.my.decorator', mock_decorator).start() 

这两种方法都可以让你在TestCase或其方法/testing用例中的任何时候导入你的函数。

 from mymodule import myfunction 

2.)使用一个单独的函数作为mock.patch的副作用。

现在你可以使用mock_decorator来打开你想要模拟的每个装饰器。 你将不得不分别嘲笑每个装饰者,所以要小心那些你想念的东西。

也许你可以应用另一个装饰器到所有你的装饰器的定义,基本上检查一些configurationvariables,看是否要使用testing模式。
如果是的话,它会用一个虚拟的装饰器代替它装饰的装饰器,它不做任何事情。
否则,它让这个装饰器通过。

以下为我工作:

  1. 消除加载testing目标的导入语句。
  2. 在上面应用的testing启动中修补装饰器。
  3. debugging后立即调用importlib.import_module()以加载testing目标。
  4. 正常运行testing。

它像一个魅力工作。

for @lru_cache(max_size = 1000)


class MockedLruCache(object):

class MockedLruCache(object):

 def __init__(self, maxsize=0, timeout=0): pass def __call__(self, func): return func 

cache.LruCache = MockedLruCache

如果使用没有参数的装饰器,你应该:

def MockAuthenticated(func): return func 

def MockAuthenticated(func): return func

从龙卷风import网
web.authenticated = MockAuthenticated