API测试:如何在一个地方模拟/修补方法?



我正在尝试修补/模拟在我的程序中的两个不同地方调用的方法(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的值重新分配给模拟对象。这次没有发生突变,因此,原始类不受影响。

最新更新