pytest-mock模拟测试指南Skill pytest-mock-guide

pytest-mock插件使用指南,详细讲解如何在Python单元测试中使用模拟、补丁、监视和存根技术。涵盖mocker夹具、patch方法、spy/stub模式、断言辅助工具等核心功能,帮助开发者编写高质量、隔离性好的测试代码。关键词:pytest-mock,Python单元测试,模拟测试,mocker夹具,补丁方法,测试隔离,自动化测试,测试驱动开发。

测试 0 次安装 0 次浏览 更新于 3/2/2026

name: pytest-mock-guide description: 使用pytest-mock插件进行模拟测试的指南。当编写需要模拟、补丁、监视或存根的pytest测试时使用。涵盖mocker夹具用法、补丁方法、监视/存根模式以及断言辅助工具。

pytest-mock 使用指南

pytest-mock是一个pytest插件,提供mocker夹具作为Python的unittest.mock补丁API的轻量级包装器。它会在每个测试结束后自动撤销所有模拟。

mocker 夹具

mocker夹具是主要接口。在测试函数中请求它:

def test_example(mocker):
    # 所有模拟在此测试后自动清理
    mock_func = mocker.patch("module.function")

可用的夹具作用域

夹具 作用域 使用场景
mocker 函数 默认,每个测试的模拟
class_mocker 在测试类中共享模拟
module_mocker 模块 在测试模块中共享模拟
package_mocker 在包中共享模拟
session_mocker 会话 在整个会话中共享模拟

补丁方法

mocker.patch(target, …)

通过点分路径补丁模块级对象:

def test_patch(mocker):
    # 补丁os.remove函数
    mock_remove = mocker.patch("os.remove")
    mock_remove.return_value = None

    os.remove("file.txt")
    mock_remove.assert_called_once_with("file.txt")

mocker.patch.object(target, attribute, …)

直接补丁对象上的属性:

def test_patch_object(mocker):
    import os
    mock_remove = mocker.patch.object(os, "remove")

    os.remove("file.txt")
    mock_remove.assert_called_once_with("file.txt")

mocker.patch.dict(in_dict, values, clear=False)

临时补丁字典:

def test_patch_dict(mocker):
    config = {"debug": False}
    mocker.patch.dict(config, {"debug": True})

    assert config["debug"] is True
    # 测试后,config["debug"]恢复为False

mocker.patch.multiple(target, **kwargs)

一次性补丁多个属性:

def test_patch_multiple(mocker):
    mocks = mocker.patch.multiple(
        "os",
        remove=mocker.DEFAULT,
        listdir=mocker.DEFAULT
    )

    os.remove("file.txt")
    os.listdir("/tmp")

    mocks["remove"].assert_called_once()
    mocks["listdir"].assert_called_once()

mocker.patch.context_manager(target, attribute, …)

patch.object相同,但当模拟用作上下文管理器时不发出警告:

def test_context_manager(mocker):
    mock_open = mocker.patch.context_manager(builtins, "open")
    # 使用`with mock_open(...)`时无警告

常见补丁参数

参数 描述
new 替换目标的对象
return_value 调用模拟时返回的值
side_effect 要引发的异常或要调用的函数
autospec 创建匹配目标签名的模拟
spec 用作规范的对象
spec_set 更严格的规范,防止设置新属性
create 允许补丁不存在的属性
new_callable 创建模拟的可调用对象

使用 mocker.spy() 进行监视

监视包装真实方法同时跟踪调用:

def test_spy(mocker):
    spy = mocker.spy(os.path, "exists")

    # 调用真实方法
    result = os.path.exists("/tmp")

    # 但我们可以检查调用
    spy.assert_called_once_with("/tmp")

    # 访问返回值
    assert spy.spy_return == result
    assert spy.spy_return_list == [result]  # 所有返回值

监视属性

属性 描述
spy_return 真实方法的最后一个返回值
spy_return_list 所有返回值的列表
spy_return_iter 迭代器副本(当duplicate_iterators=True时)
spy_exception 引发的最后一个异常(如果有)

监视迭代器

def test_spy_iterator(mocker):
    spy = mocker.spy(obj, "get_items", duplicate_iterators=True)

    items = list(obj.get_items())

    # 访问返回迭代器的副本
    spy_items = list(spy.spy_return_iter)

创建存根

mocker.stub(name=None)

创建接受任何参数的存根:

def test_stub(mocker):
    callback = mocker.stub(name="my_callback")

    some_function(on_complete=callback)

    callback.assert_called_once()

mocker.async_stub(name=None)

创建异步存根:

async def test_async_stub(mocker):
    callback = mocker.async_stub(name="async_callback")

    await some_async_function(on_complete=callback)

    callback.assert_awaited_once()

模拟辅助工具

mocker.create_autospec(spec, …)

创建匹配规范签名的模拟:

def test_autospec(mocker):
    mock_obj = mocker.create_autospec(MyClass, instance=True)

    # 使用错误参数调用会引发TypeError
    mock_obj.method()  # 如果method()无参数则OK

直接模拟类

通过mocker直接访问模拟类:

def test_mock_classes(mocker):
    mock = mocker.Mock()
    magic_mock = mocker.MagicMock()
    async_mock = mocker.AsyncMock()
    property_mock = mocker.PropertyMock()
    non_callable = mocker.NonCallableMock()

其他实用工具

def test_utilities(mocker):
    # 匹配任何参数
    mock.assert_called_with(mocker.ANY)

    # 创建用于断言的调用对象
    mock.assert_has_calls([mocker.call(1), mocker.call(2)])

    # 哨兵对象
    result = mocker.sentinel.my_result

    # 模拟文件打开
    m = mocker.mock_open(read_data="文件内容")
    mocker.patch("builtins.open", m)

    # 密封模拟以防止新属性
    mocker.seal(mock)

管理模拟

mocker.stopall()

立即停止所有补丁:

def test_stopall(mocker):
    mocker.patch("os.remove")
    mocker.patch("os.listdir")

    mocker.stopall()  # 两个补丁都停止

mocker.stop(mock)

停止特定补丁:

def test_stop(mocker):
    mock_remove = mocker.patch("os.remove")

    mocker.stop(mock_remove)  # 仅停止此补丁

mocker.resetall()

重置所有模拟而不停止它们:

def test_resetall(mocker):
    mock_func = mocker.patch("module.func")
    mock_func("arg1")

    mocker.resetall()

    mock_func.assert_not_called()  # 调用历史已清除

带有pytest内省的断言方法

pytest-mock通过pytest的比较增强了断言错误消息:

def test_assertions(mocker):
    mock = mocker.patch("module.func")
    mock("actual_arg")

    # 增强的错误显示预期与实际之间的差异
    mock.assert_called_with("expected_arg")
    # AssertionError显示:
    # Args:
    # assert ('actual_arg',) == ('expected_arg',)

可用的断言

调用断言:

  • assert_called() - 至少调用一次
  • assert_called_once() - 恰好调用一次
  • assert_called_with(*args, **kwargs) - 最后一次调用匹配
  • assert_called_once_with(*args, **kwargs) - 调用一次且参数匹配
  • assert_any_call(*args, **kwargs) - 任何调用匹配
  • assert_has_calls(calls, any_order=False) - 有特定调用
  • assert_not_called() - 从未调用

异步断言(用于AsyncMock):

  • assert_awaited()
  • assert_awaited_once()
  • assert_awaited_with(*args, **kwargs)
  • assert_awaited_once_with(*args, **kwargs)
  • assert_any_await(*args, **kwargs)
  • assert_has_awaits(calls, any_order=False)
  • assert_not_awaited()

配置选项

pytest.inipyproject.tomlsetup.cfg中:

[pytest]
# 启用/禁用增强的断言消息(默认:true)
mock_traceback_monkeypatch = true

# 使用独立的mock包而不是unittest.mock(默认:false)
mock_use_standalone_module = false

常见模式

补丁使用位置(而非定义位置)

# my_module.py
from os.path import exists

def check_file(path):
    return exists(path)

# test_my_module.py
def test_check_file(mocker):
    # 补丁使用位置,而非定义位置
    mocker.patch("my_module.exists", return_value=True)
    assert check_file("/any/path") is True

测试异常

def test_exception(mocker):
    mock_func = mocker.patch("module.func")
    mock_func.side_effect = ValueError("错误消息")

    with pytest.raises(ValueError, match="错误消息"):
        module.func()

多个返回值

def test_multiple_returns(mocker):
    mock_func = mocker.patch("module.func")
    mock_func.side_effect = [1, 2, 3]

    assert module.func() == 1
    assert module.func() == 2
    assert module.func() == 3

异步函数模拟

async def test_async(mocker):
    mock_fetch = mocker.patch("module.fetch_data")
    mock_fetch.return_value = {"data": "value"}

    result = await module.fetch_data()
    assert result == {"data": "value"}

类方法模拟

def test_class_method(mocker):
    mocker.patch.object(MyClass, "class_method", return_value="mocked")

    assert MyClass.class_method() == "mocked"

属性模拟

def test_property(mocker):
    mock_prop = mocker.patch.object(
        MyClass, "my_property",
        new_callable=mocker.PropertyMock,
        return_value="mocked"
    )

    obj = MyClass()
    assert obj.my_property == "mocked"