我正在为类OnlineService
编写测试,它实例化一个类型为api.API
的类,后者又实例化一个类型为api.Resource
的类。我在此示例中测试的方法是initialize
,它通过向远程API中的GET
资源发出Ping
请求来测试与远程服务的连接。
我目前正在使用以下代码将这些对象修补为mocks,但它看起来仍然有些冗长。
@patch('api.Resource')
@patch('api.API')
def test_initialize(self, api_mock, resource_mock):
api_instance = api_mock.return_value
api_instance.Ping = resource_mock.return_value # is this step really necessary?
api_instance.Ping.get.side_effect = [None, HTTPError()]
service = OnlineService()
service.initialize()
assert service.connected is True
service.initialize()
assert service.connected is False
我是否真的必须手动将Resource
模拟实例分配给另一个模拟实例的属性?也许mock
包中有一些功能可以为我做这个?
更新
我将测试分成两部分,并附加了正在测试的OnlineService
的相关代码。这是OnlineService
类:
class OnlineService(object):
def __init__(self):
self.webservice_url = u''
self.verify_ssl = True
self.connected = False
def initialize(self, webservice_url, verify_ssl, connectivity_check_timeout):
self.webservice_url = webservice_url
self.verify_ssl = verify_ssl
self.connected = self.can_connect_to_api(connectivity_check_timeout)
def can_connect_to_api(self, connectivity_check_timeout):
api_instance = api.API(url=self.webservice_url, verify_ssl=self.verify_ssl, timeout=connectivity_check_timeout)
try:
# api_instance.Ping of type api.Resource was instantiated in api.API()
api_instance.Ping.get()
return True
except:
return False
这是测试代码:
def test_initialize(self):
service = OnlineService()
service.can_connect_to_api = MagicMock(return_value=True)
service.initialize(u'some_url', False, 3.42)
service.can_connect_to_api.assert_called_once_with(3.42)
assert service.webservice_url is u'some_url'
assert service.verify_ssl is False
assert service.connected is True
@patch('api.Resource')
@patch('api.API')
def test_can_connect_to_api(self, api_mock, resource_mock):
api_instance = api_mock.return_value
api_instance.Ping = resource_mock.return_value # is this step really necessary?
api_instance.Ping.get.side_effect = [None, HTTPError()]
service = OnlineService()
connected = service.can_connect_to_api(5.0)
assert connected is True
connected = service.can_connect_to_api(5.0)
assert connected is False
目前测试通过,如果我运行它。评论我们正在讨论的那条线路给了我test_can_connect_to_api
中的以下失败:
======================================================================
FAIL: Services.tests.test_OnlineService.TestOnlineService.test_can_connect_to_api
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\Development\Projects\app\venv\lib\site-packages\nose\case.py", line 197, in runTest
self.test(*self.arg)
File "C:\Development\Projects\app\venv\lib\site-packages\mock.py", line 1201, in patched
return func(*args, **keywargs)
File "C:\Development\Projects\app\src\Services\tests\test_OnlineService.py", line 47, in test_can_connect_to_api
assert connected is False
AssertionError
----------------------------------------------------------------------
Ran 1 test in 0.013s
FAILED (failures=1)
答案 0 :(得分:1)
该行:
api_instance.Ping = resource_mock.return_value # is this step really necessary?
将新的MagicMock
实例分配给api_instance.Ping
。但是,在没有分配已经的情况下访问Ping
会这样做,因为api_instance
本身就是MagicMock
个对象;这条线完全是多余的:
>>> from unittest.mock import MagicMock
>>> api_instance = MagicMock()
>>> api_instance.Ping
<MagicMock name='mock.Ping' id='4515362016'>
因此,以下就足够了:
@patch('api.API')
def test_initialize(self, api_mock):
api_instance = api_mock.return_value
api_instance.Ping.get.side_effect = [None, HTTPError()]
当然,如果被测代码不使用api.API().Ping.get
来获取资源,那么上面的代码将无法实现其目标;但是你不需要改变api_instance.Ping
。
这里要记住的是你替换了api.API
;那个班级原作所做的不再是你所关心的。您需要做的就是使用 api.API
来管理代码的期望;如果它使用api.API()
并在该对象上使用属性或方法,那就嘲笑它们。如果被测代码未直接使用api.Resource
,请将其从测试中删除。
您添加的代码显示您模拟了错误的对象。您正确地模拟了api.Resource
,但CUT中的API()
对象不是模拟。请参阅unittest.mock
文档的Where to patch section。您的CUT使用全局名称API
;它确实不引用api.API
。模拟全局:
@patch('module_under_test.API')
def test_initialize(self, api_mock):
api_instance = api_mock.return_value
api_instance.Ping.get.side_effect = [None, HTTPError()]
或者你可以嘲笑只是 Ping
资源;显然,这就是你的未被嘲弄的API()
课使用的,毕竟:
@patch('api.Resource')
def test_can_connect_to_api(self, resource_mock):
# API().Ping is an instance of api.Resource; mocking that also works
resource_mock.return_value.get.side_effect = [None, HTTPError()]