API 测试:我如何 mock/patch 一个方法只在一个地方?
API testing: How do I mock/patch a method in one place only?
我正在尝试 patch/mock 在我的程序中的两个不同位置调用的方法 (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
中的 patched/mocked。我怎样才能确保是这种情况?
问题原因
my_file.py
中导入的classAsyncHTTPClient
其实只是引用tornado的原AsyncHTTPClient
class.
基本上,from x import y
语句是变量赋值,因为在当前文件中创建了一个名为 y
的新变量,引用原始对象 x.y
。
而且,由于 classes 是可变对象,当您在导入的 class 中修补 fetch
方法时,您实际上是在修补 fetch
方法原来的class.
下面是一个使用变量赋值的例子来说明这个问题:
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
方法时真正发生的事情。
解决方案
不要修补单个方法,而是修补 整个 class:
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
的值重新分配给模拟对象。这次没有变异,所以,原来的class不受影响。
我正在尝试 patch/mock 在我的程序中的两个不同位置调用的方法 (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
中的 patched/mocked。我怎样才能确保是这种情况?
问题原因
my_file.py
中导入的classAsyncHTTPClient
其实只是引用tornado的原AsyncHTTPClient
class.
基本上,from x import y
语句是变量赋值,因为在当前文件中创建了一个名为 y
的新变量,引用原始对象 x.y
。
而且,由于 classes 是可变对象,当您在导入的 class 中修补 fetch
方法时,您实际上是在修补 fetch
方法原来的class.
下面是一个使用变量赋值的例子来说明这个问题:
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
方法时真正发生的事情。
解决方案
不要修补单个方法,而是修补 整个 class:
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
的值重新分配给模拟对象。这次没有变异,所以,原来的class不受影响。