You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

测试使用loop.call_soon_threadsafe的Asyncio函数时断言失败求助

问题:asyncio.Protocol子类中使用call_soon_threadsafe的测试断言失败

我维护一个继承自asyncio.Protocol的类,负责通过套接字向硬件发送命令。因为这个类属于wxPython GUI模块,必须在非异步线程中调用方法——直接调用self._transport.write在Linux上正常,但在Windows上会崩溃,所以我改用了call_soon_threadsafe来封装调用:

def send_command(self, cmd):
    self.loop.call_soon_threadsafe(
        functools.partial(
            self._transport.write, str(cmd).encode() + b"\n"
        )
    )

为了测试这个方法,我写了如下pytest用例:

@pytest.mark.asyncio
async def test_send_command(self):
    self.sut._transport = Mock()
    self.sut.send_command("ook eek")
    assert self.sut._transport.write.called is True

但运行测试时总是得到断言错误:self.sut._transport.write从未被调用。如果我在send_command里直接调用self._transport.write(不用call_soon_threadsafe),测试就能通过,但这样代码在Windows上会崩溃。我到底忽略了什么?这应该不是很罕见的场景吧?


解决方案与分析

这个问题的核心在于**call_soon_threadsafe的工作机制**:它只是把任务提交给asyncio事件循环的待执行队列,并不会立刻执行。你的测试在调用send_command后马上断言,此时事件循环还没来得及处理这个提交的任务,所以Mock的write方法自然没有被调用。

解决步骤

1. 让事件循环处理待执行任务

在断言前,我们需要给事件循环一个机会去处理call_soon_threadsafe提交的任务。在pytest的async测试中,最简单的方式是await asyncio.sleep(0)——这会让出当前协程的控制权,让事件循环处理队列里的所有待完成任务。

修改后的测试用例:

import asyncio
import pytest

@pytest.mark.asyncio
async def test_send_command(self):
    self.sut._transport = Mock()
    self.sut.send_command("ook eek")
    # 让事件循环处理call_soon_threadsafe提交的任务
    await asyncio.sleep(0)
    assert self.sut._transport.write.called is True

2. 确保SUT使用测试的事件循环

另外要注意:如果你的SUT的loop属性指向的是另一个线程的事件循环(比如wxGUI的主线程循环),那测试用的事件循环可能无法处理提交的任务。这种情况下,需要在测试的setup阶段,把SUT的loop设置为测试当前的事件循环:

def setup_method(self):
    self.sut = YourProtocolSubclass()
    # 让SUT使用测试的事件循环
    self.sut.loop = asyncio.get_event_loop()

为什么直接调用write测试能通过?

当你直接调用self._transport.write时,这是一个同步调用——方法会立刻执行,所以断言的时候write已经被调用过了。而call_soon_threadsafe是异步提交任务,必须等事件循环调度执行后,write才会被调用,所以测试中必须等待事件循环处理完任务再断言。

内容的提问来源于stack exchange,提问作者Sardathrion - against SE abuse

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 18:39:04