问题
I have a recursive function living in a module called 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. I was playing around with the mock module (I'm using python 2.7) and I think I want something a bit like this,
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...
回答1:
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. 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.
回答2:
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.
side_effect
accepts a function, which will be passed the same arguments as the Mock
. The the return value of the Mock
is also the return value of the side_effect
function. 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.
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),
])
来源:https://stackoverflow.com/questions/31993268/python-mocking-how-to-test-the-number-of-calls-on-a-recursive-function