python用韧性重试,禁用`wait` for unittest

时间:2017-12-20 13:00:46

标签: python unit-testing

我正在使用tenacity库来使用其@retry装饰器。

我正在使用它来创建一个函数,以便在发生故障时多次“重复”HTTP请求。

这是一个简单的代码段:

@retry(stop=stop_after_attempt(7), wait=wait_random_exponential(multiplier=1, max=60))
def func():
   ...
   requests.post(...)

该函数使用韧性wait - 参数在两次调用之间等待一段时间。

该函数与@retry - 装饰器似乎工作正常。

但是我还有一个单元测试,它可以在发生故障时检查该函数是否被调用了7次。由于尝试之间存在wait,因此此测试需要花费大量时间。

是否有可能以某种方式仅在单元测试中禁用等待时间?

5 个答案:

答案 0 :(得分:2)

在这个Github问题中,解决方案来自于坚韧的维护者:https://github.com/jd/tenacity/issues/106

您可以暂时更改单元测试的等待功能:

from tenacity import wait_none

func.retry.wait = wait_none()

答案 1 :(得分:1)

您可以使用unittest.mock模块来模拟tentacity库的某些元素。 在您的情况下,您使用的所有装饰器都是例如retry是一个定义为here的装饰器类。所以它可能有点棘手,但我想尝试

mock.patch('tentacity.wait.wait_random_exponential.__call__', ...)

可能会有所帮助。

答案 2 :(得分:1)

感谢讨论here,我发现了一种基于@steveb中代码的优美方法:

from tenacity import retry, stop_after_attempt, wait_exponential


@retry(reraise=True, stop=stop_after_attempt(5), wait=wait_exponential(multiplier=1, min=4, max=10))
def do_something_flaky(succeed):
    print('Doing something flaky')
    if not succeed:
        print('Failed!')
        raise Exception('Failed!')

并进行测试:

from unittest import TestCase, mock, skip
from main import do_something_flaky


class TestFlakyRetry(TestCase):
    def test_succeeds_instantly(self):
        try:
            do_something_flaky(True)
        except Exception:
            self.fail('Flaky function should not have failed.')

    def test_raises_exception_immediately_with_direct_mocking(self):
        do_something_flaky.retry.sleep = mock.Mock()
        with self.assertRaises(Exception):
            do_something_flaky(False)

    def test_raises_exception_immediately_with_indirect_mocking(self):
        with mock.patch('main.do_something_flaky.retry.sleep'):
            with self.assertRaises(Exception):
                do_something_flaky(False)

    @skip('Takes way too long to run!')
    def test_raises_exception_after_full_retry_period(self):
        with self.assertRaises(Exception):
            do_something_flaky(False)

答案 3 :(得分:0)

模拟基类的等待功能,

mock.patch('tenacity.BaseRetrying.wait', side_effect=lambda *args, **kwargs: 0)

它总是不等待

答案 4 :(得分:0)

阅读the thread in tenacity repo(感谢@DanEEStar 启动它!)后,我想出了以下代码:

@retry(
    stop=stop_after_delay(20.0),
    wait=wait_incrementing(
        start=0,
        increment=0.25,
    ),
    retry=retry_if_exception_type(SomeExpectedException),
    reraise=True,
)
def func() -> None:
    raise SomeExpectedException()


def test_func_should_retry(monkeypatch: MonkeyPatch) -> None:
    # Use monkeypatch to patch retry behavior.
    # It will automatically revert patches when test finishes.
    # Also, it doesn't create nested blocks as `unittest.mock.patch` does.

    # Originally, it was `stop_after_delay` but the test could be
    # unreasonably slow this way. After all, I don't care so much
    # about which policy is applied exactly in this test.
    monkeypatch.setattr(
        func.retry, "stop", stop_after_attempt(3)
    )

    # Disable pauses between retries.
    monkeypatch.setattr(func.retry, "wait", wait_none())

    with pytest.raises(SomeExpectedException):
        func()

    # Ensure that there were retries.
    stats: Dict[str, Any] = func.retry.statistics
    assert "attempt_number" in stats
    assert stats["attempt_number"] == 3

我在此测试中使用了 pytest 特定的功能。也许,它可能对某人有用,至少对未来的我来说是一个例子。