Python模拟:如何测试递归函数的调用次数?

时间:2015-08-13 16:04:13

标签: python unit-testing recursion mocking

我有一个名为test_module

的模块中的递归函数
import requests    

def send_msg(msg, retries=0):
    try:
        # send the message here, e.g. a http request
        response = requests.get("http://www.doesnotexist98734.com")
        # if url does not exist raise an exception
    except Exception as e:
        if retries == 0:
            raise e
        else:
            return send_msg(msg, retries=retries-1)

我的问题是如何在设置retries = n时编写一个单元测试来检查send_msg函数被调用n次。我正在玩模拟模块(我使用python 2.7),我想我想要这样的东西,

import mock, unittest

class MyUnitTest(unittest.TestCase):

    @mock.patch('test_module.send_msg')
    def test_send_msg_tries_n_times(self, mock_send_msg):
        with self.assertRaises(Exception):
            mock_send_msg("hello", retries=3)
        self.assertEqual(mock_send_msg.call_count, 4) # initial call + 3 retries

然而,由于我嘲笑了它不会调用真实函数的函数,所以我没有得到异常,也没有递归调用自己......

2 个答案:

答案 0 :(得分:3)

您无法模拟测试中的功能。您希望测试预期结果,而不是函数正确使用递归。

模拟request.get()调用,并让它始终产生异常。然后计算你的模拟被调用的频率。

@mock.patch('requests.get')
def test_send_msg_tries_n_times(self, req_get_mock):
    req_get_mock.side_effect = Exception
    with self.assertRaises(Exception):
        send_msg("hello", retries=3)
    self.assertEqual(req_get_mock.call_count, 4)  # 1 initial call + 3 retries

如果您将来想要避免使用递归并希望使用迭代,那么您的测试仍然可以正常运行,因为它验证了行为,而不是特定的实现。您可以安全地重构被测功能。

答案 1 :(得分:1)

我发现验证函数的递归调用结构是单元测试的一个非常有用的功能。 这可以通过使用side_effect中的Mock参数轻松完成,而不是使用patch装饰器。

side_effect接受一个函数,该函数将传递与Mock相同的参数。 Mock的返回值也是side_effect函数的返回值。这意味着我们可以将原始函数作为Mock传递给side_effect,并在我们的递归函数周围有效地使用Mock包装。

例如:

def test_send_msg_tries_n_times(self, mock_send_msg):
    test_module.send_msg = Mock(side_effect=test_module.send_msg)

    test_module.send_msg("hello", retries=3)
    test.module.send_msg.assert_has_calls([
        call("hello", retries=3),
        call("hello", retries=2),
        call("hello", retries=1),
        call("hello", retries=0),
    ])