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
相关产品推荐
相关产品推荐

