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的原AsyncHTTPClientclass.

基本上,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不受影响。