如何使用 pytest-mock 模拟 python class 依赖项?
How to mock a python class dependency using pytest-mock?
我正在努力理解如何从 Python 和 pytest 中的 class 依赖项中存根 class / 模拟所有方法。下面的列表显示了我正在测试的 class。它有两个内部依赖项:OWMProxy
和 PyOwmDeserializer
.
Class 测试中
class OWM:
def __init__(self, api_key: str, units: WeatherUnits) -> None:
self._api_key = api_key
self._units = units
self._pyorm = OWMProxy.from_api_key(api_key)
self._deserializer = PyOwmDeserializer()
def at(self, city: str, iso_datetime: str) -> ForecastModel:
weather = self._pyorm.for_time(city, iso_datetime)
return self._deserializer.deserialize(weather, self._units)
def day(self, city: str, day: str) -> ForecastModel:
weather = self._pyorm.for_day(city, day)
return self._deserializer.deserialize(weather, self._units)
def now(self, city: str) -> ForecastModel:
weather = self._pyorm.now(city)
return self._deserializer.deserialize(weather, self._units)
我的问题是,在使用 PyTest 进行单元测试时是否可以模拟整个 class 依赖项?
目前我的单元测试使用mocker来mock每一个class方法,包括init方法
我可以使用依赖注入方法,即为内部反序列化器和代理接口创建一个接口,并将这些接口添加到被测 class 的构造函数中。
或者,我可以按照建议 使用 unittest.mock
模块进行测试。 pytest-mock
中是否有等效的功能???
到目前为止单元测试...
@pytest.mark.skip(reason="not implemented")
def test_owm_initialises_deserializer(
default_weather_units: WeatherUnits, mocker: MockFixture
) -> None:
api_key = "test_api_key"
proxy = OWMProxy(py_OWM(api_key))
patch_proxy = mocker.patch(
"wayhome_weather_api.openweathermap.client.OWMProxy.from_api_key",
return_value=proxy,
)
patch_val = mocker.patch(
"wayhome_weather_api.openweathermap.deserializers.PyOwmDeserializer",
"__init__",
return_value=None,
)
owm = OWM(api_key, default_weather_units)
assert owm is not None
您可以模拟整个 class 并控制其方法的 return 值 and/or 副作用,就像它在 docs.[=15 中所做的那样=]
>>> def some_function():
... instance = module.Foo()
... return instance.method()
...
>>> with patch('module.Foo') as mock:
... instance = mock.return_value
... instance.method.return_value = 'the result'
... result = some_function()
... assert result == 'the result'
假设被测class位于src.py.
test_owm.py
import pytest
from pytest_mock.plugin import MockerFixture
from src import OWM, WeatherUnits
@pytest.fixture
def default_weather_units():
return 40
def test_owm_mock(
default_weather_units: WeatherUnits, mocker: MockerFixture
) -> None:
api_key = "test_api_key"
# Note that you have to mock the version of the class that is defined/imported in the target source code to run. So here, if the OWM class is located in src.py, then mock its definition/import of src.OWMProxy and src.PyOwmDeserializer
patch_proxy = mocker.patch("src.OWMProxy.from_api_key")
patch_val = mocker.patch("src.PyOwmDeserializer")
owm = OWM(api_key, default_weather_units)
assert owm is not None
# Default patch
print("Default patch:", owm.day("Manila", "Today"))
# Customizing the return value
patch_proxy.return_value.for_day.return_value = "Sunny"
patch_val.return_value.deserialize.return_value = "Really Sunny"
print("Custom return value:", owm.day("Manila", "Today"))
patch_proxy.return_value.for_day.assert_called_with("Manila", "Today")
patch_val.return_value.deserialize.assert_called_with("Sunny", default_weather_units)
# Customizing the side effect
patch_proxy.return_value.for_day.side_effect = lambda city, day: f"{day} in hot {city}"
patch_val.return_value.deserialize.side_effect = lambda weather, units: f"{weather} is {units} deg celsius"
print("Custom side effect:", owm.day("Manila", "Today"))
patch_proxy.return_value.for_day.assert_called_with("Manila", "Today")
patch_val.return_value.deserialize.assert_called_with("Today in hot Manila", default_weather_units)
def test_owm_stub(
default_weather_units: WeatherUnits, mocker: MockerFixture
) -> None:
api_key = "test_api_key"
class OWMProxyStub:
@staticmethod
def from_api_key(api_key):
return OWMProxyStub()
def for_day(self, city, day):
return f"{day} in hot {city}"
class PyOwmDeserializerStub:
def deserialize(self, weather, units):
return f"{weather} is {units} deg celsius"
patch_proxy = mocker.patch("src.OWMProxy", OWMProxyStub)
patch_val = mocker.patch("src.PyOwmDeserializer", PyOwmDeserializerStub)
owm = OWM(api_key, default_weather_units)
assert owm is not None
# Default patch
print("Default patch:", owm.day("Manila", "Today"))
# If you want to assert the calls made as did in the first test above, you can use the mocker.spy() functionality
输出
$ pytest -q -rP
================================================================================================= PASSES ==================================================================================================
______________________________________________________________________________________________ test_owm_mock ______________________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Default patch: <MagicMock name='PyOwmDeserializer().deserialize()' id='139838844832256'>
Custom return value: Really Sunny
Custom side effect: Today in hot Manila is 40 deg celsius
______________________________________________________________________________________________ test_owm_stub ______________________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Default patch: Today in hot Manila is 40 deg celsius
2 passed in 0.06s
如您所见,我们能够控制模拟依赖项方法的 return 值。
我正在努力理解如何从 Python 和 pytest 中的 class 依赖项中存根 class / 模拟所有方法。下面的列表显示了我正在测试的 class。它有两个内部依赖项:OWMProxy
和 PyOwmDeserializer
.
Class 测试中
class OWM:
def __init__(self, api_key: str, units: WeatherUnits) -> None:
self._api_key = api_key
self._units = units
self._pyorm = OWMProxy.from_api_key(api_key)
self._deserializer = PyOwmDeserializer()
def at(self, city: str, iso_datetime: str) -> ForecastModel:
weather = self._pyorm.for_time(city, iso_datetime)
return self._deserializer.deserialize(weather, self._units)
def day(self, city: str, day: str) -> ForecastModel:
weather = self._pyorm.for_day(city, day)
return self._deserializer.deserialize(weather, self._units)
def now(self, city: str) -> ForecastModel:
weather = self._pyorm.now(city)
return self._deserializer.deserialize(weather, self._units)
我的问题是,在使用 PyTest 进行单元测试时是否可以模拟整个 class 依赖项?
目前我的单元测试使用mocker来mock每一个class方法,包括init方法
我可以使用依赖注入方法,即为内部反序列化器和代理接口创建一个接口,并将这些接口添加到被测 class 的构造函数中。
或者,我可以按照建议 unittest.mock
模块进行测试。 pytest-mock
中是否有等效的功能???
到目前为止单元测试...
@pytest.mark.skip(reason="not implemented")
def test_owm_initialises_deserializer(
default_weather_units: WeatherUnits, mocker: MockFixture
) -> None:
api_key = "test_api_key"
proxy = OWMProxy(py_OWM(api_key))
patch_proxy = mocker.patch(
"wayhome_weather_api.openweathermap.client.OWMProxy.from_api_key",
return_value=proxy,
)
patch_val = mocker.patch(
"wayhome_weather_api.openweathermap.deserializers.PyOwmDeserializer",
"__init__",
return_value=None,
)
owm = OWM(api_key, default_weather_units)
assert owm is not None
您可以模拟整个 class 并控制其方法的 return 值 and/or 副作用,就像它在 docs.[=15 中所做的那样=]
>>> def some_function(): ... instance = module.Foo() ... return instance.method() ... >>> with patch('module.Foo') as mock: ... instance = mock.return_value ... instance.method.return_value = 'the result' ... result = some_function() ... assert result == 'the result'
假设被测class位于src.py.
test_owm.py
import pytest
from pytest_mock.plugin import MockerFixture
from src import OWM, WeatherUnits
@pytest.fixture
def default_weather_units():
return 40
def test_owm_mock(
default_weather_units: WeatherUnits, mocker: MockerFixture
) -> None:
api_key = "test_api_key"
# Note that you have to mock the version of the class that is defined/imported in the target source code to run. So here, if the OWM class is located in src.py, then mock its definition/import of src.OWMProxy and src.PyOwmDeserializer
patch_proxy = mocker.patch("src.OWMProxy.from_api_key")
patch_val = mocker.patch("src.PyOwmDeserializer")
owm = OWM(api_key, default_weather_units)
assert owm is not None
# Default patch
print("Default patch:", owm.day("Manila", "Today"))
# Customizing the return value
patch_proxy.return_value.for_day.return_value = "Sunny"
patch_val.return_value.deserialize.return_value = "Really Sunny"
print("Custom return value:", owm.day("Manila", "Today"))
patch_proxy.return_value.for_day.assert_called_with("Manila", "Today")
patch_val.return_value.deserialize.assert_called_with("Sunny", default_weather_units)
# Customizing the side effect
patch_proxy.return_value.for_day.side_effect = lambda city, day: f"{day} in hot {city}"
patch_val.return_value.deserialize.side_effect = lambda weather, units: f"{weather} is {units} deg celsius"
print("Custom side effect:", owm.day("Manila", "Today"))
patch_proxy.return_value.for_day.assert_called_with("Manila", "Today")
patch_val.return_value.deserialize.assert_called_with("Today in hot Manila", default_weather_units)
def test_owm_stub(
default_weather_units: WeatherUnits, mocker: MockerFixture
) -> None:
api_key = "test_api_key"
class OWMProxyStub:
@staticmethod
def from_api_key(api_key):
return OWMProxyStub()
def for_day(self, city, day):
return f"{day} in hot {city}"
class PyOwmDeserializerStub:
def deserialize(self, weather, units):
return f"{weather} is {units} deg celsius"
patch_proxy = mocker.patch("src.OWMProxy", OWMProxyStub)
patch_val = mocker.patch("src.PyOwmDeserializer", PyOwmDeserializerStub)
owm = OWM(api_key, default_weather_units)
assert owm is not None
# Default patch
print("Default patch:", owm.day("Manila", "Today"))
# If you want to assert the calls made as did in the first test above, you can use the mocker.spy() functionality
输出
$ pytest -q -rP
================================================================================================= PASSES ==================================================================================================
______________________________________________________________________________________________ test_owm_mock ______________________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Default patch: <MagicMock name='PyOwmDeserializer().deserialize()' id='139838844832256'>
Custom return value: Really Sunny
Custom side effect: Today in hot Manila is 40 deg celsius
______________________________________________________________________________________________ test_owm_stub ______________________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Default patch: Today in hot Manila is 40 deg celsius
2 passed in 0.06s
如您所见,我们能够控制模拟依赖项方法的 return 值。