Post

Mock

Mock

目录

  1. Mock测试简介
  2. 安装与准备
  3. 核心概念:Mock和patch
  4. 基础用法
    • 创建Mock对象
    • 验证方法调用
    • 设置返回值与副作用
  5. 实战示例
    • 模拟HTTP请求
    • 模拟数据库操作
  6. 高级技巧
    • 使用autospec确保接口兼容性
    • 使用Spy部分模拟对象
  7. 常见问题与最佳实践

1. Mock测试简介

Mock测试用于隔离被测代码的依赖项,通过模拟(Mock)外部服务(如API、数据库等)的行为,确保测试专注于当前代码逻辑,而非外部系统的可靠性。核心优势包括:

  • 加速测试:避免网络请求或复杂计算。
  • 提高稳定性:防止因外部服务故障导致测试失败。
  • 验证交互:确认代码是否按预期调用了依赖项。

2. 安装与准备

Python标准库已包含unittest.mock,无需额外安装。若使用第三方框架(如pytest),可安装插件:

1
pip install pytest

3. 核心概念

  • Mock对象:模拟任意对象,可自定义属性和行为。
  • patch:临时替换目标对象为Mock,支持装饰器或上下文管理器形式。

4. 基础用法

创建Mock对象

1
2
3
4
5
6
7
from unittest.mock import Mock

# 创建Mock对象
mock_obj = Mock()

# 调用Mock方法(不会报错)
mock_obj.some_method()

设置返回值与属性

1
2
3
4
5
6
7
# 设置方法返回值
mock_obj.calculate.return_value = 42
print(mock_obj.calculate())  # 输出 42

# 设置属性
mock_obj.name = "Test Object"
print(mock_obj.name)  # 输出 "Test Object"

验证调用行为

1
2
3
4
5
6
# 验证方法是否被调用
mock_obj.some_method.assert_called_once()

# 检查调用参数
mock_obj.method(arg1, arg2)
mock_obj.method.assert_called_with(arg1, arg2)

使用side_effect模拟异常或动态行为

1
2
3
4
5
6
7
# 抛出异常
mock_obj.divide.side_effect = ZeroDivisionError

# 动态返回值
mock_obj.get_status.side_effect = [200, 404]
print(mock_obj.get_status())  # 200
print(mock_obj.get_status())  # 404

5. 实战示例

示例1:模拟HTTP请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests
from unittest.mock import patch

def fetch_data(url):
    response = requests.get(url)
    return response.json()

# 测试用例
@patch('requests.get')
def test_fetch_data(mock_get):
    # 模拟返回数据
    mock_response = Mock()
    mock_response.json.return_value = {"key": "value"}
    mock_get.return_value = mock_response

    result = fetch_data("http://api.example.com/data")
    
    assert result == {"key": "value"}
    mock_get.assert_called_once_with("http://api.example.com/data")

示例2:模拟数据库操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from unittest.mock import Mock

class Database:
    def connect(self):
        pass  # 实际会连接真实数据库

def get_user(db, user_id):
    conn = db.connect()
    # 假设这里执行查询操作
    return {"id": user_id, "name": "Alice"}

def test_get_user():
    # 创建模拟数据库对象
    mock_db = Mock()
    mock_db.connect.return_value = "connection_handle"
    
    user = get_user(mock_db, 1)
    
    assert user == {"id": 1, "name": "Alice"}
    mock_db.connect.assert_called_once()

6. 高级技巧

使用autospec确保接口一致性

1
2
3
4
5
6
7
from unittest.mock import create_autospec

real_class = SomeComplexClass
mock_spec = create_autospec(real_class)

# 错误的调用会报错(如方法不存在)
mock_spec.invalid_method()  # AttributeError

使用Spy部分模拟对象

1
2
3
4
5
6
7
8
9
10
11
from unittest.mock import MagicMock

class Calculator:
    def add(self, a, b):
        return a + b

calc = Calculator()
calc.add = MagicMock(wraps=calc.add)  # 保留原方法,同时监控调用

calc.add(2, 3)  # 返回 5
calc.add.assert_called_with(2, 3)

7. 常见问题与最佳实践

  • 问题:忘记停止patch导致状态泄漏。
    • 解决:优先使用装饰器或上下文管理器。
  • 问题:过度Mock导致测试与实现紧耦合。
    • 建议:仅Mock外部依赖,保留业务逻辑的真实测试。
  • 最佳实践
    • 为每个测试用例独立创建Mock。
    • 使用specautospec避免接口不匹配。
This post is licensed under CC BY 4.0 by the author.