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

使用pytest-mock mock类方法时值解包报错的问题求解

问题根因

你遇到的解包错误本质是mock完全没生效,_get_update_sql走了原生逻辑没返回你预设的元组,叠加原代码本身存在两个低级bug:

  1. mock用法完全错误:mocker.Mock()是用来创建普通mock对象的,根本没有patch方法,你写的mock_manager.patch(...)等于没执行,类的方法根本没被替换。patch是pytest-mock的mocker fixture直接提供的方法,不需要额外新建Mock实例调用。
  2. 原MyClass代码存在语法和属性拼写错误:
    • __init__方法定义漏了第一个参数self,正常实例化就会抛参数不匹配错误
    • __init__里赋值的是self.update_type_id(无下划线前缀),但后续所有方法里调用的都是self._update_type_id(带前缀下划线),就算mock正常,这里也会抛属性不存在的错误
  3. 就算跳过上面的问题,patch路径错误也会导致mock不生效:patch必须指定方法在被测模块中的完整路径,直接写MyClass._get_update_sql找不到目标,补丁打不上。
正确实现

首先先修复MyClass里的基础bug,不然测试永远跑不通:

import logging
from typing import Tuple
from psycopg2 import Error # 对应代码里捕获的数据库Error类型
logger = logging.getLogger(__name__)

class MyClass:
    # 补全self参数
    def __init__(self, update_type_id='1'):
        # 统一属性名,和后续调用的带下划线版本保持一致
        self._update_type_id = update_type_id
        self._cursor = <database_connection>.cursor() # 替换为实际的数据库连接获取逻辑

    def update_start_dt(self):
        self._update_job_ctrl_start_dt()
        
    def _update_job_ctrl_start_dt(self):
        update_sql, v_1  = self._get_update_sql(self._update_type_id)
        logger.debug(f'Update sql: {update_sql} and v_1: {v_1}')
                
    def _get_update_sql(self, update_type_id: int) -> Tuple:
        sql = f"SELECT start_sql, end_sql FROM <database.table> where update_type_key = {self._update_type_id}"
        self._run_sql(sql)
        record = self._cursor.fetchone()
        if record:
            return record
        else:
            logger.error(f'Record Not Found. Update type key ({update_type_id}) not found in the table in the database')
            raise Exception

    def _run_sql(self, sql_statement: str):
        try:
            self._cursor.execute(sql_statement)
        except (Exception, Error) as e:
            logger.error(f'Error {e} encountered when reading from table')
            raise e

测试代码提供两种常用方案,按需选择即可:

方案1:mock内部私有方法,隔离下层逻辑(适合快速验证方法调用链路)

这种方式不需要碰数据库相关逻辑,只验证update_start_dt的调用流是否正确:

# 替换为你自己的类导入路径
from my_module import MyClass

class TestMyClass:
    # 测试方法第一个参数必须是self
    def test_update_start_dt(self, mocker, mock_get_connection, mock_run_sql):
        # 直接用mocker fixture打补丁,路径写类所在的完整模块路径
        # autospec=True会自动匹配原方法签名,避免传参错误
        mock_get_update_sql = mocker.patch(
            'my_module.MyClass._get_update_sql',
            return_value=('123', '234')
        )
        # mock日志避免无关输出,同时可以做断言
        mock_log = mocker.patch('my_module.logger.debug')

        # patch完成后再实例化类,实例会继承被mock的方法
        obj = MyClass(update_type_id='1')
        obj.update_start_dt()

        # 断言验证行为符合预期
        mock_get_update_sql.assert_called_once_with('1')
        mock_log.assert_called_once_with('Update sql: 123 and v_1: 234')

方案2:仅mock外部数据库依赖,不mock类内部方法(适合更贴近真实逻辑的测试)

如果你想覆盖内部私有方法的逻辑,只隔离真实数据库连接,可以直接mock游标返回:

from my_module import MyClass

class TestMyClass:
    def test_update_start_dt_with_real_internal_logic(self, mocker, mock_get_connection):
        # 构造mock游标,预设fetchone返回值
        mock_cursor = mocker.MagicMock()
        mock_cursor.fetchone.return_value = ('123', '234')
        # 让数据库连接的cursor方法返回我们的mock游标
        mock_get_connection.cursor.return_value = mock_cursor
        mock_log = mocker.patch('my_module.logger.debug')

        obj = MyClass(update_type_id='1')
        obj.update_start_dt()

        # 验证SQL正确执行
        mock_cursor.execute.assert_called_once()
        executed_sql = mock_cursor.execute.call_args[0][0]
        assert 'SELECT start_sql, end_sql' in executed_sql
        assert 'update_type_key = 1' in executed_sql
        # 验证日志输出正确
        mock_log.assert_called_once_with('Update sql: 123 and v_1: 234')
避坑提示
  • patch路径必须写被测类所在模块的完整导入路径,比如你的类在src/db/my_class.py里,导入路径是src.db.my_class.MyClass,patch路径就要写这个字符串,不能直接写类名。
  • 如果不想记patch路径规则,可以实例化对象之后直接给实例绑定mock方法:obj._get_update_sql = mocker.Mock(return_value=('123','234')),这种方式不会影响其他测试用例,更灵活。
  • 写测试前先跑通被测代码的基础逻辑,先排除语法错误、属性名拼写错误这类和mock无关的问题,不然排查方向会走偏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 06:39:17