Sanic Testing 快速上手:使用官方测试客户端编写 API 测试
【免费下载链接】sanicAccelerate your web app development | Build fast. Run fast.项目地址: https://gitcode.com/gh_mirrors/sa/sanic
本篇技术指南围绕 Sanic 官方测试客户端sanic-testing展开,介绍其安装方式、环境要求,以及如何通过app.test_client与app.asgi_client编写同步与异步测试。读完本文后,你将掌握 Sanic 应用测试的基础流程、三大测试客户端的差异与适用场景,并能直接参考 Sanic 项目自身 tests 目录中的真实用法落地实践。
什么是 Sanic Testing
sanic-testing是 Sanic 的官方测试客户端(official testing client)。它的首要用途是驱动 Sanic 项目自身的测试——在本仓库的 tests 目录中,几乎所有功能测试都通过app.test_client发起请求,例如 tests/test_app.py 中就用一个最小应用验证路由处理器内的app.loop可用性:
def test_app_loop_running(app: Sanic): @app.get("/test") async def handler(request): assert isinstance(app.loop, asyncio.AbstractEventLoop) return text("pass") request, response = app.test_client.get("/test") assert response.text == "pass"同时,它也定位为"让 API 测试快速跑起来"的易用客户端:只要把包装进环境,即可直接通过应用实例上的test_client属性发起 HTTP 调用,无需额外配置。
最低环境要求
| 依赖 | 版本要求 | 说明 |
|---|---|---|
| Python | 3.7+ | 运行时环境 |
| Sanic | 21.3+ | 使用独立的sanic-testing包 |
需要特别说明的是:Sanic 21.3 之前的版本,该测试模块直接内置在 Sanic 中,即sanic.testing;21.3 起才拆分为独立的sanic-testing分发包。因此如果仍在使用旧版 Sanic,无需单独安装本包。
安装
从 PyPI 直接安装即可:
pip install sanic-testing安装完成后,不需要任何初始化动作——只要sanic-testing在当前 Python 环境中,Sanic 应用实例就会自动暴露测试客户端。这一点可以从源码得到印证:在 sanic/app.py 中,test_client是一个惰性求值的属性,首次访问时才导入并实例化客户端:
@property def test_client(self) -> SanicTestClient: if self._test_client: return self._test_client elif self._test_manager: return self._test_manager.test_client from sanic_testing.testing import SanicTestClient # type: ignore self._test_client = SanicTestClient(self) return self._test_client同理,asgi_client属性(sanic/app.py)在首次访问时惰性创建SanicASGITestClient。这意味着不装包也能正常使用 Sanic,只有访问测试客户端属性时才需要sanic-testing。
编写同步测试
同步测试是 Sanic 应用最常见的测试方式。使用测试客户端只需两步:在 fixture 中构造Sanic应用,然后在测试中访问app.test_client属性并发起请求。
import pytest from sanic import Sanic, response @pytest.fixture def app(): sanic_app = Sanic("TestSanic") @sanic_app.get("/") def basic(request): return response.text("foo") return sanic_app def test_basic_test_client(app): request, response = app.test_client.get("/") assert request.method.lower() == "get" assert response.body == b"foo" assert response.status == 200几点值得注意:
- 调用返回的是
(request, response)二元组,其中request是测试客户端收到的请求对象,response是应用返回的响应对象; - 对响应可同时使用
response.body(字节)与response.text(字符串)两种访问方式,仓库测试中两种写法都在使用; Sanic("TestSanic")的第一个参数是应用名称,仓库测试夹具 tests/conftest.py 中会用测试名生成唯一应用名,避免应用注册表冲突。
仓库自身的测试代码还展示了更丰富的断言模式,例如在 tests/test_app.py 中直接断言response.text == "pass"、在 tests/test_blueprint_group.py 中按不同 HTTP 方法逐个调用test_client.put/post/delete/patch并传入自定义headers。
编写异步测试(ASGI 客户端)
如果你希望测试以async函数编写,可以使用 ASGI 客户端app.asgi_client。由于测试函数本身是协程,需要在pytest中启用异步支持:
pip install pytest-asyncio安装后创建异步测试即可:
import pytest from sanic import Sanic, response @pytest.fixture def app(): sanic_app = Sanic(__name__) @sanic_app.get("/") def basic(request): return response.text("foo") return sanic_app @pytest.mark.asyncio async def test_basic_asgi_client(app): request, response = await app.asgi_client.get("/") assert request.method.lower() == "get" assert response.body == b"foo" assert response.status == 200与同步版本相比,唯一的区别是:
- 测试函数使用
@pytest.mark.asyncio装饰(或按pytest-asyncio的全局配置方式标记); - 每次调用前需要
await,即await app.asgi_client.get("/")。
注意pytest-asyncio是本仓库测试栈之外的第三方插件,不属于sanic-testing的依赖,需要按需自行安装。
三大测试客户端及其原理
结合同一目录下的进阶文档 测试客户端详解,sanic-testing共提供三种客户端,能力各不相同,按需选用:
1. 同步客户端SanicTestClient(真实服务器)
SanicTestClient会在本机网络端口上真实启动一个 Sanic Server来运行测试:每次调用端点时,它都会拉起一次应用实例并绑定到宿主机操作系统的某个 socket 上,然后使用httpx直接对该应用发起调用。这是 Sanic 应用测试的典型方式。
安装sanic-testing后即可直接使用,无需额外设置;也可以通过 sanic/app.py 中test_client属性的惰性导入自动获得。当然,你也可以自行实例化:
from sanic_testing.testing import SanicTestClient test_client = SanicTestClient(app) test_client.get("/path/to/endpoint")第三种方式是通过TestManager统一管理两个客户端:
from sanic_testing import TestManager mgr = TestManager(app) app.test_client.get("/path/to/endpoint") # or mgr.test_client.get("/path/to/endpoint")2. ASGI 异步客户端SanicASGITestClient(进程内执行)
与每次请求都拉起服务器的SanicTestClient不同,SanicASGITestClient不启动真实服务器,而是借助httpx将 Sanic 作为 ASGI 应用执行,直接"钻进"应用内部运行路由处理器,因此调用必须await:
await app.test_client.get("/path/to/endpoint")它提供与SanicTestClient完全相同的方法集合与使用方式。需要澄清一个常见误解:ASGI 客户端并不要求被测应用必须按 ASGI 方式运行,它和同步客户端一样,都能测试任意 Sanic 应用。
3. 常驻服务客户端ReusableClient(手动控制生命周期)
ReusableClient与SanicTestClient思路相近(真实启动应用实例并发出真实 HTTP 请求),但应用的生命周期由你控制:并非每个请求都重启一个 Web Server,而是启动一次、按需停止,并可在同一个运行实例上连续发起多次请求。与前两个客户端不同,ReusableClient必须手动实例化:
from sanic_testing.reusable import ReusableClient client = ReusableClient(app)并且要配合上下文管理器使用——一旦离开with作用域,服务器即关闭:
from sanic_testing.reusable import ReusableClient def test_multiple_endpoints_on_same_server(app): client = ReusableClient(app) with client: _, response = client.get("/path/to/1") assert response.status == 200 _, response = client.get("/path/to/2") assert response.status == 200这一模式特别适合"同一服务实例上串行验证多个端点"的场景,避免了SanicTestClient每个请求重启服务的开销。
支持的请求方法
客户端提供以下请求方法,与httpx的调用习惯几乎一致:
SanicTestClient.getSanicTestClient.postSanicTestClient.putSanicTestClient.patchSanicTestClient.deleteSanicTestClient.optionsSanicTestClient.headSanicTestClient.websocketSanicTestClient.request
使用要点(一个关键提醒):这些方法可以接受任何你平时传给httpx的参数。唯一的例外是——当你使用test_client.request并希望手动指定 HTTP 方法时,参数名是http_method而非method:
test_client.request("/path/to/endpoint", http_method="get")与 Sanic 项目自身的测试体系
sanic-testing是 Sanic 自测的基石。从本仓库 tests/conftest.py 可以看到,测试基座直接从sanic_testing.testing导入PORT常量,并开启Sanic.test_mode = True:
from sanic_testing.testing import PORT ... Sanic.test_mode = True此外,conftest.py中的portfixture(tests/conftest.py)通过绑定 0 端口让操作系统自动分配可用端口,再配合run_startupfixture(tests/conftest.py)以app.create_server(port=PORT)的方式做启动类测试——这些模式都可以直接迁移到你自己的测试工程中。
整个 tests 目录(如test_app.py、test_blueprints.py、test_middleware.py、test_request.py等)几乎都通过app.test_client发起请求,是学习 Sanic Testing 各种用法的最佳活教材。官方测试实战指南还可参考 测试最佳实践。
小结
- 环境就绪后(Python 3.7+、Sanic 21.3+),
pip install sanic-testing即可开箱即用; - 同步测试走
app.test_client,异步测试走await app.asgi_client(需pytest-asyncio); - 三种客户端各有所长:
SanicTestClient每次请求拉起真实服务器、SanicASGITestClient在进程内按 ASGI 执行、ReusableClient常驻服务手动控制生命周期; - 请求方法兼容
httpx参数,仅在request方法中用手动指定方法时需使用http_method参数; - 想深入了解三种客户端的更多细节,可继续阅读 测试客户端详解。
【免费下载链接】sanicAccelerate your web app development | Build fast. Run fast.项目地址: https://gitcode.com/gh_mirrors/sa/sanic
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考