跳转至

mock 模块

Python mock 模块详解

mock 模块(从 Python 3.3 开始集成为 unittest.mock)用于在测试中替换和模拟对象的行为。它可以帮助模拟复杂的依赖关系,使单元测试更加独立和可控。

1. 基本概念

mock 模块的核心类是 Mock,它允许你创建“伪造”的对象,这些对象可以被动态地赋予任何属性和行为。

from unittest.mock import Mock

# 创建一个 Mock 对象
mock_obj = Mock()

2. 模拟方法和属性

  • 模拟方法:
mock_obj.some_method.return_value = 42
result = mock_obj.some_method()
assert result == 42
  • 模拟属性:
mock_obj.some_attr = 'mocked attribute'
assert mock_obj.some_attr == 'mocked attribute'

3. 检查方法调用

  • 检查方法是否被调用:
mock_obj.some_method()
mock_obj.some_method.assert_called()
  • 检查方法调用次数:
mock_obj.some_method()
mock_obj.some_method()
mock_obj.some_method.assert_called_once()  # 检查是否只调用了一次
  • 检查方法调用的参数:
mock_obj.some_method('hello', 'world')
mock_obj.some_method.assert_called_with('hello', 'world')
  • 检查方法的调用顺序:
from unittest.mock import call

mock_obj.some_method('first')
mock_obj.some_method('second')

mock_obj.some_method.assert_has_calls([call('first'), call('second')])

4. 使用 patch 装饰器/上下文管理器

patchmock 模块中最常用的功能之一,它可以临时替换对象或模块的属性。

  • 使用 patch 装饰器:
from unittest.mock import patch

@patch('module.ClassName')
def test_something(mock_class):
    instance = mock_class.return_value
    instance.method.return_value = 'mocked'

    result = instance.method()
    assert result == 'mocked'
  • 使用 patch 上下文管理器:
with patch('module.ClassName') as mock_class:
    instance = mock_class.return_value
    instance.method.return_value = 'mocked'

    result = instance.method()
    assert result == 'mocked'

5. 使用 patch.object 替换对象属性

patch.object 允许替换特定对象的属性或方法。

class MyClass:
    def method(self):
        return 'original'

my_obj = MyClass()

with patch.object(my_obj, 'method', return_value='mocked'):
    result = my_obj.method()
    assert result == 'mocked'

6. MagicMock

MagicMockMock 的一个子类,它模拟了所有魔法方法(如 __len____getitem__ 等)。

from unittest.mock import MagicMock

mock_list = MagicMock()

# 模拟 list 的行为
mock_list.__len__.return_value = 10
assert len(mock_list) == 10

mock_list.__getitem__.return_value = 'mocked'
assert mock_list[0] == 'mocked'

7. 自动补全属性和方法

mock 对象的任意属性和方法默认都是 Mock 对象。

mock_obj = Mock()

# 动态生成属性和方法
result = mock_obj.anything.you.can.imagine()
assert isinstance(result, Mock)

8. side_effect 用法

side_effect 可以指定调用方法时的副作用。它可以是一个函数、异常或一个返回值序列。

  • 抛出异常:
mock_obj.method.side_effect = ValueError("Error occurred")
try:
    mock_obj.method()
except ValueError as e:
    assert str(e) == "Error occurred"
  • 根据输入参数返回不同值:
def side_effect(arg):
    return arg * 2

mock_obj.method.side_effect = side_effect
assert mock_obj.method(3) == 6
  • 返回不同的序列值:
mock_obj.method.side_effect = [1, 2, 3]
assert mock_obj.method() == 1
assert mock_obj.method() == 2
assert mock_obj.method() == 3

9. mock_open 读取文件

mock_open 用于模拟文件读取操作,特别是在测试需要模拟 open() 函数时。

from unittest.mock import mock_open, patch

m = mock_open(read_data='file content')

with patch('builtins.open', m):
    with open('file.txt') as f:
        result = f.read()
        assert result == 'file content'

10. 重置 Mock 对象

reset_mock() 方法用于重置 mock 对象的所有调用记录和状态。

mock_obj = Mock()
mock_obj.method()
mock_obj.reset_mock()
assert mock_obj.method.call_count == 0

总结

unittest.mock 模块为测试提供了强大的工具,使得你能够轻松地模拟对象、替换依赖并检查交互。它可以帮助你编写更健壮的单元测试,使测试代码与实际代码解耦。