[UnitTest05]-Mock多个接口

本文介绍了在接口继承的情况下如何使用Mock进行单元测试,通过创建虚拟对象并定义接口行为,实现对代码逻辑的独立测试。

         Mock优势是针对抽象的接口创建虚拟的对象,为了追求接口的粒度更小,通常接口会继承其他接口,这时候如何Mock呢?下面的例子演示了这种情形下如何使用Mock来写单元测试。

         第一个接口

         public interface IFirstInterface
         {
             int firstInterfaceDoSomething();
         }

         第二个接口

         public interface ISecondInterface
     {
             int secondInterfaceDoSomething();
     }

         第三个接口,继承前两个接口

         public interface IMultiInterface:IFirstInterface,ISecondInterface
    {
        int multiInterfaceDoSomething();
    }

        有一个类,里面使用了第三个接口

        public int MultiInterfaceUser<T>(T x)
            where T:IFirstInterface,ISecondInterface
        {
            IFirstInterface f = (IFirstInterface)x;
            ISecondInterface s = (ISecondInterface)x;

            return f.firstInterfaceDoSomething() + s.secondInterfaceDoSomething();
        }

        单元测试如下:

        [TestMethod]
        public void TestMultiInterface()
        {
            Mock<IMultiInterface> multiMock = new Mock<IMultiInterface>();

            Mock<IFirstInterface> fisrtMock = multiMock.As<IFirstInterface>();
            fisrtMock.Setup(x => x.firstInterfaceDoSomething()).Returns(2);

            Mock<ISecondInterface> secondMock = multiMock.As<ISecondInterface>();
            secondMock.Setup(x => x.secondInterfaceDoSomething()).Returns(4);

            int result = new SomeClass().MultiInterfaceUser(multiMock.Object);

            Assert.AreEqual(6, result);

        }

        这种情形比较常见,我们不依赖实现接口的对象,对接口的行为进行了定义,并直接进行单元测试。

<think>我们正在讨论如何在pytest中使用mock来模拟外部依赖,特别是在接口测试中。当测试一个函数或方法时,如果它依赖于外部服务(如数据库、API等),我们通常不希望实际调用这些服务,而是用模拟对象(mock)替代,以便测试更可控、快速和独立。 使用`pytest-mock`插件可以方便地创建和管理mock对象。`pytest-mock`是`pytest`的一个插件,它提供了一个名为`mocker`的fixture,用于创建mock对象。这个插件实际上是对标准库`unittest.mock`的封装,使其更易于在pytest中使用。 下面是一个使用`pytest-mock`模拟外部依赖的示例: 假设我们有一个函数`get_user_from_api`,它通过调用外部API获取用户信息。我们希望在测试中模拟这个API调用,避免实际发送网络请求。 步骤: 1. 安装`pytest-mock`(如果尚未安装): ```bash pip install pytest-mock ``` 2. 编写被测试的代码(假设在`api.py`中): ```python import requests def get_user_from_api(user_id): url = f"https://jsonplaceholder.typicode.com/users/{user_id}" response = requests.get(url) response.raise_for_status() # 如果响应状态码不是200,抛出异常 return response.json() ``` 3. 编写测试用例(在测试文件中): ```python import pytest from api import get_user_from_api # 测试函数,使用mocker fixture def test_get_user_from_api(mocker): # 模拟requests.get方法 mock_get = mocker.patch('api.requests.get') # 创建一个模拟的响应对象 mock_response = mocker.Mock() mock_response.json.return_value = {'id': 1, 'name': 'John Doe'} mock_response.raise_for_status.return_value = None # 不抛出异常,表示成功 mock_get.return_value = mock_response # 调用被测试的函数 user = get_user_from_api(1) # 断言 assert user == {'id': 1, 'name': 'John Doe'} mock_get.assert_called_once_with("https://jsonplaceholder.typicode.com/users/1") ``` 解释: - `mocker.patch('api.requests.get')`:替换`api`模块中的`requests.get`方法为一个mock对象。注意,这里要模拟的是在`api`模块中导入的`requests.get`,所以路径是`'api.requests.get'`。 - 创建一个模拟的响应对象(`mock_response`),并设置它的`json`方法返回一个特定的字典,`raise_for_status`方法不抛出异常(模拟成功情况)。 - 将`mock_get`的返回值设置为这个模拟的响应对象。 - 调用被测试函数`get_user_from_api`,并断言返回结果是否符合预期。 - 使用`mock_get.assert_called_once_with(...)`来验证`requests.get`是否被正确调用(即调用了正确的URL)。 注意事项: - 模拟的位置:确保模拟的是被测试代码中实际使用的对象。例如,如果被测试函数中导入的是`import requests`,然后使用`requests.get`,那么在测试中应该模拟`api.requests.get`(假设函数在`api`模块中)。 - 清理:`pytest-mock`会自动在测试结束后撤销模拟,不需要手动清理。 除了模拟函数,还可以模拟类、属性、异常等。例如,模拟一个异常: ```python def test_get_user_from_api_raises_exception(mocker): mock_get = mocker.patch('api.requests.get') mock_response = mocker.Mock() # 模拟raise_for_status抛出异常 mock_response.raise_for_status.side_effect = requests.exceptions.HTTPError("Not found") mock_get.return_value = mock_response with pytest.raises(requests.exceptions.HTTPError): get_user_from_api(1) ``` 通过使用`pytest-mock`,我们可以有效地隔离外部依赖,使测试更专注于被测试代码的逻辑。 §§相关问题§§ 1. 在pytest中,如何使用mock模拟一个类的方法? 2. 当需要模拟多个外部依赖时,在pytest中如何组织mock对象? 3. 在pytest-mock中,`mocker.patch`和`mocker.patch.object`有什么区别? 4. 如何模拟一个第三方库中不存在的属性或方法? 5. 在pytest中,如何测试一个函数是否抛出了特定的异常?</think>在pytest中使用`pytest-mock`插件模拟外部依赖时,主要通过`mocker` fixture创建mock对象来隔离被测代码与外部服务(如API、数据库等)。以下是具体实现方法和示例: ### 核心步骤 1. **安装插件**: ```bash pip install pytest pytest-mock ``` 2. **使用`mocker` fixture**: ```python import pytest # 被测函数(依赖外部API) def fetch_data(api_client): return api_client.get("https://api.example.com/data") # 测试用例 def test_fetch_data(mocker): # 1. 创建API客户端的mock对象 mock_api = mocker.Mock() # 2. 设置mock返回值 mock_api.get.return_value = {"id": 1, "name": "Mocked Data"} # 3. 注入mock依赖并执行测试 result = fetch_data(mock_api) # 4. 验证行为 assert result == {"id": 1, "name": "Mocked Data"} mock_api.get.assert_called_once_with("https://api.example.com/data") # 验证调用参数 ``` ### 高级用法示例 **模拟异常和副作用**: ```python def test_api_failure(mocker): mock_api = mocker.Mock() # 模拟抛出异常 mock_api.get.side_effect = ConnectionError("API unreachable") with pytest.raises(ConnectionError): fetch_data(mock_api) ``` **模拟类方法**: ```python class Database: def query(self, sql): # 实际数据库操作 pass def test_db_query(mocker): # 创建类实例的mock mock_db = mocker.Mock(spec=Database) mock_db.query.return_value = [{"id": 1}] # 被测代码使用mock数据库 result = some_function(mock_db) assert len(result) == 1 ``` ### 最佳实践 1. **使用`spec`或`autospec`**:保持mock对象与原对象接口一致 ```python real_api = ExternalAPIClient() mock_api = mocker.Mock(spec=real_api) # 约束mock方法 ``` 2. **参数化模拟**: ```python @pytest.mark.parametrize("input,expected", [(1, "A"), (2, "B")]) def test_multiple_cases(mocker, input, expected): mock_service = mocker.Mock() mock_service.transform.return_value = expected ... ``` 3. **补丁上下文管理器**: ```python def test_patch_context(mocker): with mocker.patch("module.ExternalClass") as MockClass: instance = MockClass.return_value instance.method.return_value = 42 ... ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值