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

Python Mocking:如何获取传入另一个Mock的函数调用参数?

测试smbus2 I2C通信函数时,如何验证write/read的调用参数?

问题背景

我有一段通过smbus2与I2C设备交互的代码:

# device.py
import smbus2

def set_config(bus):
    write = smbus2.i2c_msg.write(0x76, [0x00, 0x01])
    read = smbus2.i2c_msg.read(0x76, 3)
    bus.i2c_rdwr(write, read)

为了不依赖硬件做单元测试,我用pytest-mock对smbus2进行Mock,但遇到了问题:测试时传入的bus mock里,只能看到i2c_rdwr被调用,却看不到smbus2.i2c_msg.write和read的传入参数——这些参数只出现在smbus2模块的mock调用记录里。

测试代码如下:

# test_device.py
# Depends on pytest-mock
import device

def test_set_config(mocker):
    mocker.patch('device.smbus2')
    smbus = mocker.MagicMock()

    device.set_config(smbus)

    # 调试发现:smbus.method_calls里只有i2c_rdwr的调用,参数是两个MagicMock对象,看不到write/read的参数
    # 但smbus2.method_calls里能看到write(0x76, [0x00, 0x01])和read(0x76, 3)的完整调用

问题原因

这是因为smbus2.i2c_msg.write和read是直接调用smbus2模块的方法,它们的调用记录会被存在smbus2的Mock对象里;而传入set_config的bus只是接收了这两个方法返回的Mock实例,bus.i2c_rdwr的调用记录只会记录传入的对象,不会追踪这些对象的创建参数。

用简化示例就能看明白:

from unittest.mock import MagicMock

foo = MagicMock()
bar = MagicMock()

w = foo.write(1, 2)  # 调用记录存在foo里
r = foo.read(1, 2)   # 调用记录存在foo里
bar.func(w, r)       # bar的调用记录只存w和r这两个对象,不存它们的创建参数

解决办法

不需要从bus的调用记录里找参数,直接从smbus2的Mock对象里验证write和read的调用即可,同时验证bus.i2c_rdwr是否被正确调用。

修改后的测试代码:

def test_set_config(mocker):
    # 捕获被patch的smbus2模块Mock
    mock_smbus2 = mocker.patch('device.smbus2')
    smbus = mocker.MagicMock()

    device.set_config(smbus)

    # 验证smbus2.i2c_msg.write的调用参数
    mock_smbus2.i2c_msg.write.assert_called_once_with(0x76, [0x00, 0x01])
    # 验证smbus2.i2c_msg.read的调用参数
    mock_smbus2.i2c_msg.read.assert_called_once_with(0x76, 3)
    # 验证bus.i2c_rdwr被调用,且参数是write和read返回的Mock对象
    smbus.i2c_rdwr.assert_called_once_with(
        mock_smbus2.i2c_msg.write.return_value,
        mock_smbus2.i2c_msg.read.return_value
    )

额外说明

如果想完全避免在测试环境安装smbus2,可以直接Mock整个模块的导入:

def test_set_config(mocker):
    # Mock整个smbus2模块,包括i2c_msg和里面的方法
    mock_smbus2 = mocker.MagicMock()
    mocker.patch('device.smbus2', mock_smbus2)
    smbus = mocker.MagicMock()

    device.set_config(smbus)

    # 同样的验证逻辑
    mock_smbus2.i2c_msg.write.assert_called_once_with(0x76, [0x00, 0x01])
    mock_smbus2.i2c_msg.read.assert_called_once_with(0x76, 3)
    smbus.i2c_rdwr.assert_called_once()

这样即使测试环境没有安装smbus2,也能正常运行测试。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 10:45:37