- 后端
- 即时通讯
【免费下载链接】nonebot2
跨平台 Python 异步聊天机器人框架 / Asynchronous multi-platform chatbot framework written in Python
NoneBot2 的驱动器为适配器提供了 HTTP 服务端、WebSocket 服务端以及 HTTP/WebSocket 客户端等多种网络通信能力。本文基于 模拟网络通信文档,讲解如何借助 nonebug 测试框架在本地模拟真实的网络通信,对机器人应用进行接近实战的集成测试,同时验证适配器是否正确注册路由、创建 Bot 连接与处理消息。读完本文,你将掌握app.test_server()模拟客户端的完整用法,能独立编写针对 HTTP WebHook 与 WebSocket 长连接的测试用例。
模拟网络通信的动机与通信类型
NoneBot 驱动器(Driver)封装了底层网络框架,为适配器提供多种通信原语。从 nonebot/internal/driver/abstract.py 可以看到,驱动器体系以Driver基类为核心,并通过混入(Mixin)扩展能力:ReverseMixin代表服务端能力(abstract.py),ForwardMixin代表客户端能力(abstract.py);其中ASGIMixin是服务端实现基类,负责 HTTP 与 WebSocket 路由的注册(abstract.py),HTTPClientMixin与WebSocketClientMixin则分别提供客户端请求能力(abstract.py)。
NoneBot 中的网络通信主要包括以下四种:
- HTTP 服务端(WebHook):平台通过 POST 等请求把事件推送给机器人;
- WebSocket 服务端:平台与机器人建立 WebSocket 长连接并推送事件;
- HTTP 客户端:机器人主动向外部服务发起 HTTP 请求;
- WebSocket 客户端:机器人主动建立 WebSocket 连接。
模拟网络通信的意义在于:它不需要真实接入平台,就能让测试环境尽量贴近实际机器人运行场景,进行更真实的集成测试;同时,通过向路由注入请求、检查 Bot 的注册与事件处理结果,我们还可以完成对适配器本身的测试。
测试环境准备
模拟网络通信测试依赖 pytest 与 nonebug。nonebug 是一个独立的测试工具库,通过App对象提供test_server()等上下文管理器,配合 pytest 的异步插件使用。
在 tests/conftest.py 中可以看到,NoneBot 仓库自身的测试同时覆盖了asyncio与trio两个异步后端:
@pytest.fixture(scope="session", params=[pytest.param("asyncio"), pytest.param("trio")]) def anyio_backend(request: pytest.FixtureRequest): return request.param因此编写测试用例时建议同样使用@pytest.mark.asyncio(或 anyio 标记)并注入app: App参数,nonebug 会负责初始化 NoneBot 环境。此外,仓库在 tests/conftest.py 中提供了一个基于 Werkzeug 的独立回显服务器(server/server_urlfixture),其实现见 tests/fake_server.py,可作为验证驱动客户端能力的外部服务,这一点我们会在后文客户端部分再次提及。
测试 HTTP 服务端(WebHook)
当 NoneBot 作为 ASGI 服务端应用运行时,可以定义一系列路由来处理 HTTP 请求,适配器同样通过定义路由来响应机器人平台的 WebHook 推送。假设我们使用了一个适配器fake,它注册了路由/fake/http用于接收平台 WebHook 并处理。实际测试时,应将/fake/http替换为真实适配器注册的路由地址。
第一步:获取测试用模拟客户端
from nonebug import App @pytest.mark.asyncio async def test_http_server(app: App): async with app.test_server() as ctx: client = ctx.get_client()默认情况下,app.test_server()会通过nonebot.get_asgi()获取测试对象;也可以通过参数显式指定 ASGI 应用:
async with app.test_server(asgi=asgi_app) as ctx: ...这里nonebot.get_asgi()对应全局驱动ASGIMixin.asgi属性(见 nonebot/init.py)。在 FastAPI 驱动中,asgi与server_app都是同一个 FastAPI 应用实例(nonebot/drivers/fastapi.py),这意味着测试客户端直接面向真实注册了适配器路由的 ASGI 应用发起请求。
第二步:发起模拟 POST 请求并验证
获取到模拟客户端后,即可像使用requests、httpx等库一样调用它:
import nonebot from nonebug import App from nonebot.adapters.fake import Adapter @pytest.mark.asyncio async def test_http_server(app: App): adapter = nonebot.get_adapter(Adapter) async with app.test_server() as ctx: client = ctx.get_client() response = await client.post("/fake/http", json={"bot_id": "fake"}) assert response.status_code == 200 assert response.json() == {"status": "success"} assert "fake" in nonebot.get_bots() adapter.bot_disconnect(nonebot.get_bot("fake"))这个测试用例覆盖了几个关键环节:
- 获取适配器实例:
nonebot.get_adapter(Adapter)从驱动已注册的适配器字典中取出fake适配器(实现见 nonebot/init.py),后续清理 Bot 时要用到它。 - 模拟 WebHook 推送:
client.post("/fake/http", json={"bot_id": "fake"})向适配器注册的路由发送 POST 请求,请求体为 JSON。适配器处理请求后返回{"status": "success"}。 - 验证处理结果:依次断言 HTTP 状态码、响应 JSON 内容,以及 Bot 是否被正确注册进
nonebot.get_bots()——这是"集成测试"的核心:不仅验证路由可达,还验证适配器确实依据事件创建并注册了 Bot 对象。 - 清理 Bot:测试结束后调用
adapter.bot_disconnect(...)注销 Bot,避免残留连接污染其他测试用例。
服务端路由的底层实现
理解路由如何被注册,有助于定位"为什么请求没被处理"之类的问题。适配器通过setup_http_server把路由配置交给驱动器(nonebot/internal/adapter/adapter.py),路由配置HTTPServerSetup包含四个字段(nonebot/internal/driver/model.py):
path:路由路径(不应是绝对 URL);method:HTTP 方法,如POST、GET;name:路由名称;handle_func:处理函数,签名(Request) -> Awaitable[Response]。
在 FastAPI 驱动中,setup_http_server最终调用add_api_route把处理函数注册到 FastAPI 应用上,并可通过配置fastapi_include_adapter_schema控制是否将适配器路由纳入 OpenAPI 文档(nonebot/drivers/fastapi.py)。因此,模拟客户端请求的目标,本质上是驱动server_app中那张包含适配器路由的完整路由表。
测试 WebSocket 服务端
当 NoneBot 作为 ASGI 服务端应用时,也可以定义路由处理 WebSocket 通信。假设适配器fake注册了路由/fake/ws处理平台的 WebSocket 连接。同样需要通过app.test_server()获取模拟客户端,之后使用client.websocket_connect建立模拟连接:
import nonebot from nonebug import App from nonebot.adapters.fake import Adapter @pytest.mark.asyncio async def test_ws_server(app: App): adapter = nonebot.get_adapter(Adapter) async with app.test_server() as ctx: client = ctx.get_client() async with client.websocket_connect("/fake/ws") as ws: await ws.send_json({"bot_id": "fake"}) response = await ws.receive_json() assert response == {"status": "success"} assert "fake" in nonebot.get_bots()这个用例模拟了一次完整的 WebSocket 交互:客户端连接/fake/ws后,ws.send_json发送 JSON 消息触发适配器逻辑,ws.receive_json接收适配器的响应,最后断言响应内容与 Bot 注册状态。相比 HTTP 测试,WebSocket 测试更接近"长连接 + 双向消息"的真实场景。
WebSocket 服务端路由的注册与 HTTP 类似:适配器调用setup_websocket_server(nonebot/internal/adapter/adapter.py)提交WebSocketServerSetup配置——包含path、name与处理函数(WebSocket) -> Awaitable[Any](nonebot/internal/driver/model.py)。FastAPI 驱动将其封装为add_api_websocket_route(nonebot/drivers/fastapi.py)。驱动层抽象的WebSocket对象(nonebot/internal/driver/model.py)提供了accept、close、receive、send以及send_text/send_bytes等收发原语,适配器在此基础上实现事件分发与回复。
HTTP 客户端与 WebSocket 客户端:当前测试现状
原文档在"测试 HTTP 客户端"与"测试 WebSocket 客户端"两节标注了"暂不支持"。这意味着截至该文档对应的版本,nonebug 的test_server()模拟客户端仅面向服务端(ASGI)方向,尚未提供对适配器主动发起客户端请求的模拟能力。
不过,客户端能力本身在框架中是完整存在的:驱动通过HTTPClientMixin提供request、stream_request与get_session(nonebot/internal/driver/abstract.py),通过WebSocketClientMixin提供websocket连接(abstract.py),适配器层也封装了对应的request与websocket方法(nonebot/internal/adapter/adapter.py)。
从仓库自身的测试代码看,对驱动客户端请求能力的验证,是通过连接一个真实可用的回显服务器完成的:tests/conftest.py中serverfixture 在会话期间启动一个 Werkzeug WSGI 服务器(tests/conftest.py),server_url导出其访问地址(tests/conftest.py);该服务器的处理逻辑在 tests/fake_server.py 中实现,http_echo(fake_server.py)回显请求的 URL、方法、请求头、表单与 JSON 等字段,websocket_echo(fake_server.py)则对收到的文本/二进制消息原样返回。这种"外部回显服务"的方式与 nonebug 的服务端模拟是互补的:前者验证驱动主动发起的请求,后者验证适配器被动接收的推送。如果你的适配器需要测试客户端方向,可以参考这一模式自行搭建回显服务。
测试编写要点与注意事项
结合文档示例与仓库实现,编写模拟网络通信测试时建议注意以下几点:
- 路由地址必须替换为真实值:文档示例中的
/fake/http、/fake/ws只是占位符,实际测试要使用目标适配器通过setup_http_server/setup_websocket_server注册的真实路径(可查阅适配器源码确认)。 - 优先断言 Bot 注册状态:
assert "fake" in nonebot.get_bots()这类断言能同时验证"请求已到达适配器"和"事件被正确转换为 Bot 连接"两层逻辑,比只断言 HTTP 状态码更有价值。get_bots返回的是驱动中以self_id为键的 Bot 字典(nonebot/init.py)。 - 测试后务必清理 Bot:
adapter.bot_disconnect(...)会触发驱动层的_bot_disconnect,将 Bot 从驱动字典注销并执行断开钩子(nonebot/internal/driver/abstract.py)。不做清理的话,前一个用例注册的 Bot 会残留在全局状态中,影响后续用例的断言结果。 - 区分服务端与客户端能力:
app.test_server()仅适用于基于ASGIMixin的服务端驱动(如 FastAPI、Quart、aiohttp)。如果测试中遇到 "app object is only available for asgi driver" 之类的断言失败,说明当前驱动不具备 ASGI 服务端能力,需要检查.env中的DRIVER配置。 - 关注异步后端差异:若使用 anyio 风格编写用例,建议同时覆盖
asyncio与trio后端(参见仓库 tests/conftest.py),以发现不同事件循环下的兼容性问题。
小结
模拟网络通信是 NoneBot2 集成测试中最贴近实战的一环。通过 nonebug 的app.test_server(),我们可以不连接真实平台,就能对 HTTP WebHook 与 WebSocket 长连接两条服务端通路进行端到端验证:模拟请求注入 → 适配器处理 → Bot 注册 → 响应断言 → 连接清理。结合驱动器ASGIMixin的路由注册机制(nonebot/internal/driver/abstract.py)与 FastAPI 等驱动的具体实现(nonebot/drivers/fastapi.py),读者既可以快速上手编写测试,也能在遇到路由、连接或清理问题时追查到底层原因。至于 HTTP/WebSocket 客户端方向的模拟,当前仍需借助自建回显服务等手段实现,可作为后续测试能力扩展的关注点。
- 后端
- 即时通讯
【免费下载链接】nonebot2
跨平台 Python 异步聊天机器人框架 / Asynchronous multi-platform chatbot framework written in Python
相关推荐
NoneBot2 模拟网络通信测试指南:使用 NoneBug 测试 HTTP 与 WebSocket 服务端
NoneBot2 模拟网络通信测试指南:使用 NoneBug 测试 HTTP 与 WebSocket 服务端 本篇指南聚焦 NoneBot2 中的网络通信测试场
后端即时通讯jQuery countTo vs 原生JS:为什么这款插件仍是数字动画的首选方案
jQuery countTo vs 原生JS:为什么这款插件仍是数字动画的首选方案 在数字动画效果领域,jQuery countTo插件以其卓越的易用性和流畅的
UI库/组件Polkadot SDK测试策略:单元测试、集成测试和网络测试全覆盖
Polkadot SDK作为构建区块链应用的核心开发工具包,采用了全面的测试策略确保系统稳定性和可靠性。本文将深入解析Polkadot SDK的测试架构,涵盖单
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考