如何在Python中Mock继承的对象引用属性
看起来你在测试B类的foo方法时,被继承链里的对象属性Mock难住了——没关系,我们一步步拆解问题,找到合适的Mock方式。
先分析你的调用链路:B继承自A,A的__init__里初始化了self.attr_a = C(),而B的foo方法会调用self.attr_a.session.get('model')。要让这个调用返回我们想要的测试值,我们可以在几个关键节点做Mock,下面给你几种可行的方案:
方案一:Mock C类的session属性
因为self.attr_a是C的实例,而session是C的@property,我们可以直接替换这个属性的返回值:
import unittest from b import B from unittest.mock import patch class TestB(unittest.TestCase): def setUp(self): self.b = B() @patch('c.C.session') def test_foo(self, mock_session): # 设置mock的session返回值,让get('model')拿到'TEST' mock_session.return_value = {'model': 'TEST'} found = self.b.foo() expected = 'test' self.assertEqual(found, expected) # 验证session属性被访问过一次 mock_session.assert_called_once()
解释:这里用@patch('c.C.session')替换了C类的session属性,当self.attr_a.session被访问时,会返回我们设置的{'model': 'TEST'},调用get后lower就得到了预期的'test'。
方案二:Mock A类中的attr_a实例
如果不想修改C类的行为,我们可以直接替换A类里初始化的attr_a对象,让它返回一个Mock实例:
import unittest from b import B from unittest.mock import patch, MagicMock class TestB(unittest.TestCase): def setUp(self): self.b = B() @patch('a.A.attr_a') def test_foo(self, mock_attr_a): # 创建一个mock的session对象,让get('model')返回'TEST' mock_session = MagicMock() mock_session.get.return_value = 'TEST' # 让mock的attr_a的session属性返回这个mock_session mock_attr_a.session = mock_session found = self.b.foo() expected = 'test' self.assertEqual(found, expected) # 验证get方法被正确调用 mock_session.get.assert_called_once_with('model', '')
解释:这里我们替换了A类的attr_a属性,让它变成一个Mock对象,然后给这个Mock对象的session属性绑定另一个Mock,指定get方法的返回值,这样foo里的调用就会走我们的Mock逻辑。
方案三:更细粒度Mock get方法
如果只想Mockget方法的行为,可以直接定位到session的get方法:
import unittest from b import B from unittest.mock import patch class TestB(unittest.TestCase): def setUp(self): self.b = B() @patch('c.C.session.get') def test_foo(self, mock_get): # 设置get方法返回'TEST' mock_get.return_value = 'TEST' found = self.b.foo() expected = 'test' self.assertEqual(found, expected) # 验证get被调用时的参数 mock_get.assert_called_once_with('model', '')
解释:这种方式直接Mock了session字典的get方法,不管session本身是什么,只要调用get('model', '')就会返回'TEST',适合只关注get方法行为的场景。
为什么你之前的代码没生效?
你之前的测试代码没有使用patch来替换真实的对象或方法,所以foo方法还是会访问真实的C实例的session,拿到的是真实的'modelName',lower后是'modelname',自然和预期的'test'不匹配。
另外要注意:Mock的路径很关键,必须Mock被测试代码实际访问的对象路径,比如如果B里的attr_a是从A的__init__里初始化的C实例,那么Mocka.A.attr_a是正确的;如果要MockC的session,就要Mockc.C.session,不能用错误的路径。
备注:内容来源于stack exchange,提问作者smark

