使用mock.patch.object时为何能修改未被补丁的属性返回值
你代码里的核心误区在先:@mock.patch.object(PhoneBook, "add")注入到测试函数的mock_phonebook参数,本质是被补丁替换的add方法的Mock对象,根本不是PhoneBook类或其实例的Mock,你把它传给Mobile的构造是完全错误的用法,后续的异常表现都是这个错误+Mock的默认特性叠加的结果。
1 上述表现是否是mock.patch.object的预期行为?
是,属于两个设计特性的正常表现:
unittest.mock.Mock默认开启自动创建子属性的特性:只要访问Mock对象不存在的属性/方法,会自动生成新的子Mock对象返回,不会抛出属性不存在的异常。所以你可以给这个add方法的Mock对象随意设置lookup的返回值、做调用断言,都是Mock的正常行为。- patch.object只负责替换你指定的属性,不会限制注入的Mock对象的属性访问逻辑。
2 为什么没用到指定的add属性也需要传入?为什么不校验补丁属性的实际使用情况?
- 你传入
add是告诉patch.object你要替换PhoneBook类下的add属性,不管测试过程中用不用,patch都会执行「替换-测试结束后还原」的流程,这个参数是patch执行替换的必填目标,和你后续用不用没关系。 - patch本身只负责替换和还原的生命周期,不会校验你有没有用到被替换的属性,这是设计如此:很多场景下补丁的作用只是屏蔽原方法的副作用,不需要主动调用或者断言,自然不需要做使用校验。
- 只有传入不存在的属性会报错,是因为patch执行替换前需要先定位到要替换的目标,找不到目标自然无法执行替换操作,属于替换动作本身的前置校验,和后续使用无关。
- 补充:你当前的测试逻辑完全没有用到
add方法,这个patch的写法本身就是多余的,完全可以删掉。
3 是否错误理解了patch装饰器的使用场景?
是的,你对patch的作用、注入参数的含义都有偏差:
- patch系列方法的核心作用是:在测试的生命周期内,把你指定位置的目标替换成Mock对象,测试结束后自动还原。你需要替换什么目标就传什么,不需要替换的目标不要随意加patch。
- 你这个测试场景根本不需要用patch:你只是要给Mobile传入一个模拟的PhoneBook依赖,直接手动创建Mock对象即可,正确写法如下:
from unittest.mock import Mock import logging class PhoneBook: def __init__(self): self.numbers: dict[str, str] = {} def add(self, name: str, number: str) -> None: self.numbers[name] = number def lookup(self, name: str) -> str: return self.numbers[name] class Mobile: def __init__(self, phonebook: PhoneBook): self.phonebook = phonebook def call(self, name: str) -> None: number = self.phonebook.lookup(name) logging.info(f"Calling {name} on number: {number}") def test_mobile_call(): # Arrange mock_phonebook = Mock() mock_phonebook.lookup.return_value = "123" mobile = Mobile(mock_phonebook) # Act mobile.call("Nick") # Assert mock_phonebook.lookup.assert_called_with("Nick")
只有当你要替换的目标是代码其他位置隐式导入/实例化的对象,无法手动传入Mock的时候,才需要用到patch。
内容的提问来源于stack exchange,提问作者broodjetom
相关产品推荐
相关产品推荐

