2. Python unittest.mock入门:安装与导入、Mock对象创建、基本断言方法

unittest.mock是Python标准库中用于测试替身的核心模块。通过在测试中替换真实依赖,让被测单元与外界解耦,从而实现快速、可控、可重复的单元测试。本章从零开始搭建mock环境,掌握Mock对象的创建与基础断言。

核心原则:Mock对象模拟真实对象的行为——记录调用、返回预设值、验证交互,而不执行真实逻辑。

2.1 安装与导入

Python 3.3+内置unittest.mock模块,无需额外安装。直接导入即可使用:

# 标准导入方式
from unittest.mock import Mock, MagicMock, patch, call, sentinel

# 或导入整个模块
import unittest.mock

若使用Python 3.2及更早版本,需通过pip安装第三方backport:

pip install mock

导入后,通过mock.__version__确认版本(仅对第三方mock包生效)。

2.2 Mock对象创建

Mock对象有两种创建方式:直接实例化或通过工厂方法。

方式代码示例说明
直接实例化m = Mock()创建无配置的Mock对象
带参数m = Mock(return_value=42)设置默认返回值
MagicMockmm = MagicMock()支持魔术方法(如__len__、__iter__)
指定namem = Mock(name='db_client')调试时显示名称
指定specm = Mock(spec=SomeClass)限制只有spec中存在的属性/方法可用
实战推荐:测试接口类时优先使用spec参数,防止调用了真实对象不存在的方法而通过测试。

2.3 基本断言方法

Mock对象自动记录所有调用,通过断言方法验证调用是否如预期发生。主要断言方法见下表:

断言方法作用示例
assert_called()至少被调用一次mock.assert_called()
assert_called_once()仅被调用一次mock.assert_called_once()
assert_called_with(*args, **kwargs)最近一次调用使用指定参数mock.assert_called_with(1, key='val')
assert_called_once_with(*args, **kwargs)仅调用一次且参数匹配mock.assert_called_once_with(42)
assert_any_call(*args, **kwargs)曾经有某次调用匹配参数(不限顺序)mock.assert_any_call('start')
assert_not_called()从未被调用mock.assert_not_called()
assert_has_calls(calls, any_order=False)按顺序(或任意顺序)包含指定调用列表mock.assert_has_calls([call(1), call(2)])

配合call对象可以构建调用列表进行比较:

from unittest.mock import call

mock = Mock()
mock(1)
mock(2, x=3)
mock(4)

# 获取所有调用记录
print(mock.call_args_list)
# 输出: [call(1), call(2, x=3), call(4)]

# 断言调用顺序
mock.assert_has_calls([call(1), call(2, x=3), call(4)])

2.4 设置返回值与副作用

除了断言,Mock对象还需模拟被替换对象的返回值。通过return_value和side_effect控制:

属性/方法作用示例
return_value固定返回值mock.return_value = 42
side_effect可迭代对象、异常或函数mock.side_effect = [1, 2, 3]
或mock.side_effect = ValueError('bad')
side_effect为函数根据输入参数动态返回mock.side_effect = lambda x: x * 2
# 场景:模拟数据库查询,第一次返回空列表,第二次返回一条记录
mock_query = Mock()
mock_query.side_effect = [[], [{'id': 1}]]

assert mock_query() == []
assert mock_query() == [{'id': 1}]
常见陷阱:side_effect优先级高于return_value。若同时设置,side_effect生效。side_effect耗尽后若仍被调用,会触发StopIteration异常。

2.5 属性模拟与魔术方法

Mock对象可以动态添加属性,或使用MagicMock支持魔术方法:

# 普通Mock模拟属性
mock = Mock()
mock.some_attr = "hello"
mock.configure_mock(other_attr=100)

# MagicMock支持魔术方法
mm = MagicMock()
mm.__len__.return_value = 5
mm.__iter__.return_value = iter([1, 2, 3])
assert len(mm) == 5
assert list(mm) == [1, 2, 3]
Mock对象创建与断言流程图 创建Mock对象 Mock() / MagicMock() 配置行为 return_value/side_effect 在测试中使用 替换真实依赖 调用后断言 assert_called_once_with(...) 验证调用记录 call_args_list / assert_has_calls 清理恢复 tearDown / stop patching 核心工作流总结 ① 创建Mock → ② 配置行为(返回值/副作用/属性) ③ 注入测试代码 → ④ 调用后使用断言方法验证交互 ⑤ 清理:恢复原始依赖(patch配合tearDown)

2.6 完整实战示例

下面通过一个用户服务测试案例演示Mock的基本用法:

# 被测试的模块 user_service.py
class UserService:
    def __init__(self, db_client):
        self.db = db_client

    def get_user(self, user_id):
        result = self.db.query("SELECT * FROM users WHERE id=?", [user_id])
        if result:
            return {"id": result[0][0], "name": result[0][1]}
        return None

# 测试代码 test_user_service.py
from unittest.mock import Mock
from user_service import UserService

def test_get_user_returns_user_dict():
    # Arrange: 创建Mock数据库客户端,模拟查询返回
    mock_db = Mock()
    mock_db.query.return_value = [(1, "Alice")]
    service = UserService(mock_db)

    # Act
    user = service.get_user(1)

    # Assert
    assert user == {"id": 1, "name": "Alice"}
    mock_db.query.assert_called_once_with(
        "SELECT * FROM users WHERE id=?", [1]
    )

def test_get_user_returns_none_when_not_found():
    mock_db = Mock()
    mock_db.query.return_value = []
    service = UserService(mock_db)

    user = service.get_user(999)

    assert user is None
    mock_db.query.assert_called_once()

2.7 常见错误与调试

错误现象原因解决方案
断言通过但实际不调用未正确注入Mock对象打印mock.mock_calls查看实际调用
AttributeError: Mock object has no attribute 'xxx'访问了未设置的属性,且spec限制检查spec类是否包含该属性,或移除spec
调用统计偏差Mock被意外复用每个测试用例使用新的Mock实例
side_effect耗尽后异常side_effect列表长度小于调用次数改用side_effect函数动态生成
调试技巧:在测试临时添加print(mock.method_calls)或print(mock.call_args_list),可以快速确认Mock的实际交互。

2.8 性能与稳定性要点

Mock对象本身轻量,但大规模使用时的注意事项:

  • 避免过度Mock:只模拟直接依赖,不Mock内部实现细节,否则测试与实现耦合,重构时容易断裂。
  • 使用spec提升稳定性:若真实接口变更(如新增参数),Mock无spec则测试仍通过,产生假阳性。指定spec能让Mock在运行时校验接口一致性。
  • 清理Mock避免泄漏:使用patch时建议用上下文管理器或decorator,确保测试结束后自动恢复。
  • 避免共享状态:每个测试用例创建独立的Mock对象,不要全局复用,防止调用计数污染。

2.9 本章小结

通过本章学习,你应该掌握:

  • 导入unittest.mock模块(Python ≥ 3.3直接导入,老旧版本pip install mock)
  • 使用Mock()和MagicMock()创建测试替身,并通过return_value、side_effect、spec配置行为
  • 运用assert_called_with、assert_called_once_with、assert_has_calls等基本断言方法验证调用
  • 结合call对象分析调用历史
  • 遵循最佳实践避免常见陷阱

下一阶段将在第3章深入patch函数的使用,实现依赖注入的自动化替换,进一步简化测试准备代码。