简体   繁体   中英

Python unit test recursive higher order function using mocking

I have the following function which implements the exponential backoff algorithm:

class Retry():
def exponential_backoff_retry(self, function, *args, n=1):
    MAX_TRIES = 8
    try:
        f = function(*args)
    except:
        if n > MAX_TRIES:
            return None
        n += 1
        time.sleep((2 ** n) + (random.randint(0, 1000) / 1000.0))
        return self.exponential_backoff_retry(function, *args, n)
    else:
        return f

What I'd like to do is write a unit test that confirms the behaviour, given a passed in function for example a function making an api request - that in case of an exception it retries a number of times.

Here's what I've tried so far:

@mock.patch('requests.post')
@mock.patch('utils.retry.Retry.exponential_backoff_retry', side_effect=Exception('whoops'))
def test_exponential_backoff(self, mock_retry, req_post_mock):
    req_post_mock.return_value = {"status_code": 202}
    with self.assertRaises(Exception):
        mock_retry(req_post_mock)
    self.assertEqual(req_post_mock.return_value["status_code"], 202)
    self.assertEqual(mock_retry.call.count, 8)

Any advise would be appreciated.

Pre asumptions

First of all the code supplied seems to have a little bug(except the indication one). You should either change the order of function arguments like def exponential_backoff_retry(self, function,n=1, *args): instead of def exponential_backoff_retry(self, function, *args, n=1): or if you prefer to make na named argument in the recursive function invocation return self.exponential_backoff_retry(function, *args, n=n)


Main answer on how to test

You can create a mock class to hold the function invocation counter

class MockFunctionHelperClass:
    def __init__(self, failed_attempts_before_success: int) -> None:
        self.failed_attempts_before_success = failed_attempts_before_success
        self.attempts_counter = 0

    def mock_function(self, return_value):
        self.attempts_counter += 1
        if self.attempts_counter <= self.failed_attempts_before_success:
            raise Exception
        return return_value

you can utilize the helper class in test methods as follows:

def test_success_before_max_retries():
    failed_attempts_before_success = 2
    expected_result = True
    mock_function_helper_class = MockFunctionHelperClass(failed_attempts_before_success)
    result = Retry().exponential_backoff_retry(
    mock_function_helper_class.mock_function, 1, expected_result
)
    assert result == expected_result
    assert (
    mock_function_helper_class.attempts_counter
    == failed_attempts_before_success + 1
)

or

def test_failed_attempt_limit():
    expected_result = True
    mock_function_helper_class = MockFunctionHelperClass(3)
    result = Retry().exponential_backoff_retry(
    mock_function_helper_class.mock_function, 8, expected_result
    )
    assert result == None
    assert mock_function_helper_class.attempts_counter == 2

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM