Featured image of post Pytest_mock详解

Pytest_mock详解

Pytest_mock的使用方法


使用 pytest-mock 进行 Python 高级单元测试与模拟

一、单元测试与模拟的意义

在软件开发中,单元测试用于验证代码逻辑的正确性。但实际项目中,代码常依赖外部服务(如数据库、API、文件系统)。直接测试这些依赖会导致:

  1. 测试速度变慢
  2. 测试结果不可控
  3. 产生副作用(如真实发送邮件)

模拟(Mocking) 技术通过创建虚拟对象替代真实依赖,使测试聚焦于当前单元的逻辑。


二、环境安装

使用 pytestpytest-mock 插件:

1
pip install pytest pytest-mock

三、基础用法示例

场景:邮件发送服务

假设我们有一个邮件发送类 EmailSender

1
2
3
4
5
# email_sender.py
class EmailSender:
    def send(self, to, subject, body):
        # 真实发送邮件的逻辑
        return True

测试时不应实际发送邮件。使用 pytest-mock 模拟 send 方法:

1
2
3
4
5
6
7
8
9
# test_email_sender.py
def test_send_email(mocker):
    mock_send = mocker.patch("email_sender.EmailSender.send")
    sender = EmailSender()
    
    result = sender.send("user@example.com", "Hello", "Test email")
    
    mock_send.assert_called_once_with("user@example.com", "Hello", "Test email")
    assert result is True

代码解析:

  • mocker.patch 替换了 EmailSender.send 方法
  • 验证方法是否以正确参数被调用
  • 断言返回值为 True

四、高级模拟技巧

1. 模拟返回值

1
2
3
4
def test_mock_return_value(mocker):
    mocker.patch("module.Class.method", return_value="mocked_response")
    instance = Class()
    assert instance.method() == "mocked_response"

2. 模拟抛出异常

1
2
3
4
5
def test_mock_exception(mocker):
    mocker.patch("module.Class.method", side_effect=Exception("DB Error"))
    instance = Class()
    with pytest.raises(Exception, match="DB Error"):
        instance.method()

3. 模拟连续调用

1
2
3
4
5
6
7
def test_multiple_returns(mocker):
    mock_method = mocker.patch("module.Class.method")
    mock_method.side_effect = [10, 20, 30]
    
    assert Class().method() == 10
    assert Class().method() == 20
    assert Class().method() == 30

4. 模拟属性与方法链

1
2
3
4
5
6
def test_chained_methods(mocker):
    mock_instance = mocker.MagicMock()
    mock_instance.method1().method2().method3.return_value = "final"
    
    assert mock_instance.method1().method2().method3() == "final"
    mock_instance.method1.assert_called_once()

五、模拟复杂场景

场景:API 客户端测试

1
2
3
4
5
6
7
# api_client.py
import requests

class APIClient:
    def get_user_data(self, user_id):
        response = requests.get(f"https://api.example.com/users/{user_id}")
        return response.json()

测试时模拟 requests.get

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def test_api_client(mocker):
    mock_response = mocker.MagicMock()
    mock_response.json.return_value = {"id": 1, "name": "John"}
    
    mock_get = mocker.patch("requests.get", return_value=mock_response)
    
    client = APIClient()
    data = client.get_user_data(1)
    
    mock_get.assert_called_once_with("https://api.example.com/users/1")
    assert data == {"id": 1, "name": "John"}

六、最佳实践与注意事项

  1. 精确断言:不仅要验证是否调用,还要检查参数

    1
    
    mock_method.assert_called_with(arg1, arg2=expected_value)
    
  2. 使用 autospec:保持模拟对象与原对象接口一致

    1
    
    mocker.patch("module.Class", autospec=True)
    
  3. 避免过度模拟:核心业务逻辑尽量使用真实对象

  4. 清理模拟:使用 mocker Fixture 自动清理,避免测试污染


七、总结

通过 pytest-mock 我们可以:

  • ✅ 隔离测试环境
  • ✅ 模拟各种边界条件
  • ✅ 验证对象交互行为
  • ✅ 提升测试执行速度

掌握模拟技术能显著提高测试覆盖率与代码质量。建议结合 官方文档 深入学习更复杂的使用场景。


使用 Hugo 构建
主题 StackJimmy 设计