You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 19:27:03