如何使用pytest-mock模拟Python类依赖开展单元测试
常见问题解答
pytest-mock和unittest.mock的关系
pytest-mock本质是对unittest.mock的封装,所有unittest.mock支持的能力在pytest-mock中都可以直接使用,二者用法完全等效,pytest-mock只是将相关能力封装为了mocker fixture,不需要手动导入patch、管理上下文,使用更便捷。
可以直接模拟整个类依赖,不需要逐个模拟方法
你不需要逐个给依赖类的每个方法打桩,将整个类或类实例替换为MagicMock后,所有方法默认都是Mock对象,仅需要为测试用例中会用到的方法设置返回值即可,未用到的方法不会影响测试执行。
无代码侵入的测试示例
以测试OWM类的now方法为例,完整测试代码如下:
def test_owm_now_method(mocker, default_weather_units): test_api_key = "test_key" test_city = "Beijing" # 模拟OWMProxy实例 mock_proxy = mocker.MagicMock() mock_weather_data = {"mock": "weather_data"} mock_proxy.now.return_value = mock_weather_data # 打桩OWMProxy的工厂方法 mocker.patch( "wayhome_weather_api.openweathermap.client.OWMProxy.from_api_key", return_value=mock_proxy ) # 模拟PyOwmDeserializer实例 mock_deserializer = mocker.MagicMock() mock_forecast = mocker.MagicMock(spec=ForecastModel) mock_deserializer.deserialize.return_value = mock_forecast # 打桩PyOwmDeserializer类的初始化 mocker.patch( "wayhome_weather_api.openweathermap.deserializers.PyOwmDeserializer", return_value=mock_deserializer ) # 执行测试逻辑 owm = OWM(test_api_key, default_weather_units) result = owm.now(test_city) # 断言调用符合预期 mock_proxy.now.assert_called_once_with(test_city) mock_deserializer.deserialize.assert_called_once_with(mock_weather_data, default_weather_units) assert result == mock_forecast
你之前编写的初始化测试也可以简化为:
def test_owm_initialises_successfully(mocker, default_weather_units): test_api_key = "test_key" # 仅打桩依赖的创建逻辑即可,不需要额外设置返回值 mock_proxy_factory = mocker.patch( "wayhome_weather_api.openweathermap.client.OWMProxy.from_api_key" ) mock_deserializer_cls = mocker.patch( "wayhome_weather_api.openweathermap.deserializers.PyOwmDeserializer" ) owm = OWM(test_api_key, default_weather_units) assert owm is not None # 可额外断言依赖的初始化逻辑符合预期 mock_proxy_factory.assert_called_once_with(test_api_key) mock_deserializer_cls.assert_called_once()
更推荐的依赖注入优化方案
你提到的依赖注入方案可测性更强,不需要维护冗长的patch路径,也不会因为模块路径变更导致测试失效,仅需要修改OWM类的构造函数,新增可选的依赖注入参数即可:
from typing import Protocol, Optional # 定义依赖的接口协议 class OWMProxyProtocol(Protocol): def for_time(self, city: str, iso_datetime: str): ... def for_day(self, city: str, day: str): ... def now(self, city: str): ... class DeserializerProtocol(Protocol): def deserialize(self, weather_data, units: WeatherUnits) -> ForecastModel: ... class OWM: def __init__( self, api_key: str, units: WeatherUnits, proxy: Optional[OWMProxyProtocol] = None, deserializer: Optional[DeserializerProtocol] = None ) -> None: self._api_key = api_key self._units = units self._pyorm = proxy or OWMProxy.from_api_key(api_key) self._deserializer = deserializer or PyOwmDeserializer()
修改后测试时不需要使用patch,直接传入模拟的依赖实例即可:
def test_owm_now_with_di(mocker, default_weather_units): test_api_key = "test_key" test_city = "Beijing" # 直接创建模拟依赖 mock_proxy = mocker.MagicMock(spec=OWMProxyProtocol) mock_weather = {"test": "data"} mock_proxy.now.return_value = mock_weather mock_deserializer = mocker.MagicMock(spec=DeserializerProtocol) mock_forecast = mocker.MagicMock() mock_deserializer.deserialize.return_value = mock_forecast # 注入依赖实例化待测类 owm = OWM( api_key=test_api_key, units=default_weather_units, proxy=mock_proxy, deserializer=mock_deserializer ) result = owm.now(test_city) mock_proxy.now.assert_called_once_with(test_city) mock_deserializer.deserialize.assert_called_once_with(mock_weather, default_weather_units) assert result == mock_forecast
内容的提问来源于stack exchange,提问作者anon_dcs3spp
相关产品推荐
相关产品推荐

