Harness 详解
约 945 字大约 3 分钟
2026-03-19
TestHarness 与 PluginTestHarness 的完整使用指南
目录
1. TestHarness 生命周期
TestHarness 在后台启动一个完整的 BotClient(使用 MockAdapter),无需连接 NapCat。
async with(推荐)
async with TestHarness() as h:
# h.bot, h.adapter, h.mock_api, h.dispatcher 可用
await h.inject(group_message("hi"))
await h.settle()
# 自动 stop手动管理
h = TestHarness()
await h.start() # 启动 BotClient
try:
# ... 测试逻辑 ...
finally:
await h.stop() # 停止 BotClient内部做了什么
start()→ 调用BotClient.run_async(),启动 MockAdapter + Dispatcher + HandlerDispatcherstop()→ 调用BotClient.shutdown(),停止所有后台任务- MockAdapter 替代了真实的 NapCat 连接,所有 API 调用被
MockBotAPI记录
2. 事件注入
注入单个事件
await h.inject(group_message("hello"))注入多个事件
await h.inject_many([
group_message("a"),
private_message("b"),
group_message("c"),
])settle — 等待处理
settle() 给 handler 一点时间执行(默认 50ms):
await h.settle() # 默认 0.05 秒
await h.settle(0.2) # 复杂 handler 可增大何时增大 settle? handler 中有
asyncio.sleep()、self.wait_event()或多步对话时。
wait_event — 等待特定事件
event = await h.wait_event(
predicate=lambda e: e.type == "message.group",
timeout=2.0,
)3. API 断言
所有 API 调用都被 MockBotAPI 记录为 APICall(action, args, kwargs)。
基础断言
# 检查是否被调用
assert h.api_called("send_group_msg")
# 检查调用次数
assert h.api_call_count("send_group_msg") == 1
# 检查未被调用
assert not h.api_called("set_group_kick")检查调用参数
# 获取最近一次调用
call = h.last_api_call("send_group_msg")
print(call.action) # "send_group_msg"
print(call.args) # (group_id, message)
print(call.kwargs) # 额外关键字参数
# 获取所有调用
calls = h.get_api_calls("send_group_msg")
for c in calls:
print(c.args, c.kwargs)重置调用记录
多步测试中,用 reset_api() 隔离每步的断言:
await h.inject(group_message("step1"))
await h.settle()
assert h.api_called("send_group_msg")
h.reset_api() # 清空记录
await h.inject(group_message("step2"))
await h.settle()
assert h.api_call_count("send_group_msg") == 1 # 只计 step24. Mock 响应配置
如果 handler 依赖 API 返回值,可预配置 Mock 响应:
# 配置 get_group_member_info 的返回值
h.mock_api.set_response("get_group_member_info", {
"user_id": "99",
"nickname": "测试用户",
"role": "member",
})
# handler 中 await self.api.get_group_member_info(...) 会收到上面的 dict未配置的 API 调用返回空 {}。
5. PluginTestHarness
PluginTestHarness 继承 TestHarness,增加了插件选择性加载和查询能力。
构造参数
async with PluginTestHarness(
plugin_names=["hello_world"], # 要加载的插件名
plugin_dir=Path("examples/qq/01_hello_world"), # 插件根目录
skip_builtin=True, # 不加载内置插件(默认)
skip_pip=True, # 不安装 pip 依赖(默认)
) as h:
...plugin_dir 是包含插件文件夹的父目录。例如插件在
examples/qq/01_hello_world/hello_world/下,则plugin_dir应为examples/qq/01_hello_world。
查询已加载的插件
# 列出所有已加载的插件名
print(h.loaded_plugins) # ["hello_world"]
# 获取插件实例
plugin = h.get_plugin("hello_world")
# 获取插件配置/数据
config = h.plugin_config("hello_world")
data = h.plugin_data("hello_world")热重载
success = await h.reload_plugin("hello_world")
assert success传递依赖
如果目标插件在 manifest.toml 中声明了 [dependencies],PluginTestHarness 会自动解析并加载传递依赖。
6. 对比表
| 能力 | TestHarness | PluginTestHarness |
|---|---|---|
| 事件注入 | ✓ | ✓ |
| API 断言 | ✓ | ✓ |
| Mock 响应 | ✓ | ✓ |
| 选择性加载插件 | ✗ | ✓ |
| 插件状态查询 | ✗ | ✓ |
| 热重载 | ✗ | ✓ |
| skip_builtin / skip_pip | — | ✓ |
插件开发者请始终使用
PluginTestHarness。TestHarness主要用于框架内部测试。
7. 常见模式与陷阱
✅ 多步对话测试
async with PluginTestHarness(...) as h:
await h.inject(group_message("注册", group_id="100", user_id="99"))
await h.settle(0.1)
assert h.api_called("send_group_msg")
h.reset_api() # 关键:隔离每步断言
await h.inject(group_message("张三", group_id="100", user_id="99"))
await h.settle(0.1)
assert h.api_called("send_group_msg")⚠️ settle 时间不足
默认 settle(0.05) 对简单 handler 足够。如果断言失败,先尝试增大 settle:
await h.settle(0.2) # 复杂 handler
await h.settle(0.5) # 含 wait_event 的多步对话⚠️ plugin_dir 路径错误
# ✗ 错误:指向插件本身
PluginTestHarness(plugin_names=["hello"], plugin_dir=Path("plugins/hello/"))
# ✓ 正确:指向插件的父目录
PluginTestHarness(plugin_names=["hello"], plugin_dir=Path("plugins/"))⚠️ 同一测试中测试多个命令
每个命令测试前用 reset_api() 清空记录,避免断言受之前调用的干扰。
版权所有
版权归属:huan-yp
