如何应用装饰器而不将其用作装饰器?

时间:2014-07-05 09:43:48

标签: python unit-testing mocking

我正在尝试测试一个装饰类方法:

class S3Store(object):
    @retry(exceptions=Exception, delay=1, tries=5, backoff=2)
    def delete(self, dest_id):
        return self._delete(dest_id=dest_id)

    def _delete(self, dest_id):
        bucket = self.conn.get_bucket(get_bucket_from_s3_uri(dest_id))
        key = Key(bucket, get_key_from_s3_uri(dest_id))
        key.delete()

我已经嘲笑并测试了_delete,现在我想测试重试逻辑。

我不能直接测试delete(),因为Key不会被嘲笑。所以我希望做的事情如下:

decorated_fn = retry.retry_decorator(storage_backend._delete, delay=0.00001)
storage_backend.delete = decorated_fn
storage_backend.delete(...) ...         # add assertions, etc.

这不起作用。我收到一个错误:

AttributeError: 'function' object has no attribute 'retry_decorator'

我认为问题在于retry decorator本身就是装饰的。

如何在我的delete()方法上测试重试逻辑,以便可以模拟其内部对象,因此延迟超时非常低?

2 个答案:

答案 0 :(得分:0)

您不应该在delete函数中测试重试装饰器,而是使用测试函数来测试重试装饰器。

def test_retry(self):
    @retry(exceptions=ValueError, delay=1, tries=5, backoff=2)
    def test_raise_wrong_exception():
        raise AssertionError()
    self.assertRaises(AssertionError, test_raise_wrong_exception)
    ...

答案 1 :(得分:0)

装饰器是一个函数,它将一个函数作为参数,并返回一个装饰版本。

背景

你的情况很混乱,因为它包含很多嵌套。让我们首先刷新装饰器的语法:

当我们写:

@decorator
def fun():
    pass

这相当于:

def fun():
    pass
fun = decorator(fun)

在您的示例中,retry函数实际上不是装饰器,而是创建装饰器:

@decorator_factory(...)
def fun():
    pass

相当于:

def fun():
    pass
decorator = decorator_factory(...)
fun = decorator(fun)

解决方案

现在显然你想要:

decorator = retry(delay=0.00001)
decorated_fn = decorator(storage_backend._delete)

其他

如果我们查看源代码,看起来retry_decorator实际上并不是装饰器:它返回f的结果,而不是具有增强行为的新函数:

@decorator
def retry_decorator(f, *args, **kwargs):
    for i in count():
        try:
        return f(*args, **kwargs)
        except exceptions, e:
        if i >= tries:
        raise
        round_delay = delay * backoff ** i
    log.warning('%s, retrying in %s seconds...', e, round_delay)
        time.sleep(round_delay)

但是@decorator会将retry_decorator转换为实际的装饰者see here