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
,但剪切中的h
api()
对象不是模拟对象。请参阅
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()]