我正在尝试修补/模拟在我的程序中的两个不同地方调用的方法(AsyncHTTPClient.fetch
):首先在tornado_openapi3.testing
中,其次在my_file
中。问题是该方法在第一个位置被打了补丁,这破坏了我的测试的功能。
my_file.py:
import tornado
class Handler(tornado.web.RequestHandler, ABC):
def initialize(self):
<some_code>
async def get(self, id):
<some_code>
client = AsyncHTTPClient()
response = await client.fetch(<some_path>)
<some_code>
test_handler.py:
from tornado_openapi3.testing import AsyncOpenAPITestCase
class HandlerTestCase(AsyncOpenAPITestCase):
def get_app(self) -> Application:
return <some_app>
def test_my_handler(self):
with patch.object(my_file.AsyncHTTPClient, 'fetch') as mock_fetch:
f = asyncio.Future()
f.set_result('some_result_for_testing')
mock_fetch.return_value = f
self.fetch(<some_path>)
从我从各种模拟教程(例如https://docs.python.org/3/library/unittest.mock.html)中了解到,fetch
应该只在my_file
中修补/模拟。我怎样才能确定是这样呢?
问题原因
my_file.py
中导入的AsyncHTTPClient
类实际上只是对tornado的原始AsyncHTTPClient
类的引用。
基本上,from x import y
语句是变量赋值,在当前文件中创建一个名为y
的新变量,引用原始对象x.y
。
并且,由于类是可变对象,当您在导入的类中修补fetch
方法时,您实际上是在原始类上修补fetch
方法。
下面是一个使用变量赋值的例子来说明这个问题:
class A:
x = 1
b = A # create a variable 'b' referencing the class 'A'
b.x = 2 # change the value of 'x' attribute' of 'b'
print(A.x)
# Outputs -> 2 (not 1 because classes are mutable)
就像我之前说的,from ... import ...
语句基本上是变量赋值。因此,上面的插图是当您修补fetch
方法时实际发生的情况。
解决方案不修补单个方法,而是修补整个类:
with patch.object(my_file, 'AsyncHTTPClient') as mock_client:
f = asyncio.Future()
f.set_result('some_result_for_testing')
mock_client.fetch.return_value = f
self.fetch(<some_path>)
这次发生的事情是Python将局部变量AsyncHTTPClient
的值重新分配给模拟对象。这次没有发生突变,因此,原始类不受影响。