Browse Source
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>pull/9720/head
committed by
GitHub
2 changed files with 213 additions and 0 deletions
@ -0,0 +1,212 @@ |
|||||
|
# 测试 |
||||
|
|
||||
|
感谢 <a href="https://www.starlette.io/testclient/" class="external-link" target="_blank">Starlette</a>,测试**FastAPI** 应用轻松又愉快。 |
||||
|
|
||||
|
它基于 <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a>, 而HTTPX又是基于Requests设计的,所以很相似且易懂。 |
||||
|
|
||||
|
有了它,你可以直接与**FastAPI**一起使用 <a href="https://docs.pytest.org/" class="external-link" target="_blank">pytest</a>。 |
||||
|
|
||||
|
## 使用 `TestClient` |
||||
|
|
||||
|
!!! 信息 |
||||
|
要使用 `TestClient`,先要安装 <a href="https://www.python-httpx.org" class="external-link" target="_blank">`httpx`</a>. |
||||
|
|
||||
|
例:`pip install httpx`. |
||||
|
|
||||
|
导入 `TestClient`. |
||||
|
|
||||
|
通过传入你的**FastAPI**应用创建一个 `TestClient` 。 |
||||
|
|
||||
|
创建名字以 `test_` 开头的函数(这是标准的 `pytest` 约定)。 |
||||
|
|
||||
|
像使用 `httpx` 那样使用 `TestClient` 对象。 |
||||
|
|
||||
|
为你需要检查的地方用标准的Python表达式写个简单的 `assert` 语句(重申,标准的`pytest`)。 |
||||
|
|
||||
|
```Python hl_lines="2 12 15-18" |
||||
|
{!../../../docs_src/app_testing/tutorial001.py!} |
||||
|
``` |
||||
|
|
||||
|
!!! 提示 |
||||
|
注意测试函数是普通的 `def`,不是 `async def`。 |
||||
|
|
||||
|
还有client的调用也是普通的调用,不是用 `await`。 |
||||
|
|
||||
|
这让你可以直接使用 `pytest` 而不会遇到麻烦。 |
||||
|
|
||||
|
!!! note "技术细节" |
||||
|
你也可以用 `from starlette.testclient import TestClient`。 |
||||
|
|
||||
|
**FastAPI** 提供了和 `starlette.testclient` 一样的 `fastapi.testclient`,只是为了方便开发者。但它直接来自Starlette。 |
||||
|
|
||||
|
!!! 提示 |
||||
|
除了发送请求之外,如果你还想测试时在FastAPI应用中调用 `async` 函数(例如异步数据库函数), 可以在高级教程中看下 [Async Tests](../advanced/async-tests.md){.internal-link target=_blank} 。 |
||||
|
|
||||
|
## 分离测试 |
||||
|
|
||||
|
在实际应用中,你可能会把你的测试放在另一个文件里。 |
||||
|
|
||||
|
您的**FastAPI**应用程序也可能由一些文件/模块组成等等。 |
||||
|
|
||||
|
### **FastAPI** app 文件 |
||||
|
|
||||
|
假设你有一个像 [更大的应用](./bigger-applications.md){.internal-link target=_blank} 中所描述的文件结构: |
||||
|
|
||||
|
``` |
||||
|
. |
||||
|
├── app |
||||
|
│ ├── __init__.py |
||||
|
│ └── main.py |
||||
|
``` |
||||
|
|
||||
|
在 `main.py` 文件中你有一个 **FastAPI** app: |
||||
|
|
||||
|
|
||||
|
```Python |
||||
|
{!../../../docs_src/app_testing/main.py!} |
||||
|
``` |
||||
|
|
||||
|
### 测试文件 |
||||
|
|
||||
|
然后你会有一个包含测试的文件 `test_main.py` 。app可以像Python包那样存在(一样是目录,但有个 `__init__.py` 文件): |
||||
|
|
||||
|
``` hl_lines="5" |
||||
|
. |
||||
|
├── app |
||||
|
│ ├── __init__.py |
||||
|
│ ├── main.py |
||||
|
│ └── test_main.py |
||||
|
``` |
||||
|
|
||||
|
因为这文件在同一个包中,所以你可以通过相对导入从 `main` 模块(`main.py`)导入`app`对象: |
||||
|
|
||||
|
```Python hl_lines="3" |
||||
|
{!../../../docs_src/app_testing/test_main.py!} |
||||
|
``` |
||||
|
|
||||
|
...然后测试代码和之前一样的。 |
||||
|
|
||||
|
## 测试:扩展示例 |
||||
|
|
||||
|
现在让我们扩展这个例子,并添加更多细节,看下如何测试不同部分。 |
||||
|
|
||||
|
### 扩展后的 **FastAPI** app 文件 |
||||
|
|
||||
|
让我们继续之前的文件结构: |
||||
|
|
||||
|
``` |
||||
|
. |
||||
|
├── app |
||||
|
│ ├── __init__.py |
||||
|
│ ├── main.py |
||||
|
│ └── test_main.py |
||||
|
``` |
||||
|
|
||||
|
假设现在包含**FastAPI** app的文件 `main.py` 有些其他**路径操作**。 |
||||
|
|
||||
|
有个 `GET` 操作会返回错误。 |
||||
|
|
||||
|
有个 `POST` 操作会返回一些错误。 |
||||
|
|
||||
|
所有*路径操作* 都需要一个`X-Token` 头。 |
||||
|
|
||||
|
=== "Python 3.10+" |
||||
|
|
||||
|
```Python |
||||
|
{!> ../../../docs_src/app_testing/app_b_an_py310/main.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.9+" |
||||
|
|
||||
|
```Python |
||||
|
{!> ../../../docs_src/app_testing/app_b_an_py39/main.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.6+" |
||||
|
|
||||
|
```Python |
||||
|
{!> ../../../docs_src/app_testing/app_b_an/main.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.10+ non-Annotated" |
||||
|
|
||||
|
!!! tip |
||||
|
Prefer to use the `Annotated` version if possible. |
||||
|
|
||||
|
```Python |
||||
|
{!> ../../../docs_src/app_testing/app_b_py310/main.py!} |
||||
|
``` |
||||
|
|
||||
|
=== "Python 3.6+ non-Annotated" |
||||
|
|
||||
|
!!! tip |
||||
|
Prefer to use the `Annotated` version if possible. |
||||
|
|
||||
|
```Python |
||||
|
{!> ../../../docs_src/app_testing/app_b/main.py!} |
||||
|
``` |
||||
|
|
||||
|
### 扩展后的测试文件 |
||||
|
|
||||
|
然后您可以使用扩展后的测试更新`test_main.py`: |
||||
|
|
||||
|
```Python |
||||
|
{!> ../../../docs_src/app_testing/app_b/test_main.py!} |
||||
|
``` |
||||
|
|
||||
|
每当你需要客户端在请求中传递信息,但你不知道如何传递时,你可以通过搜索(谷歌)如何用 `httpx`做,或者是用 `requests` 做,毕竟HTTPX的设计是基于Requests的设计的。 |
||||
|
|
||||
|
接着只需在测试中同样操作。 |
||||
|
|
||||
|
示例: |
||||
|
|
||||
|
* 传一个*路径* 或*查询* 参数,添加到URL上。 |
||||
|
* 传一个JSON体,传一个Python对象(例如一个`dict`)到参数 `json`。 |
||||
|
* 如果你需要发送 *Form Data* 而不是 JSON,使用 `data` 参数。 |
||||
|
* 要发送 *headers*,传 `dict` 给 `headers` 参数。 |
||||
|
* 对于 *cookies*,传 `dict` 给 `cookies` 参数。 |
||||
|
|
||||
|
关于如何传数据给后端的更多信息 (使用`httpx` 或 `TestClient`),请查阅 <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX 文档</a>. |
||||
|
|
||||
|
!!! 信息 |
||||
|
注意 `TestClient` 接收可以被转化为JSON的数据,而不是Pydantic模型。 |
||||
|
|
||||
|
如果你在测试中有一个Pydantic模型,并且你想在测试时发送它的数据给应用,你可以使用在[JSON Compatible Encoder](encoder.md){.internal-link target=_blank}介绍的`jsonable_encoder` 。 |
||||
|
|
||||
|
## 运行起来 |
||||
|
|
||||
|
之后,你只需要安装 `pytest`: |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
$ pip install pytest |
||||
|
|
||||
|
---> 100% |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
他会自动检测文件和测试,执行测试,然后向你报告结果。 |
||||
|
|
||||
|
执行测试: |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
$ pytest |
||||
|
|
||||
|
================ test session starts ================ |
||||
|
platform linux -- Python 3.6.9, pytest-5.3.5, py-1.8.1, pluggy-0.13.1 |
||||
|
rootdir: /home/user/code/superawesome-cli/app |
||||
|
plugins: forked-1.1.3, xdist-1.31.0, cov-2.8.1 |
||||
|
collected 6 items |
||||
|
|
||||
|
---> 100% |
||||
|
|
||||
|
test_main.py <span style="color: green; white-space: pre;">...... [100%]</span> |
||||
|
|
||||
|
<span style="color: green;">================= 1 passed in 0.03s =================</span> |
||||
|
``` |
||||
|
|
||||
|
</div> |
Loading…
Reference in new issue