我试图模拟一个对象,该对象在初始化时会进行一些昂贵的网络调用,但实例化它的父类无法识别单元测试中的补丁。
我已经看过一些类似的问题:
- 1在我目前的情况下不涉及任何继承
- 2具有类似的继承结构,但它模拟的是一个方法而不是构造函数
最小可再现性示例3
文件结构
--src
|-- resource.py
|-- family.py
|-- test_child.py
resource.py
class Resource:
def __init__(self):
print("Expensive network call")
def use_resource(self) -> str:
print("Another expensive network call")
return "bar"
家族.py
from resource import Resource
class Parent:
def __init__(self):
print("In the Parent constructor.")
self.resource = Resource()
def foo(self):
print("Parent's foo")
print(self.resource.use_resource())
class Child(Parent):
def __init__(self):
super().__init__()
print("In the Child constructor")
def foo(self):
print("Child's foo")
super().foo()
test_child.py
import unittest
from unittest.mock import patch
from family import Child
class TestChild(unittest.TestCase):
@patch('resource.Resource')
def test_foo(self, mock_resource):
mock_resource.return_value.use_resource.return_value = "mock_bar"
child = Child()
child.foo()
mock_resource.return_value.use_resource.assert_called_once()
unittest.main()
预期结果
由于我正在修补resource.Resource
,所以我希望避免在Parent
实例化Resource
时通常发生的昂贵的网络调用。理论上,运行test_child.py
的输出应该是这样的:
In the Parent constructor.
In the Child constructor
Child's foo
Parent's foo
mock_bar
实际结果
然而,尽管在测试中修补了resource.Resource
,但Parent
仍在实例化实际的Resource
,如";昂贵的网络呼叫";输出中的消息和失败的CCD_ 8断言。
In the Parent constructor.
Expensive network call # Should have been patched
In the Child constructor
Child's foo
Parent's foo
Another expensive network call # Should have been patched
bar # Should have been patched
F
======================================================================
FAIL: test_foo (__main__.TestChild)
----------------------------------------------------------------------
Traceback (most recent call last):
File "[REMOVED]unittestmock.py", line 1342, in patched
return func(*newargs, **newkeywargs)
File "[REMOVED]test_child.py", line 13, in test_foo
mock_resource.return_value.use_resource.assert_called_once()
File "[REMOVED]unittestmock.py", line 886, in assert_called_once
raise AssertionError(msg)
AssertionError: Expected 'use_resource' to have been called once. Called 0 times.
----------------------------------------------------------------------
Ran 1 test in 0.005s
为了使Parent
的Resource
实例化使用模拟的Resource
而不是实际的Resource
,我需要更改什么
使用@patch(family.Resource)
而不是@patch(resource.Resource)
有两个关键的想法可以解释你的补丁失败的原因:
- 当
Parent
类实例化Resource
时,它不是直接实例化resource.Resource
,而是实例化family
模块的Resource
的导入 - 虽然您的
family
模块确实导入了resource.Resource
(您确实正在对其进行修补(,但该导入发生在修补程序的之前,因为您在执行修补程序之前导入family
模块
如果需要更多详细信息,请参阅unittest.mock文档。Medium的这篇文章也非常有助于理解Python中嘲讽的怪异之处。
演示
为了完整起见,这是您的test_child.py
和更正。。。
import unittest
from unittest.mock import patch
from family import Child
class TestChild(unittest.TestCase):
@patch('family.Resource') # Changed this line
def test_foo(self, mock_resource):
mock_resource.return_value.use_resource.return_value = "mock_bar"
# Alternatively, move `from family import Child` here.
child = Child()
child.foo()
mock_resource.return_value.use_resource.assert_called_once()
unittest.main()
并且测试输出与问题中的预期输出相匹配。
In the Parent constructor.
In the Child constructor
Child's foo
Parent's foo
mock_bar
.
----------------------------------------------------------------------
Ran 1 test in 0.004s
OK