---
sidebar_position: 1
description: 使用 NoneBug 测试机器人
slug: /advanced/unittest/

options:
  menu:
    weight: 80
    category: advanced
---

import CodeBlock from "@theme/CodeBlock";

# 单元测试

[单元测试](https://zh.wikipedia.org/wiki/%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95)

> 在计算机编程中，单元测试（Unit Testing）又称为模块测试，是针对程序模块（软件设计的最小单位）来进行正确性检验的测试工作。

NoneBot2 使用 [Pytest](https://docs.pytest.org) 单元测试框架搭配 [NoneBug](https://github.com/nonebot/nonebug) 插件进行单元测试，通过直接与事件响应器/适配器等交互简化测试流程，更易于编写。

## 安装 NoneBug

安装 NoneBug 时，Pytest 会作为依赖被一起安装。

要运行 NoneBug，还需要额外安装 Pytest 异步插件 `pytest-asyncio` 或 `anyio`，文档将以 `pytest-asyncio` 为例。

```bash
poetry add nonebug pytest-asyncio --dev
# 也可以通过 pip 安装
pip install nonebug pytest-asyncio
```

:::tip 提示
建议首先阅读 [Pytest 文档](https://docs.pytest.org) 理解相关术语。
:::

## 加载插件

我们可以使用 Pytest **Fixtures** 来加载插件，下面是一个示例：

```python title=conftest.py
from pathlib import Path
from typing import TYPE_CHECKING, Set

import pytest

if TYPE_CHECKING:
    from nonebot.plugin import Plugin


@pytest.fixture
def load_plugins(nonebug_init: None) -> Set["Plugin"]:
    import nonebot  # 这里的导入必须在函数内

    # 加载插件
    return nonebot.load_plugins("awesome_bot/plugins")
```

此 Fixture 的 [`nonebug_init`](https://github.com/nonebot/nonebug/blob/master/nonebug/fixture.py) 形参也是一个 Fixture，用于初始化 NoneBug。

Fixture 名称 `load_plugins` 可以修改为其他名称，文档以 `load_plugins` 为例。需要加载插件时，在测试函数添加形参 `load_plugins` 即可。加载完成后即可使用 `import` 导入事件响应器。

## 测试流程

Pytest 会在函数开始前通过 Fixture `app`(nonebug_app) **初始化 NoneBug** 并返回 `App` 对象。

:::warning 警告
所有从 `nonebot` 导入模块的函数都需要首先初始化 NoneBug App，否则会发生不可预料的问题。

在每个测试函数结束时，NoneBug 会自动销毁所有与 NoneBot 相关的资源。所有与 NoneBot 相关的 import 应在函数内进行导入。
:::

随后使用 `test_matcher` 等测试方法获取到 `Context` 上下文，通过上下文管理提供的方法（如 `should_call_send` 等）预定义行为。

在上下文管理器关闭时，`Context` 会调用 `run_test` 方法按照预定义行为对事件响应器进行断言（如：断言事件响应和 API 调用等）。

## 测试样例

:::tip 提示
将从 `utils` 导入的 `make_fake_message`，`make_fake_event` 替换为对应平台的消息/事件类型。

将 `load_example` 替换为加载插件的 Fixture 名称。
:::

使用 NoneBug 的 `test_matcher` 可以模拟出一个事件流程。如下是一个简单的示例：

import WeatherSource from "!!raw-loader!@site/../tests/examples/weather.py";
import WeatherTest from "!!raw-loader!@site/../tests/test_examples/test_weather.py";

<CodeBlock className="language-python" title="test_weather.py">
  {WeatherTest}
</CodeBlock>

<details>
  <summary>示例插件</summary>
  <CodeBlock className="language-python" title="examples/weather.py">
    {WeatherSource}
  </CodeBlock>
</details>

在测试用例编写完成后 ，可以使用下面的命令运行单元测试。

```bash
pytest test_weather.py
```
