Python mock能否自动实例化mock对象?
我正在为类Python mock能否自动实例化mock对象?,python,unit-testing,mocking,Python,Unit Testing,Mocking,我正在为类OnlineService编写测试,它实例化了一个类型为api.api的类,然后又实例化了一个类型为api.Resource的类。我在本例中测试的方法是initialize,它通过向远程API中的Ping资源发出GET请求来测试与远程服务的连接 我目前正在使用以下代码将这些对象修补为模拟对象,但在我看来仍然有点冗长 @patch('api.Resource') @patch('api.API') def test_initialize(self, api_mock, resource_
OnlineService
编写测试,它实例化了一个类型为api.api
的类,然后又实例化了一个类型为api.Resource
的类。我在本例中测试的方法是initialize
,它通过向远程API中的Ping
资源发出GET
请求来测试与远程服务的连接
我目前正在使用以下代码将这些对象修补为模拟对象,但在我看来仍然有点冗长
@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
我真的必须手动将资源模拟实例分配给另一个模拟实例的属性吗?也许mock
包中有一些功能可以帮我做到这一点
更新
我已经将测试一分为二,并附上了正在测试的在线服务
中的相关代码。以下是在线服务
课程:
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
当前,如果我运行它,测试将通过。注释掉我们正在讨论的这一行,我在测试中遇到了以下失败\u can\u connect\u to \u 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)
该行:
api_instance.Ping = resource_mock.return_value # is this step really necessary?
将新的空MagicMock
实例分配给api\u instance.Ping
。但是,在没有该赋值的情况下访问Ping
已经做到了这一点,因为api_实例
本身就是一个MagicMock
对象;线路完全冗余:
>>> from unittest.mock import MagicMock
>>> api_instance = MagicMock()
>>> api_instance.Ping
<MagicMock name='mock.Ping' id='4515362016'>
当然,如果被测试的代码没有使用api.api().Ping.get
来获取资源,那么上述代码将无法实现其目标;但是您也不需要更改api\u instance.Ping
这里要记住的是,您替换了api.api
;你不再关心那堂课的原作了。您所需要做的就是使用api.api
管理代码的期望值;如果它使用api.api()
,并在该对象上使用属性或方法,则模拟这些属性或方法。如果被测代码未直接使用api.Resource
,则也将其排除在测试之外
但是,您添加的代码表明您在模拟错误的对象。您正确模拟了api.Resource
,但剪切中的hapi()
对象不是模拟对象。请参阅unittest.mock
文档的。您的切割使用全局名称API
;它不引用api.api
。嘲笑全球:
或者,您可以只模拟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()]
CUT实例化api.api
(它隐式实例化了一组api.Resource
对象,其中Ping
就是其中之一),然后在资源
实例Ping
上调用get
。所以我不能把它排除在测试之外。更重要的是,由于Ping
是由api.api
的构造函数实例化的,因此该行对我来说似乎并不多余,因为MagicMock
不会为我实例化Ping
。有什么窍门我可以用吗?还是我应该让方法保持原样?@Korijn:但它是api.api()
现在是一个模拟。无论原作做了什么,这都不再发生,也不重要了。剪切中的代码在使用Ping.get
时是什么样子的?@Korijn:您在模拟错误的API
对象。您需要在_test.API
下模拟模块,而不是API.API
@Korijn:因为您没有模拟正确的API
对象,所以您的原始API类现在使用模拟的API.Resource
对象。线路仍然冗余;在这种情况下,api\u mock
的使用也是如此。@Korijn:对于单元测试,我会模拟api()
,因为这是切割和外部世界之间的边界。毕竟,这不是CUT和API对象之间的集成测试;这就是接受标记的用途。:-)但你使用的确切代码对未来的访问者来说并不重要。这里重要的是概念;我的回答告诉了你如何解决你的具体问题,因为我解释了机制是如何工作的。如果未来的访问者无法确定@patch('module_under_test.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()]
@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()]