简体   繁体   English

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

[英]Python mocking: How to test the number of calls on a recursive function?

I have a recursive function living in a module called test_module 我在名为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)

My question is how can I write a unittest that checks the send_msg function is called n times when I set retries = n. 我的问题是,当我设置重试次数= n时,我该如何编写一个检查send_msg函数是否被调用n次的单元测试。 I was playing around with the mock module (I'm using python 2.7) and I think I want something a bit like this, 我在玩模拟模块(我正在使用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

However since I have mocked the function it won't call the real function so I don't get an exception nor does it call itself recursively... 但是由于我已经嘲笑了这个函数,所以它不会调用真正的函数,所以我不会得到异常,也不会递归地调用自身...

You can't mock the function under test. 您不能嘲笑被测函数。 You want to test for expected results , not if the function used recursion correctly. 您想测试预期的结果 ,而不是如果函数正确使用了递归,则需要测试。

Mock the request.get() call, and have it always produce an exception. 模拟request.get()调用,并使其始终产生异常。 Then count how often your mock was called. 然后计算一下您的模拟被调用的频率。

@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

If in future you want to avoid using recursion and want to use iteration instead, your test still will work as it validates the behaviour, not the specific implementation. 如果将来您想避免使用递归而要使用迭代,那么您的测试仍将在验证行为而不是特定实现时仍然有效。 You can safely refactor the function-under-test. 您可以安全地重构被测功能。

I find verifying the recursive call structure of a function to be an incredibly useful feature of a unit test. 我发现验证函数的递归调用结构是单元测试中一个非常有用的功能。 This can be accomplished easily by using the side_effect argument in Mock , instead of using the patch decorator. 通过使用Mockside_effect参数而不是使用patch装饰器,可以轻松实现此side_effect

side_effect accepts a function, which will be passed the same arguments as the Mock . side_effect接受一个函数,该函数将传递与Mock相同的参数。 The the return value of the Mock is also the return value of the side_effect function. Mock的返回值也是side_effect函数的返回值。 What this means is that we can pass the original function to the Mock as a side_effect , and effectively have a Mock wrapper around our recursive function. 这意味着我们可以将原始函数作为side_effect传递给Mock ,并在我们的递归函数周围有效地使用Mock包装器。

For example: 例如:

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),
    ])

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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