【问题标题】:How to apply a decorator without using it as a decorator?如何在不将其用作装饰器的情况下应用装饰器?
【发布时间】:2014-07-05 09:43:48
【问题描述】:

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

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() 方法上测试重试逻辑,以便可以模拟其内部对象,从而使延迟超时非常低?

【问题讨论】:

    标签: python unit-testing mocking


    【解决方案1】:

    您不应在删除函数中测试重试装饰器,而应使用测试函数来测试重试装饰器。

    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)
        ...
    

    【讨论】:

      【解决方案2】:

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

      背景

      您的案例令人困惑,因为它包含很多嵌套。我们先刷新一下装饰器的语法:

      我们写的时候:

      @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)
      

      但是@decoratorretry_decorator 转换为实际的装饰器see here

      【讨论】:

        猜你喜欢
        • 2015-02-01
        • 2012-03-20
        • 2018-06-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-03-25
        • 1970-01-01
        • 2020-02-10
        相关资源
        最近更新 更多