使用Mox模拟依赖测试create_interface函数时出现意外方法调用错误的问题排查
你遇到的Unexpected method call function.__call__(...)错误,通常是因为Mox没有正确捕获到代码中实际调用的函数,或者mock的目标对象不对。结合你的代码,我们来一步步排查问题。
可能的原因及解决方案
1. 被mock的函数与实际调用的函数不匹配
最常见的原因是:你测试中mock的vi.validate_interface,并不是create_interface函数实际调用的那个函数。
比如,如果你的vi模块中是这样导入validate_interface的:
# vi.py from another_module import validate_interface def create_interface(iface, address, netmask): validate_interface(iface, address, netmask) # 这里调用的是another_module.validate_interface # ... 其他代码
那你测试中mockvi.validate_interface就不对了,因为函数内部实际调用的是another_module里的原函数,而不是vi模块中的引用。这种情况下,你需要mock原始模块的函数:
# 测试代码中替换对应的mock语句 self.mox.StubOutWithMock(another_module, "validate_interface") another_module.validate_interface(iface, address, netmask).AndReturn(None)
如果validate_interface是定义在vi模块内部的,那请确认你的mock步骤顺序是否正确:必须先StubOut函数,再设置调用预期,最后执行ReplayAll。你的代码顺序是对的,但可以尝试在StubOut之后打印vi.validate_interface,确认它已经被Mox替换成mock对象。
2. subprocess.Popen的mock缺少关键参数
看你被测试的代码中,Popen调用带有shell=True参数,但你的测试mock时没有指定这个参数:
# 被测试代码中的Popen调用 proc = Popen(["sudo ifconfig ..."], shell=True, stdout=PIPE, stderr=subprocess.PIPE) # 你的测试mock subprocess.Popen( IgnoreArg(), stdout=subprocess.PIPE, stderr=subprocess.PIPE).AndReturn(process)
Mox会严格匹配参数(除非用IgnoreArg忽略),这里你忽略了第一个参数,但shell=True是明确传递的,所以需要在mock中加上这个参数,否则Mox会认为这是一个未预期的调用:
subprocess.Popen( IgnoreArg(), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).AndReturn(process)
3. 未处理函数抛出的异常
你的测试中设置了process.returncode = 1,这会导致create_interface抛出Exception,但你的测试代码直接调用了vi.create_interface却没有捕获这个异常,这会导致测试失败。你需要用断言来捕获这个预期的异常:
# 替换原调用语句 self.assertRaises(Exception, vi.create_interface, iface, address, netmask)
4. communicate返回值类型错误
proc.communicate()返回的是两个字符串(stdout和stderr的内容),但你的测试中返回了("", 0),第二个值是整数,这不符合实际返回类型,可能导致后续代码逻辑异常,建议修改为:
process.communicate().AndReturn(("", "some error message"))
修正后的测试代码示例
def test_create_interface(self): iface = "eth0:1" address = "1.2.3.4" netmask = "255.255.255.0" import subprocess # 创建process mock并设置返回值 process = self.mox.CreateMock(subprocess.Popen) process.returncode = 1 process.communicate().AndReturn(("", "Failed to configure interface")) # Mock validate_interface(确保mock的是实际被调用的函数) self.mox.StubOutWithMock(vi, "validate_interface") vi.validate_interface(iface, address, netmask).AndReturn(None) # Mock ping_test self.mox.StubOutWithMock(vi, "ping_test") vi.ping_test(address).AndReturn(False) # Mock subprocess.Popen,加上shell=True参数 self.mox.StubOutWithMock(subprocess, "Popen") subprocess.Popen( IgnoreArg(), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE ).AndReturn(process) self.mox.ReplayAll() # 捕获预期的异常 self.assertRaises(Exception, vi.create_interface, iface, address, netmask) self.mox.VerifyAll()
额外检查点
- 确认你的Mox版本是否兼容当前Python版本,部分旧版本Mox对模块内函数的mock支持有问题。
- 检查测试中是否有其他未被mock的函数调用(比如
logging.info),不过这个不会导致当前的错误,但如果需要完整测试可以考虑mock它。
备注:内容来源于stack exchange,提问作者aΨVaN

