You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Pytest如何对Mock对象内部不可访问的本地实例做断言验证

问题描述

在Pytest单元测试中,需要对无法从外层对象直接访问的本地实例对象进行Mock并执行断言校验。

最小复现示例

待Mock的类为Executor:

class Executor:    
    def execute(self, func, *args):
        func(*args)

Mock Executor的fixture实现:

import pytest
@pytest.fixture
def executor(mocker):
    mocker.patch('executor.Executor.execute')
    return Executor()

基础场景下可验证Executor.execute被正确调用:

def test_execute(executor):
    func = lambda x: x**2
    executor.execute(func, 7)
    Executor.execute.assert_called_once_with(func, 7)

    # 以下场景会失败:当无法获取传入的lambda实例时如何完成测试
    # func2 = lambda y:y**2
    # Executor.execute.assert_called_once_with(func2, 7)

更通用的复杂参数场景:

def test_execute_with_complex_args(executor):
    def somefunc(x, y):
        x.increment(y)
    class SomeObj:
        x=30
        def increment(self, y):
            self.x += y
 
    obj = SomeObj()
    executor.execute(somefunc, obj, 7)
    Executor.execute.assert_called_once()

    # 以下断言仅在可获取somefunc、obj实例时生效
    Executor.execute.assert_called_once_with(somefunc, obj, 7)

    # 当无法访问somefunc、obj实例时,如何验证increment被传入参数7调用?
实际业务场景

测试基于SqlAlchemy实现的数据库客户端时,需要验证查询语句携带正确参数执行,客户端典型方法如下:

def get_job(self, job_id: UUID) -> Job:
        return run_transaction(sessionmaker(bind=self.engine),
                               lambda s: copy(s.query(Job).filter(Job.id == job_id).one()))

其中sessionmaker和run_transaction可直接Mock测试,但需要验证事务内部Session.query方法是否被正确调用,现有Mock方式无法覆盖这类内部调用的验证需求。


解决方案

核心思路:不要直接把外层拦截方法Mock成无逻辑的哑对象,通过Mock的side_effect配置自定义拦截逻辑,在捕获调用记录的同时拿到内部传参、执行内部逻辑,即可完成无实例引用场景下的断言。

通用处理方式

  • 如果不需要执行内部真实逻辑:调用完成后直接访问Mock对象的call_args属性,就能拿到所有历史调用的位置参数、关键字参数,不需要提前在测试用例中持有参数实例引用。
    def test_no_ref(executor):
        # 执行业务调用,内部传入的func、obj对测试用例不可见
        call_business_method()
        # 直接从mock对象取入参
        args, kwargs = Executor.execute.call_args
        called_func, called_first_arg = args[0], args[1]
        # 后续可以对拿到的参数做任意校验,比如检查函数源码、手动传参验证行为
    
  • 如果需要校验内部调用链行为:给外层Mock配置side_effect,拦截到调用后手动执行传入的函数/逻辑,同时提前Mock函数内部依赖的对象,即可捕获完整调用链的执行记录。

示例1:本地函数内部调用校验

def test_execute_inner_call(mocker):
    # 初始化Mock,不要直接截断execute逻辑
    mock_execute = mocker.patch('executor.Executor.execute')
    executor = Executor()
    # 提前Mock内部会调用的目标方法
    mock_increment = mocker.patch.object(SomeObj, 'increment')

    # 配置side_effect,拿到入参后手动执行传入的函数
    def execute_side_effect(func, *args):
        func(*args)
    mock_execute.side_effect = execute_side_effect

    # 执行业务代码(模拟内部生成somefunc、obj的不可见场景)
    call_business_execute()

    # 直接断言内部方法的调用情况,不需要持有somefunc、obj实例
    mock_increment.assert_called_once_with(7)

示例2:SqlAlchemy事务内部逻辑校验

针对数据库客户端场景,不需要让Mock的run_transaction直接返回固定值,配置side_effect主动执行传入的事务lambda,同时传入提前构造好的Mock Session对象,即可捕获Session上的所有调用:

def test_get_job(mocker):
    test_job_id = UUID("替换为测试用ID")
    # 提前构造Mock Session
    mock_session = mocker.MagicMock()
    # Mock session工厂
    mock_sessionmaker = mocker.patch('your_client_module.sessionmaker')
    mock_sessionmaker.return_value = lambda: mock_session
    # Mock事务方法
    mock_run_tx = mocker.patch('your_client_module.run_transaction')

    # 配置side_effect,执行事务内部的lambda
    def run_tx_side_effect(session_factory, tx_func):
        return tx_func(mock_session)
    mock_run_tx.side_effect = run_tx_side_effect

    # 执行被测方法
    db_client.get_job(test_job_id)

    # 直接断言Session查询逻辑是否符合预期
    mock_session.query.assert_called_once_with(Job)
    mock_session.query.return_value.filter.assert_called_once_with(Job.id == test_job_id)

内容的提问来源于stack exchange,提问作者Najeeb

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.03 05:57:31