☰
NoneBot2 模拟网络通信测试实战:用 nonebug 覆盖 HTTP 与 WebSocket 服务端集成测试
2026/9/28 3:03:47 网站建设 项目流程
  • 后端
  • 即时通讯

【免费下载链接】nonebot2

跨平台 Python 异步聊天机器人框架 / Asynchronous multi-platform chatbot framework written in Python

项目地址:https://gitcode.com/gh_mirrors/no/nonebot2
点击查看免费下载

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"))

这个测试用例覆盖了几个关键环节:

  1. 获取适配器实例:nonebot.get_adapter(Adapter)从驱动已注册的适配器字典中取出fake适配器(实现见 nonebot/init.py),后续清理 Bot 时要用到它。
  2. 模拟 WebHook 推送:client.post("/fake/http", json={"bot_id": "fake"})向适配器注册的路由发送 POST 请求,请求体为 JSON。适配器处理请求后返回{"status": "success"}。
  3. 验证处理结果:依次断言 HTTP 状态码、响应 JSON 内容,以及 Bot 是否被正确注册进nonebot.get_bots()——这是"集成测试"的核心:不仅验证路由可达,还验证适配器确实依据事件创建并注册了 Bot 对象。
  4. 清理 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

项目地址:https://gitcode.com/gh_mirrors/no/nonebot2
点击查看免费下载

相关推荐

上一篇:告别手动扫码!米哈游游戏全自动登录神器使用指南
下一篇:Outlook日历迁移到Linux:DavMail + Lightning插件无缝同步方案

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询