后端Web框架【免费下载链接】starletteThe little ASGI framework that shines. 项目地址https://gitcode.com/gh_mirrors/st/starlette点击查看免费下载TestClient 是 Starlette 内置的 ASGI 测试客户端它让你无需启动真实服务器即可对 ASGI 应用发起 HTTP/WebSocket 请求是编写单元测试与集成测试的核心工具。本文将以 docs/testclient.md 为主线结合 starlette/testclient.py 源码与 tests/test_testclient.py 测试用例系统讲解 TestClient 的安装、基础用法、Debug 扩展、异步后端切换、WebSocket 会话测试以及异步测试场景帮助你掌握一套可复制、可验证的 ASGI 测试方案。TestClient 是什么基于 httpx2 的同步测试入口TestClient 允许你向 ASGI 应用直接发起请求其底层由httpx2库驱动。在 starlette/testclient.py 的导入逻辑第 35–51 行中可以清楚看到源码优先import httpx2 as httpx只有在httpx2缺失时才回退到传统的httpx并发出弃用警告StarletteDeprecationWarning提示“使用httpx搭配starlette.testclient已被弃用请安装httpx2”。因此安装时请务必使用pip install httpx2httpx2已包含在starlette[full]扩展安装中pip install starlette[full]最小示例一个最基础的测试长这样直接来自 docs/testclient.mdfrom starlette.responses import HTMLResponse from starlette.testclient import TestClient async def app(scope, receive, send): assert scope[type] http response HTMLResponse(htmlbodyHello, world!/body/html) await response(scope, receive, send) def test_app(): client TestClient(app) response client.get(/) assert response.status_code 200TestClient 暴露的接口与任何httpx2会话session完全一致。关键点在于发起请求的调用是普通的同步函数调用不是 awaitable例如上面的client.get(/)直接返回响应对象测试函数无需写成async def。TestClient 在源码中的定位从源码结构看TestClient继承自httpx.Client见 starlette/testclient.py并通过自定义的_TestClientTransport将 HTTP 请求翻译成 ASGI 消息http.request/http.response.start/http.response.body直接喂给应用。其构造参数第 376–388 行包括参数默认值说明app必填待测试的 ASGI 应用兼容 ASGI2/ASGI3ASGI2 会经_WrapASGI2自动包装base_urlhttp://testserver请求的基础 URL相对路径会与之拼接raise_server_exceptionsTrue是否在测试进程中直接抛出应用内异常root_pathASGIroot_path挂载子应用时使用backendasyncio异步后端可选triobackend_optionsNone传给anyio.start_blocking_portal()的选项cookiesNone初始 CookieheadersNone初始请求头默认注入user-agent: testclientfollow_redirectsTrue是否自动跟随重定向client(testclient, 50000)注入 ASGI scope 的客户端地址此外TestClient 自动在 scope 中声明支持的 ASGI 扩展包括http.response.debug与websocket.http.response见 starlette/testclient.py 与 starlette/testclient.py这为后面要讲的 Debug 信息和 WebSocket 拒绝响应提供了通道。请求头、会话 Cookie 与文件上传由于 TestClient 就是一个httpx2会话你可以使用其全部标准 API比如认证、会话 Cookie 管理、文件上传等。设置请求头既可以在客户端上统一设置也可以为单个请求单独设置原文档示例client TestClient(app) # 在客户端上设置请求头作用于后续所有请求 client.headers {Authorization: ...} response client.get(/) # 只为某一次请求单独设置请求头 response client.get(/, headers{Authorization: ...})注意client.headers {...}这种赋值会整体替换掉默认的请求头集合而client.get(..., headers{...})只在单次请求上附加。重复头也是合法的测试用例 test_with_duplicate_headers 验证了可以通过headers[(x-token, foo), (x-token, bar)]发送同名多值请求头。发送文件TestClient 支持与httpx2一致的文件上传语法client TestClient(app) # 发送单个文件 with open(example.txt, rb) as f: response client.post(/form, files{file: f}) # 发送多个文件可为每个文件指定文件名与 MIME 类型 with open(example.txt, rb) as f1: with open(example.png, rb) as f2: files {file1: f1, file2: (filename, f2, image/png)} response client.post(/form, filesfiles)在第二段示例中files字典的值既可以是文件对象f1也可以是(filename, fileobj, content_type)三元组f2这样上传到服务器后file2会携带自定义文件名filename和 MIME 类型image/png。Cookie 与域名限制会话 Cookie 由httpx2的 cookie jar 自动管理。测试用例 test_domain_restricted_cookies 说明了一个实用细节TestClient 的默认base_url是http://testserver因此仅当 Cookie 的域名与该 URL 匹配时才会被接受例如设置domainexample.com的 Cookie 会被丢弃返回False而domaintestserver或testserver.local会被保留。控制服务端异常raise_server_exceptions默认情况下TestClient 会直接抛出应用内发生的任何异常——这对调试很有用但偶尔你需要测试 500 错误响应的内容而不是让测试因服务端异常直接失败。此时应使用client TestClient(app, raise_server_exceptionsFalse)从源码看这个开关同时影响异常处理与响应构造在 starlette/testclient.py 中当应用抛出异常且raise_server_exceptionsFalse时异常被吞掉若此时尚未收到任何http.response.start消息TestClient 会构造一个status_code500、空响应体的httpx.Response返回。这样断言response.status_code 500以及检查错误页内容就成为可能。用上下文管理器触发 lifespanTestClient 只有在作为上下文管理器即with语句使用时才会运行应用的lifespanstartup/shutdown处理器单独实例化并不会触发。这一点在 docs/testclient.md 的 note 中特别强调详细说明见 docs/lifespan.mdfrom example import app from starlette.testclient import TestClient def test_homepage(): with TestClient(app) as client: # 进入 with 块时应用的 lifespan startup 被调用 response client.get(/) assert response.status_code 200 # 退出 with 块时lifespan 的 teardown 被执行测试用例 test_app_async_cm_lifespan 精确验证了这一行为startup_complete在进入with块后立即变为Truecleanup_complete则要等退出with块后才变为True。从源码看__enter__starlette/testclient.py会启动一个 anyio blocking portal向应用发送lifespan.startup并等待lifespan.startup.complete__exit__第 517–518 行则关闭 portal触发lifespan.shutdown流程见wait_shutdown第 544–558 行。因此只要应用依赖 lifespan 完成资源初始化如连接数据库、加载模型测试就应始终使用with TestClient(app) as client:写法。Debug 信息通过 http.response.debug 扩展断言原始数据TestClient 支持 ASGI 的http.response.debug扩展参见 ASGI 扩展规范。应用在响应过程中发送的info字典会被 TestClient 收集并暴露为response.extensions[http.response.debug]这样自定义响应类就能把结构化的调试信息直接透传给测试而不必在测试里解析序列化后的响应体。这在“断言响应体需要反解析”的场景下特别有用。例如一个把数据行渲染成 CSV 的响应类可以把原始 rows 通过 debug 扩展发送出去测试直接断言原始数据import csv import io from starlette.responses import Response class CSVResponse(Response): media_type text/csv def __init__(self, rows): self.rows rows buffer io.StringIO() csv.writer(buffer).writerows(rows) super().__init__(buffer.getvalue()) async def __call__(self, scope, receive, send): if http.response.debug in scope.get(extensions, {}): await send({type: http.response.debug, info: {rows: self.rows}}) await super().__call__(scope, receive, send) def test_export(): client TestClient(app) response client.get(/export) rows response.extensions[http.response.debug][rows] assert rows[0] (id, username, joined) assert len(rows) 101这里的关键模式是自定义响应类在__call__中先检查scope[extensions]里是否存在http.response.debugTestClient 会默认在 scope 中注入该扩展存在才发送 debug 消息info是任意 JSON 可序列化的字典测试端通过response.extensions[http.response.debug]原样取回。模板响应的 template/context 调试信息模板响应复用了同一个扩展来暴露.template与.context属性。在 starlette/templating.py 中_TemplateResponse.__call__检测到http.response.debug扩展时会发送await send({type: http.response.debug, info: {template: self.template, context: self.context}})对应地TestClient 在收集到 debug 信息后starlette/testclient.py如果info中带有template/context键还会额外把它们挂到响应对象上作为response.template/response.context属性。因此可以在测试中这样断言参考 docs/templates.mdfrom starlette.testclient import TestClient def test_homepage(): client TestClient(app) response client.get(/) assert response.status_code 200 assert response.template.name index.html assert request in response.contextinfo字典始终可以通过response.extensions[http.response.debug]获取即使它携带的是template/context键。对应的测试见 test_debug_info_in_response_extensions 与 test_debug_info_in_response_extensions_with_template后者验证了info中同时包含template、context、blocks等键时的完整行为。改变客户端地址client 参数默认情况下TestClient 注入到 ASGI scope 中的客户端地址scope[client]是(testclient, 50000)。你可以通过client参数覆盖client TestClient(app, client(localhost, 8000))测试用例 test_client 与 test_client_custom_client 分别验证了默认值与自定义值的行为前者断言response.json() {host: testclient, port: 50000}后者传入client(192.168.0.1, 3000)后断言应用收到的 host/port 随之改变。这在测试依赖客户端 IP 的功能如地区限制、审计日志、访问控制时非常实用。选择异步后端asyncio 与 trio、uvloopTestClient 接受backend字符串与backend_options字典两个参数它们会被原样传给anyio.start_blocking_portal()。关于可用的后端选项请参阅 anyio 的官方文档。默认使用asyncio及默认选项。使用 Trio 后端def test_app(): with TestClient(app, backendtrio) as client: ...使用 asyncio uvloopdef test_app(): with TestClient(app, backend_options{use_uvloop: True}) as client: ...从源码看这两个参数被收集进_AsyncBackendTypedDictstarlette/testclient.py在_portal_factory第 416–422 行中通过anyio.from_thread.start_blocking_portal(**self.async_backend)展开。也就是说应用代码运行在由 anyio 管理的后台线程的独立事件循环中backend决定用哪个事件循环实现backend_options决定其具体配置。如果你的代码库依赖 Trio 生态或希望用 uvloop 加速 asyncio 循环都可以通过这两个参数在测试中切换。测试 WebSocket 会话TestClient 同样支持 WebSocket 会话测试。握手部分由httpx2构建因此 HTTP 与 WebSocket 测试之间可以复用相同的认证选项和其他请求头。基本会话from starlette.testclient import TestClient from starlette.websockets import WebSocket async def app(scope, receive, send): assert scope[type] websocket websocket WebSocket(scope, receivereceive, sendsend) await websocket.accept() await websocket.send_text(Hello, world!) await websocket.close() def test_app(): client TestClient(app) with client.websocket_connect(/) as websocket: data websocket.receive_text() assert data Hello, world!同 HTTP 场景一样会话上的操作都是普通函数调用不是 awaitable。必须把会话放在with块内使用这能确保承载 ASGI 应用的后台线程被正确终止并保证应用内发生的任何异常一定会被 TestClient 重新抛出。从源码看WebSocketTestSession.__enter__starlette/testclient.py启动 portal 并发送websocket.connect消息、等待应用接受连接__exit__时自动调用self.close(1000)并等待后台任务结束。建立测试会话.websocket_connect(url, subprotocolsNone, **options)接收与httpx2.get()相同的参数集。如果应用没有接受 WebSocket 连接可能抛出starlette.websockets.WebSocketDisconnect如果应用通过send_denial_response()拒绝握手则会抛出WebSocketDenialResponse一个同时继承httpx.Response与WebSocketDisconnect的特殊类型见 starlette/testclient.py。websocket_connect()必须以上下文管理器with块形式使用。注意websocket_connect不支持params参数。如果需要传查询参数请直接硬编码进 URLwith client.websocket_connect(/path?foobar) as websocket: ...从源码starlette/testclient.py看websocket_connect会基于ws://testserver拼接 URL自动补齐connection: upgrade、sec-websocket-key: testserver、sec-websocket-version: 13等握手头并在传入subprotocols时设置sec-websocket-protocol。发送数据.send_text(data)— 向应用发送给定文本。.send_bytes(data)— 向应用发送给定字节串。.send_json(data, modetext)— 向应用发送给定数据使用modebinary则通过二进制数据帧发送 JSON。接收数据.receive_text()— 等待应用发来的文本并返回。.receive_bytes()— 等待应用发来的字节串并返回。.receive_json(modetext)— 等待应用发来的 JSON 并返回使用modebinary则通过二进制数据帧接收 JSON。上述接收方法可能抛出starlette.websockets.WebSocketDisconnect应用主动关闭连接时。源码中send_json使用json.dumps(data, separators(,, :), ensure_asciiFalse)紧凑序列化starlette/testclient.pyreceive_json则根据mode从 text 或 bytes 帧解码第 197–204 行。关闭连接.close(code1000)— 从客户端侧关闭 WebSocket 连接可指定关闭码。测试用例还演示了两个典型的 WebSocket 场景应用先主动send_json而测试端阻塞receive_json时不影响应用继续执行test_websocket_blocking_receive以及应用持续运行、退出with块时后台任务被取消test_websocket_not_block_on_close。异步测试直接使用 httpx2.AsyncClient有些时候你需要在应用之外做异步操作例如调用应用后用现有的异步数据库客户端检查数据库状态。这类场景用 TestClient 会比较别扭因为它会创建自己的事件循环而异步资源如数据库连接通常无法跨事件循环共享。最简单的解决办法是让整个测试变成 async配合异步客户端使用例如httpx2.AsyncClientfrom httpx2 import AsyncClient, ASGITransport from starlette.applications import Starlette from starlette.routing import Route from starlette.requests import Request from starlette.responses import PlainTextResponse def hello(request: Request) - PlainTextResponse: return PlainTextResponse(Hello World!) app Starlette(routes[Route(/, hello)]) # 如果使用 pytest需要添加异步标记例如 # pytest.mark.anyio # 使用 https://github.com/agronholm/anyio # 或安装并配置 pytest-asynciohttps://github.com/pytest-dev/pytest-asyncio async def test_app() - None: # 注意必须设置 base_url相对路径如 / 才能正常工作 transport ASGITransport(appapp) async with AsyncClient(transporttransport, base_urlhttp://testserver) as client: r await client.get(/) assert r.status_code 200 assert r.text Hello World!ASGITransport与AsyncClient的组合让请求仍然直接进入 ASGI 应用不走真实网络同时测试代码本身运行在你自己的事件循环上从而可以自由地复用同一事件循环中的异步数据库连接等资源。这是“需要异步断言”场景下的标准替代方案。小结如何选择测试方式场景推荐方式说明常规 HTTP 接口测试TestClient(app)同步函数调用与httpx2会话接口一致需要触发 lifespanwith TestClient(app) as client:进入/退出with块时分别执行 startup/shutdown断言 500 响应内容TestClient(app, raise_server_exceptionsFalse)吞掉服务端异常并返回 500 响应断言非序列化原始数据response.extensions[http.response.debug]自定义响应类经 debug 扩展透传结构化信息WebSocket 双向通信with client.websocket_connect(/) as ws:必须使用with块操作均为同步函数调用应用外需要异步资源httpx2.AsyncClientASGITransport测试自身运行在既有事件循环上TestClient 的核心价值在于它以同步接口封装了 ASGI 消息循环让你获得真实 ASGI 应用的完整行为包括 lifespan、扩展、WebSocket 升级却不需要真实服务器与网络栈。建议在编写新功能时同步维护 tests/test_testclient.py 这类测试文件把本文中的调试扩展、会话 Cookie、文件上传、WebSocket 握手等模式沉淀为可回归的测试资产。赞分享后端Web框架【免费下载链接】starletteThe little ASGI framework that shines. 项目地址https://gitcode.com/gh_mirrors/st/starlette点击查看免费下载相关推荐用 Rust 客户端驱动 TigerBeetle 全系统混沌测试Vortex Rust Driver 实战指南用 Rust 客户端驱动 TigerBeetle 全系统混沌测试Vortex Rust Driver 实战指南 Vortex 是 TigerBeetle 仓库数据库金融科技分布式数据库后端FastAPI WebSocket 自动化测试实战用 TestClient 与 websocket_connect 编写端到端用例FastAPI WebSocket 自动化测试实战用 TestClient 与 websocket_connect 编写端到端用例 WebSocket 是 F后端Web框架API设计swagger-codegen 生成的 C 客户端 FakeApi 全解析Petstore 测试端点实战指南swagger codegen 生成的 C 客户端 FakeApi 全解析Petstore 测试端点实战指南 本篇技术指南以 swagger codegen开发工具代码生成API设计创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
企业数字化 ERP 产品动态
相关推荐
电竞行业开发入门到精通:避开教程陷阱,3天搞定实战 电竞行业开发入门到精通:避开教程陷阱,3天搞定实战 看了一堆视频,代码敲得滚瓜烂熟,一上手做项目就脑子空白?这其实是典型的“眼高手低”。在电竞行业,无论是做赛事直播平台、选手数据大屏,还是后端高并发匹配系统,光懂语法不够,得懂业务场景下的工… · 2026/9/23 1:39:22
屏幕点击助手源码拆解:从入门到精通的避坑指南 屏幕点击助手源码拆解:从入门到精通的避坑指南 看了一堆教程还是不会写项目?很多学员卡在“屏幕点击助手”这类自动化工具上,觉得代码能跑但不知其所以然,导致一旦环境变化或目标应用更新就彻底失效。想真正从入门到精通,不能只抄代码,必须拆解底层逻辑… · 2026/9/23 1:39:22
3步搞定dex编辑器性能优化,新手也能跑通实战 3步搞定dex编辑器性能优化,新手也能跑通实战 刚毕业写代码,是不是觉得语法都会,一到搭项目就卡壳?别慌,很多新人都在【dex编辑器】这个工具上栽过跟头。很多人只知其名,不知其如何用于高性能场景下的代码查看与调试,尤其是当涉及Android… · 2026/9/23 4:16:24
轮胎字符识别实战:从数据标注到YOLOv5与CNN两阶段模型训练 简介:这份资源面向计算机、电子信息工程、数学等专业的大学生,用于课程设计、期末大作业与毕业设计场景,核心任务是轮胎字符识别。包内提供完整源代码、文档说明与配套数据,覆盖从原始数据提取高度数据、转化为高度图、裁切与修复… · 2026/9/23 4:16:24
3秒看懂shell意思:程序员必备速查手册 3秒看懂shell意思:程序员必备速查手册 官方文档翻了三页还没找到重点?别急,很多新手卡在“shell”这个词上,其实它没那么玄乎。 今天这篇 速查手册… · 2026/9/23 4:16:11
6677源码解析:搞懂底层逻辑,面试不再被问懵 6677源码解析:搞懂底层逻辑,面试不再被问懵 面试时被问“这玩意底层怎么实现的”,你脑子是不是瞬间空白?平时只会在框架里调API,真让你扒开源码看细节,立马露馅。别慌,很多老手也是从背八股文开始,但想拿高薪,必须得懂点 源码解析… · 2026/9/23 4:16:05
3招搞定阿拉伯语输入法性能瓶颈,面试必问实战 3招搞定阿拉伯语输入法性能瓶颈,面试必问实战 版本升级后 API 全变了,你的阿拉伯语输入法还卡在 50ms 以上吗? 很多后端开发在面试中被问起国际化文本处理时,往往只停留在“支持 UTF-8”这个层面。… · 2026/9/23 4:16:05
3招搞定手机怎么下载微信面试难题实战项目解析 3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A… · 2026/9/23 0:00:03
你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 面试被问“高并发下如何保证消息不丢失”,你张口就是“用Redis”,结果面试官追问“如果Redis宕机了怎么办”,你瞬间卡壳。这种场景太常见了,很多新手在背八股文时,只记住了技术名词… · 2026/9/23 0:00:29