如何模拟 returns 一个 class 实例的属性变量
How to mock an attribute variable that returns a class instance
所以我有这个 class 位于 folder/layer/base.py 里面有这样的东西:
from folder.plugin import load_plugin
class BaseLayer:
def __init__(self):
self.tileindex = load_plugin()
我需要向 class 中已有的函数添加单元测试。我的问题是,函数 load_plugin()
returns 是 class 的一个实例,位于 folder/tileindex/base.py 中。因此,它发生了多次,并且在多个不同的函数中,一行看起来像这样:
def somefunction(self):
key = self.tileindex.get_key(...)
r = self.tileindex.bulk_add(...)
self.tileindex.add(...)
而且我不知道如何嘲笑它。起初我在嘲笑 load_plugin
并返回任何值,以便我以后可以断言它。但是现在我已经看到这些函数使用 self.tileindex
作为另一个 class 的实例,我不知道该怎么做。例如:
def register(self):
"""
Registers a file into the system
:returns: `bool` of status result
"""
items = [item for item in self.items if item['register_status']]
if len(items) > 1:
item_bulk = []
for item in items:
item_bulk.append(self.layer2dict(item))
LOGGER.debug('Adding to tileindex (bulk)')
r = self.tileindex.bulk_add(item_bulk)
status = r[items[0]['identifier']]
当我模拟 load_plugin
时,代码在最后一行失败说 TypeError: 'Mock' object is not subscriptable
。
我尝试导入实例化的 class 并直接模拟它。但是由于某种原因,我一输入 @patch('folder.tileindex.base')
.
就收到错误 AttributeError: <Group tileindex> does not have the attribute 'base'
有什么方法可以模拟 self.tileindex
本身,以便测试其余代码?
谢谢!
确保不使用 unittest.mock.Mock
,而是使用 unittest.mock.MagicMock
,原因如下 . You can follow this documentation about Mocking Classes(所有这些都将使用 MagicMock
)。
对于您的情况,这里有 3 个选项来模拟 load_plugin()
返回的对象。您可以选择最适合您的需求。
mock_plugin_return_values
- 通过 return_value
进行模拟
mock_plugin_side_effect
- 通过 side_effect
进行模拟
mock_plugin_stub
- 通过打桩 class 来模拟
文件树
.
├── folder
│ ├── layer
│ │ └── base.py
│ ├── plugin.py
│ └── tileindex
│ └── base.py
└── tests
└── test_layer.py
folder/layer/base.py
from folder.plugin import load_plugin
class BaseLayer:
def __init__(self):
self.tileindex = load_plugin()
def somefunction(self):
a = self.tileindex.add("a")
print("add:", a)
key = self.tileindex.get_key("a")
print("get_key:", key)
r = self.tileindex.bulk_add([1, 2, 3])
print("bulk_add:", r)
status = r['identifier']
print("status:", status)
return a, key, r, status
folder/plugin.py
from folder.tileindex.base import SomePlugin
def load_plugin():
return SomePlugin()
folder/tileindex/base.py
class SomePlugin():
pass
test/test_layer.py
import pytest
from folder.layer.base import BaseLayer
# Note, this requires <pip install pytest-mock>
@pytest.fixture
def mock_plugin_return_values(mocker):
mock_cls = mocker.patch("folder.plugin.SomePlugin")
mock_obj = mock_cls.return_value
mock_obj.add.return_value = "Anything!"
mock_obj.get_key.return_value = "Something!"
mock_obj.bulk_add.return_value = {"identifier": "Nothing!"}
@pytest.fixture
def mock_plugin_side_effect(mocker):
mock_cls = mocker.patch("folder.plugin.SomePlugin")
mock_obj = mock_cls.return_value
mock_obj.add.side_effect = lambda arg: f"Adding {arg} here"
mock_obj.get_key.side_effect = lambda arg: f"Getting {arg} now"
mock_obj.bulk_add.side_effect = lambda arg: {"identifier": f"Adding the {len(arg)} elements"}
@pytest.fixture
def mock_plugin_stub(mocker):
# Option 1: Create a new class
# class SomePluginStub:
# Option 2: Inehrit from the actual class and just override the functions to mock
from folder.tileindex.base import SomePlugin
class SomePluginStub(SomePlugin):
def add(self, arg):
return f"Adding {arg} here"
def get_key(self, arg):
return f"Getting {arg} now"
def bulk_add(self, arg):
return {"identifier": f"Adding the {len(arg)} elements"}
mocker.patch("folder.plugin.SomePlugin", SomePluginStub)
def test_return_values(mock_plugin_return_values):
layer = BaseLayer()
result = layer.somefunction()
print(result)
assert result == ('Anything!', 'Something!', {'identifier': 'Nothing!'}, 'Nothing!')
def test_side_effect(mock_plugin_side_effect):
layer = BaseLayer()
result = layer.somefunction()
print(result)
assert result == ('Adding a here', 'Getting a now', {'identifier': 'Adding the 3 elements'}, 'Adding the 3 elements')
def test_stub(mock_plugin_stub):
layer = BaseLayer()
result = layer.somefunction()
print(result)
assert result == ('Adding a here', 'Getting a now', {'identifier': 'Adding the 3 elements'}, 'Adding the 3 elements')
输出
$ pytest -q -rP
... [100%]
=========================================== PASSES ============================================
_____________________________________ test_return_values ______________________________________
------------------------------------ Captured stdout call -------------------------------------
add: Anything!
get_key: Something!
bulk_add: {'identifier': 'Nothing!'}
status: Nothing!
('Anything!', 'Something!', {'identifier': 'Nothing!'}, 'Nothing!')
______________________________________ test_side_effect _______________________________________
------------------------------------ Captured stdout call -------------------------------------
add: Adding a here
get_key: Getting a now
bulk_add: {'identifier': 'Adding the 3 elements'}
status: Adding the 3 elements
('Adding a here', 'Getting a now', {'identifier': 'Adding the 3 elements'}, 'Adding the 3 elements')
__________________________________________ test_stub __________________________________________
------------------------------------ Captured stdout call -------------------------------------
add: Adding a here
get_key: Getting a now
bulk_add: {'identifier': 'Adding the 3 elements'}
status: Adding the 3 elements
('Adding a here', 'Getting a now', {'identifier': 'Adding the 3 elements'}, 'Adding the 3 elements')
3 passed in 0.06s
所以我有这个 class 位于 folder/layer/base.py 里面有这样的东西:
from folder.plugin import load_plugin
class BaseLayer:
def __init__(self):
self.tileindex = load_plugin()
我需要向 class 中已有的函数添加单元测试。我的问题是,函数 load_plugin()
returns 是 class 的一个实例,位于 folder/tileindex/base.py 中。因此,它发生了多次,并且在多个不同的函数中,一行看起来像这样:
def somefunction(self):
key = self.tileindex.get_key(...)
r = self.tileindex.bulk_add(...)
self.tileindex.add(...)
而且我不知道如何嘲笑它。起初我在嘲笑 load_plugin
并返回任何值,以便我以后可以断言它。但是现在我已经看到这些函数使用 self.tileindex
作为另一个 class 的实例,我不知道该怎么做。例如:
def register(self):
"""
Registers a file into the system
:returns: `bool` of status result
"""
items = [item for item in self.items if item['register_status']]
if len(items) > 1:
item_bulk = []
for item in items:
item_bulk.append(self.layer2dict(item))
LOGGER.debug('Adding to tileindex (bulk)')
r = self.tileindex.bulk_add(item_bulk)
status = r[items[0]['identifier']]
当我模拟 load_plugin
时,代码在最后一行失败说 TypeError: 'Mock' object is not subscriptable
。
我尝试导入实例化的 class 并直接模拟它。但是由于某种原因,我一输入 @patch('folder.tileindex.base')
.
AttributeError: <Group tileindex> does not have the attribute 'base'
有什么方法可以模拟 self.tileindex
本身,以便测试其余代码?
谢谢!
确保不使用 unittest.mock.Mock
,而是使用 unittest.mock.MagicMock
,原因如下 MagicMock
)。
对于您的情况,这里有 3 个选项来模拟 load_plugin()
返回的对象。您可以选择最适合您的需求。
mock_plugin_return_values
- 通过return_value
进行模拟
mock_plugin_side_effect
- 通过side_effect
进行模拟
mock_plugin_stub
- 通过打桩 class 来模拟
文件树
.
├── folder
│ ├── layer
│ │ └── base.py
│ ├── plugin.py
│ └── tileindex
│ └── base.py
└── tests
└── test_layer.py
folder/layer/base.py
from folder.plugin import load_plugin
class BaseLayer:
def __init__(self):
self.tileindex = load_plugin()
def somefunction(self):
a = self.tileindex.add("a")
print("add:", a)
key = self.tileindex.get_key("a")
print("get_key:", key)
r = self.tileindex.bulk_add([1, 2, 3])
print("bulk_add:", r)
status = r['identifier']
print("status:", status)
return a, key, r, status
folder/plugin.py
from folder.tileindex.base import SomePlugin
def load_plugin():
return SomePlugin()
folder/tileindex/base.py
class SomePlugin():
pass
test/test_layer.py
import pytest
from folder.layer.base import BaseLayer
# Note, this requires <pip install pytest-mock>
@pytest.fixture
def mock_plugin_return_values(mocker):
mock_cls = mocker.patch("folder.plugin.SomePlugin")
mock_obj = mock_cls.return_value
mock_obj.add.return_value = "Anything!"
mock_obj.get_key.return_value = "Something!"
mock_obj.bulk_add.return_value = {"identifier": "Nothing!"}
@pytest.fixture
def mock_plugin_side_effect(mocker):
mock_cls = mocker.patch("folder.plugin.SomePlugin")
mock_obj = mock_cls.return_value
mock_obj.add.side_effect = lambda arg: f"Adding {arg} here"
mock_obj.get_key.side_effect = lambda arg: f"Getting {arg} now"
mock_obj.bulk_add.side_effect = lambda arg: {"identifier": f"Adding the {len(arg)} elements"}
@pytest.fixture
def mock_plugin_stub(mocker):
# Option 1: Create a new class
# class SomePluginStub:
# Option 2: Inehrit from the actual class and just override the functions to mock
from folder.tileindex.base import SomePlugin
class SomePluginStub(SomePlugin):
def add(self, arg):
return f"Adding {arg} here"
def get_key(self, arg):
return f"Getting {arg} now"
def bulk_add(self, arg):
return {"identifier": f"Adding the {len(arg)} elements"}
mocker.patch("folder.plugin.SomePlugin", SomePluginStub)
def test_return_values(mock_plugin_return_values):
layer = BaseLayer()
result = layer.somefunction()
print(result)
assert result == ('Anything!', 'Something!', {'identifier': 'Nothing!'}, 'Nothing!')
def test_side_effect(mock_plugin_side_effect):
layer = BaseLayer()
result = layer.somefunction()
print(result)
assert result == ('Adding a here', 'Getting a now', {'identifier': 'Adding the 3 elements'}, 'Adding the 3 elements')
def test_stub(mock_plugin_stub):
layer = BaseLayer()
result = layer.somefunction()
print(result)
assert result == ('Adding a here', 'Getting a now', {'identifier': 'Adding the 3 elements'}, 'Adding the 3 elements')
输出
$ pytest -q -rP
... [100%]
=========================================== PASSES ============================================
_____________________________________ test_return_values ______________________________________
------------------------------------ Captured stdout call -------------------------------------
add: Anything!
get_key: Something!
bulk_add: {'identifier': 'Nothing!'}
status: Nothing!
('Anything!', 'Something!', {'identifier': 'Nothing!'}, 'Nothing!')
______________________________________ test_side_effect _______________________________________
------------------------------------ Captured stdout call -------------------------------------
add: Adding a here
get_key: Getting a now
bulk_add: {'identifier': 'Adding the 3 elements'}
status: Adding the 3 elements
('Adding a here', 'Getting a now', {'identifier': 'Adding the 3 elements'}, 'Adding the 3 elements')
__________________________________________ test_stub __________________________________________
------------------------------------ Captured stdout call -------------------------------------
add: Adding a here
get_key: Getting a now
bulk_add: {'identifier': 'Adding the 3 elements'}
status: Adding the 3 elements
('Adding a here', 'Getting a now', {'identifier': 'Adding the 3 elements'}, 'Adding the 3 elements')
3 passed in 0.06s