#python #unit-testing #mocking
#python #модульное тестирование #издевательство
Вопрос:
Я пытаюсь протестировать оформленный метод класса:
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
декоратор сам оформлен.
Как я могу протестировать логику повторных попыток в моем delete()
методе, чтобы его внутренние объекты можно было имитировать, и чтобы время ожидания задержки было очень низким?
Ответ №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)
Но @decorator
преобразует retry_decorator
в реальный декоратор, смотрите здесь .