
FastAPI 事件测试实战在 TestClient 中运行 lifespan 与已弃用的 startup/shutdown 事件【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi当应用在lifespan中完成数据库连接、预加载缓存等资源初始化时若测试代码不真实触发这一生命周期很容易在开发环境一切正常、测试环境却拿不到数据的边界上踩坑。本文以 FastAPI 仓库中「测试事件Testing Events」这一主题的官方指南为骨架给出在TestClient中用with语句驱动lifespan启动/关闭的完整可运行测试并同时覆盖已被标记弃用的startup/shutdown事件写法。读完你将掌握什么时候测试会真实执行启动清理逻辑、两种写法各自的形态与弃用风险以及如何结合仓库源码与测试用例验证行为。核心背景TestClient 与事件的关系在 FastAPI 中TestClient并非独立实现而是直接复用了 Starlette 的实现——fastapi/testclient.py 中只有一行再导出from starlette.testclient import TestClient as TestClient # noqaTestClient底层通过 ASGI 协议与应用交互而lifespan正是 ASGI 规范中的生命周期协议。因此应用的事件代码是否在测试中执行取决于TestClient是否会驱动该协议。在 Starlette 的TestClient设计中只有把TestClient作为上下文管理器配合with语句使用时才会真实驱动 lifespan 的启动与关闭流程lifespan中yield之前的代码会在进入with块时执行yield之后的清理代码会在退出with块时执行。这正是测试事件类逻辑的前提想要让初始化/清理真正跑起来就必须使用with TestClient(app) as client:这种形态。更多关于在测试中运行 lifespan的设计细节可以查阅 Starlette 官方文档的 lifespan 专题其行为与本仓库TestClient的实际表现一致且可直接用下面两个官方示例源码与对应测试用例验证。方法一推荐用with TestClient测试 lifespan完整示例源码本主题的第一段核心示例位于 docs_src/app_testing/tutorial004_py310.py完整代码如下from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.testclient import TestClient items {} asynccontextmanager async def lifespan(app: FastAPI): items[foo] {name: Fighters} items[bar] {name: Tenders} yield # clean up items items.clear() app FastAPI(lifespanlifespan) app.get(/items/{item_id}) async def read_items(item_id: str): return items[item_id] def test_read_items(): # Before the lifespan starts, items is still empty assert items {} with TestClient(app) as client: # Inside the with TestClient block, the lifespan starts and items added assert items {foo: {name: Fighters}, bar: {name: Tenders}} response client.get(/items/foo) assert response.status_code 200 assert response.json() {name: Fighters} # After the requests is done, the items are still there assert items {foo: {name: Fighters}, bar: {name: Tenders}} # The end of the with TestClient block simulates terminating the app, so # the lifespan ends and items are cleaned up assert items {}关键机制逐步拆解这段示例的核心验证点在于借助一个模块级可变容器items观察 lifespan 的副作用从而把事件执行过程变成可断言的可见事实启动前断言assert items {}确认在进入with块之前lifespan 尚未执行应用还处于未初始化状态。进入with触发启动with TestClient(app) as client:这一行会驱动 lifespan 的启动阶段即执行yield之前的语句items被填充两条数据随后的断言assert items {foo: ..., bar: ...}验证了启动逻辑确实运行。块内正常发起请求client.get(/items/foo)走的是普通 HTTP 请求路径验证在 lifespan 已初始化的前提下路径操作函数可以读到items[foo]并正确序列化返回。块内数据保持请求完成后立即再次断言确认应用运行期间共享状态没有被意外清空。退出with触发关闭with块结束相当于模拟应用终止lifespan 在yield之后的items.clear()清理逻辑被执行退出块后assert items {}验证关闭清理确实生效。这里需要特别注意一个细节清空发生在yield之后的代码段中因此清理必须等with块退出后才可见。若把清理断言误放在块内测试就会失败——这直观说明了 lifespan 的yield与with上下文管理器在时序上的一一对应关系。仓库测试用例的印证FastAPI 仓库自测时并没有重写一份测试而是直接复用这份示例中定义的测试函数tests/test_tutorial/test_testing/test_tutorial004.py 通过from docs_src.app_testing.tutorial004_py310 import test_read_items将其导入并执行。这一做法同时印证了示例代码具备开箱即跑的测试形态test_read_items本身就是完整独立的测试函数lifespan注入、状态断言、清理验证全部包含在内可直接用于任何 pytest 测试集。方法二为已弃用的 startup / shutdown 事件编写测试旧版事件写法的完整示例在引入 lifespan 之前事件通过app.on_event(startup)/app.on_event(shutdown)注册。对应的示例见 docs_src/app_testing/tutorial003_py310.pyfrom fastapi import FastAPI from fastapi.testclient import TestClient app FastAPI() items {} app.on_event(startup) async def startup_event(): items[foo] {name: Fighters} items[bar] {name: Tenders} app.get(/items/{item_id}) async def read_items(item_id: str): return items[item_id] def test_read_items(): with TestClient(app) as client: response client.get(/items/foo) assert response.status_code 200 assert response.json() {name: Fighters}用法与区别写法上旧事件模式与 lifespan 模式在测试端是一致的同样需要把TestClient放进with语句中进入块时触发startup事件填充items请求/items/foo才能拿到{name: Fighters}并断言通过。为什么现在不推荐它startup/shutdown 事件目前处于**弃用deprecated**状态理由在仓库源码中有明确交代。查看 fastapi/applications.py 中on_event方法的实现与文档字符串可以看到这样的说明on_eventis deprecated, uselifespanevent handlers instead.同时FastAPI.__init__附近fastapi/applications.py在受理on_event相关参数时也给出了同样的引导建议改用 lifespan 处理器并指向 FastAPI 官方的事件Events文档。因此新代码应直接使用上文方法一的 lifespan 写法。仓库自身的测试也把弃用告警作为断言的一部分加以固化tests/test_tutorial/test_testing/test_tutorial003.py 用pytest.warns(DeprecationWarning)包裹对该示例模块的导入import pytest def test_main(): with pytest.warns(DeprecationWarning): from docs_src.app_testing.tutorial003_py310 import test_read_items test_read_items()也就是说示例中的app.on_event(startup)装饰器在注册阶段就会发出DeprecationWarning而官方测试通过pytest.warns显式断言了这条告警的存在。这既是对弃用状态的事实确认也提醒开发者旧写法不仅自身要承担迁移成本其产生的警告还可能使把警告当错误-W error的严格 CI 配置直接失败。迁移建议与速查对比如果现有代码仍在使用on_event(startup)建议按如下方式迁移为 lifespan把分散的多个startup/shutdown事件函数收敛进一个asynccontextmanager修饰的异步生成器lifespan(app: FastAPI)中将原startup逻辑放在yield之前原shutdown逻辑放在yield之后在FastAPI(lifespanlifespan)中注册并移除全部app.on_event(...)装饰器测试侧无需改动调用方式——两种模式都使用with TestClient(app) as client:。两种模式在测试中的行为对比如下维度lifespan推荐on_event(startup/shutdown)弃用注册方式asynccontextmanagerFastAPI(lifespanlifespan)app.on_event(startup)等启动/清理逻辑位置yield之前 / 之后分散在不同事件函数测试触发方式with TestClient(app) as client:相同弃用状态现行推荐已弃用注册即触发DeprecationWarning对应示例源码tutorial004_py310.pytutorial003_py310.py编写事件测试的实用清单必须使用上下文管理器只要你的路径操作依赖启动阶段创建的共享资源连接池、预加载缓存、全局配置等就应写成with TestClient(app) as client:否则事件不会执行测试会得到虚假的失败或空数据。用状态断言看见事件时序参考 tutorial004在进入with前、块内、退出后分别断言共享容器内容即可把 lifespan 的启动/清理行为固化为测试契约。清理逻辑放在yield之后它对应退出with块的时刻不要在块内断言清理已生效。避免使用已弃用写法不要在新的on_event测试中新增旧事件代码仓库源码fastapi/applications.py与官方测试tests/test_tutorial/test_testing/test_tutorial003.py均已明确其弃用状态。保持示例即测试可将事件场景直接写成def test_xxx()并在自己的tests/目录中导入复用与仓库的做法保持一致。如果需要在测试中隔离外部依赖例如不想让认证提供商等副作用真实执行可以与同一测试主题下的依赖覆盖机制配合使用相关思路参见韩文官方文档 docs/ko/docs/advanced/testing-dependencies.md。【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考