Skip to main content
 首页 » 编程设计

python之如何在单元测试结束前取消我的异步任务

2025年05月04日20yxwkf

我正在尝试对异步套接字服务器进行单元测试,并使用 pytest-asyncio 使 pytest 与异步代码库兼容。服务器一旦启动,就会始终通过 while 循环发送回复,并且可能大部分时间都在等待 client_loop() 中的传入消息。问题是在单元测试框架终止事件循环并发出此警告之前无法取消此任务:

Task was destroyed but it is pending!

task: < Task pending coro=< Server.new_client() done, defined at /[...path...]/server.py:16> wait_for=< Future pending cb=[< TaskWakeupMethWrapper object at 0x106d7cbe8>()]>>

看来我可以访问的唯一任务是由 asyncio.create_task() 创建的任务,这似乎不是同一个任务。该任务看起来像这样:

task: < Task pending coro=< start_server() running at /usr/local/Cellar/python/[...different path...]/streams.py:86>>

所以调用task.cancel(); await task.wait_cancelled() 对此任务无效。

如何编写此单元测试以干净地启动并为每个测试启动服务器而不中断可能仍在运行的任务?

例子如下:

测试服务器.py

import pytest 
import asyncio 
 
@pytest.fixture 
async def server(event_loop): 
    from server import Server 
    the_server = Server() 
    await the_server.start() 
    yield the_server 
    the_server.stop() 
 
@pytest.mark.asyncio 
async def test_connect(server): 
    loop = asyncio.get_event_loop() 
    reader, writer = await asyncio.open_connection('0.0.0.0', 8888, loop = loop) 
    writer.write(b'something') 
    await reader.read(100) 
    writer.write(b'something else') 
    await reader.read(100) 
    assert 1 

服务器.py

import asyncio 
 
class Server(): 
    async def start(self): 
        loop = asyncio.get_event_loop() 
        coro = asyncio.start_server(self.new_client, '0.0.0.0', 8888, loop = loop) 
        task = loop.create_task(coro) 
        print('\n') 
        print(task) 
        self.server = await task 
 
    def stop(self): 
        self.server.close() 
 
    async def new_client(self, reader, writer): 
        await self.client_loop(reader, writer) 
 
    async def client_loop(self, reader, writer): 
        while True: 
            await reader.read(100) 
            writer.write(b'reply') 

如果你想运行这个例子,只需运行 pip3 install pytest-asyncio pytest 就可以选择这个插件。

请您参考如下方法:

在调用 self.server.close() 之后,您必须 await self.server.wait_closed()。

因此,您的 fixture 应如下所示:

@pytest.fixture 
async def server(event_loop): 
    from server import Server 
    the_server = Server() 
    await the_server.start() 
    yield the_server 
    await the_server.stop() 

Server 的stop 方法应该如下所示:

    async def stop(self): 
        self.server.close() 
        await self.server.wait_closed() 

参见 the documentation了解详情。