发布时间:2026/9/5 18:16:07
FastAPI 事件测试实战:用 TestClient 上下文管理器触发 lifespan 与 startup/shutdown 事件 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在 FastAPI 应用中lifespan上下文管理器和已弃用的startup/shutdown事件负责应用的启动与关闭逻辑例如加载全局数据、建立连接、注册资源等。但直接实例化TestClient(app)并不会执行这些事件导致依赖启动初始化状态的测试会意外失败。本文讲解如何把TestClient作为with上下文管理器使用让 lifespan 在测试中完整跑完启动 → 请求 → 清理三阶段并给出 lifespan 与 startup/shutdown 两种写法下可直接复制运行的测试示例。为什么 lifespan 事件需要显式触发FastAPI 允许你为应用定义生命周期逻辑现代写法是传入一个lifespan异步上下文管理器其yield之前执行启动逻辑yield之后执行清理逻辑旧写法是app.on_event(startup)/app.on_event(shutdown)装饰器现已标记为弃用deprecated。关键点在于TestClient对 lifespan 的执行采用惰性触发策略——只有当客户端进入上下文管理块with TestClient(app) as client:时才会模拟应用启动、执行 lifespan 的yield之前部分退出该with块时才会模拟应用终止、执行yield之后的清理部分。这与 Starlette 的测试客户端行为一致FastAPI 的测试客户端就是直接复用 Starlette 的实现见 fastapi/testclient.py其内容仅有一行from starlette.testclient import TestClient as TestClient。如果不使用with语句而是简单写client TestClient(app)那么 lifespan 根本不会执行任何依赖启动初始化数据的请求都会得到空数据或报错。使用 with 语句测试 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]这里的lifespan是一个由asynccontextmanager装饰的异步上下文管理器yield之前往全局字典items中写入两条数据模拟启动时的资源初始化yield之后清空字典模拟关闭时的资源释放。FastAPI(lifespanlifespan)把它注册到应用上lifespan参数的官方文档说明见 fastapi/applications.py明确指出它用一个上下文管理器取代了startup和shutdown两个函数列表。然后是测试部分核心就是with TestClient(app) as client:这一行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 {}这段测试值得逐段拆解它验证了 lifespan 的完整时序进入with块之前items仍为空字典。此时 lifespan 尚未开始任何启动逻辑都未运行——这是不使用上下文管理器时最常见的测试陷阱。进入with块之后with TestClient(app) as client:语句执行完的瞬间lifespan 的启动部分已经执行完毕items中已有两条数据。这个断言证明了进入上下文即触发启动逻辑的行为。在块内发起请求client.get(/items/foo)返回 200 和预期 JSON且请求完成后items依然存在说明启动数据在整个请求生命周期内可用而不是用完即弃。退出with块之后items重新变回空字典。退出上下文块会模拟应用终止触发 lifespan 中yield之后的清理代码items.clear()。仓库中的测试验证这个教程示例本身就是被测对象。仓库的测试套件 tests/test_tutorial/test_testing/test_tutorial004.py 直接从教程模块导入并执行了上述测试函数from docs_src.app_testing.tutorial004_py310 import test_read_items def test_main(): test_read_items()这说明示例代码是可独立运行的 pytest 用例你可以把它复制到本地项目中原样执行而不必依赖文档渲染工具。测试已弃用的 startup/shutdown 事件如果你的项目仍在使用旧版on_event事件app.on_event(startup)与app.on_event(shutdown)测试方式完全相同把TestClient放入with块即可。官方示例 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 示例的区别有两点这里没有显式的清理逻辑原示例未定义shutdown事件因此测试退出with块时只会模拟应用终止没有数据断言上的变化启动逻辑被拆成独立的app.on_event(startup)装饰函数而不是一个围绕yield的上下文管理器。对应地仓库中也有专门的测试 tests/test_tutorial/test_testing/test_tutorial003.py 来执行这个用例。为什么应该优先使用 lifespan从源码看on_event在 fastapi/applications.py 中被显式标注为deprecated文档字符串直接写明 on_event is deprecated, use lifespan event handlers insteadFastAPI构造函数的on_startup/on_shutdown参数fastapi/applications.py的说明同样建议改用lifespanhandlers。lifespan 写法的优势在于启动与清理逻辑被收敛到同一个上下文管理器中yield前后代码紧邻资源获取与释放天然配对不易出现注册了启动却忘记注册清理的遗漏。FastAPI 更完整的生命周期事件说明包括lifespan的推荐写法与替代方案的细节见 docs/en/docs/advanced/events.md德语版文档见 docs/de/docs/advanced/events.md。实践要点任何依赖启动初始化状态的测试必须使用with TestClient(app) as client:而不是client TestClient(app)两者对 lifespan 的触发行为完全不同。断言放在正确的阶段with块之前断言未初始化状态块内断言已初始化状态和请求结果块外断言已清理状态这样才能完整覆盖启动、服务、清理三个阶段的时序。每个with块对应一次完整的应用生命周期多次进入with块会多次执行 lifespan测试中若需在共享 fixture 里启动应用建议把TestClient的创建和关闭都放进with保证清理逻辑必然执行。新代码统一使用lifespan避免引入新的on_event弃用告警存量startup/shutdown事件的迁移方式是把启动逻辑移到yield前、清理逻辑移到yield后。FastAPI 的TestClient直接转发自 Starlettefastapi/testclient.py因此 Starlette 文档中关于 Running lifespan in tests 的上下文管理器语义在这里同样适用。总结测试 FastAPI 事件机制的核心只有一条规则把TestClient当作上下文管理器使用。进入with块触发 lifespan 启动逻辑退出块触发清理逻辑已弃用的startup/shutdown事件遵循同样的触发规则。配套的教程示例 docs_src/app_testing/tutorial004_py310.py 与 docs_src/app_testing/tutorial003_py310.py 均为可直接运行的 pytest 用例配合 tests/test_tutorial/test_testing/ 下的对应测试文件可作为你项目中事件测试的模板直接参考。【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

2026/9/5 18:11:07

有源电力滤波器DSOGI-PLL:从正负序分离到参数整定

有源电力滤波器专题做到下半,DSOGI-PLL的核心问题已经不是“能不能锁相”,而是“在电网不平衡、畸变和频率偏移同时出现时,锁出来的角度还能不能稳定地用于指令提取”。常规 SRF-PLL 在电压不平衡时,dq 轴上会出现二倍频振荡&…

2026/9/5 20:46:17

10行代码跑通大模型调用:Agent开发的第一站与避坑指南

1. 为什么我用 10 行代码作为 Agent 开发的第一站先说结论:Agent 项目不管吹得多花哨,落地时都要先去问一句“大模型到底能不能按我的要求稳定返回结果”。而验证这件事,10 行代码完全够了。很多人一上来就想整 Agent 框架,什么编…

2026/9/5 20:46:17

手撸大模型API调用:10行代码跑通,Agent底层循环与踩坑全复盘

1. 为什么我坚持“手撸”而不是直接用 LangChain 先说个背景。我接触 AI Agent 这个概念有半年多了,但一直处于“看文章很懂、动手就废”的状态。市面上的教程分两种:一种是讲概念讲得天花乱坠,什么规划、记忆、工具调用、多智能体协作&#…

2026/9/5 20:46:17

AI编程中的Skills技能包:从原理剖析到实战编写与避坑指南

最近 AI 编程圈里,不管你在哪个开发者群潜水,应该都被 “Skills” 这个词刷屏了。Claude Code 在推 Skills,Codex 在推 Skills,Cursor 和 OpenCode 也都跟进,GitHub 上几乎每天都有新的 AI Skills 仓库冒出来。更夸张的…

2026/9/5 20:41:17

南卡Clip E耳夹式耳机体验:AI实时翻译能否成为旅行翻译官?

从一个很常见的画面说起:你刚落地,左手拖着行李箱,右手举着护照。柜台对面的工作人员语速很快,口音也很重,你听到三四个单词,但没抓住完整意思。你想确认是不是这个登机口、是不是需要先取行李,…

2026/9/5 2:46:54

vSound小提琴数字处理器实操指南:从接线到演出的完整配置

电小提琴或者原声小提琴插电演出,第一个绕不开的坎就是声音难听。原声琴的共鸣和空气感一旦进了拾音器,出来的往往是一坨干瘪、发尖、带着奇怪塑料味的信号。我当初第一次把琴接上乐队调音台,直接被主唱吐槽"你这声音像在锯钢丝"。…

2026/9/5 2:46:52

传感器接口IC如何攻克生物化学传感的微弱信号难题?

1. 从电极到比特流:为什么生物化学传感必须依赖专用接口IC 做生物化学传感的人都有过类似的经历:明明传感器本身性能很好,信号输出却一塌糊涂——噪声大、漂移明显、重复性差,怎么调都达不到预期。很多时候问题并不在传感器&#…

2026/9/5 2:44:34

STM32F411CEU6多通道ADC采集:扫描模式+DMA实现详解

1. 多通道 ADC 的用武之地把“Multichannel ADC”和“STM32F411CEU6”这两个关键字放在一起,其实就是嵌入式开发里最常遇到的一类需求:用一块不算贵的 MCU,同时采集多路模拟信号。STM32F411CEU6 是 48 引脚的 Cortex-M4F 主控,主频…

2026/9/5 0:04:47

流式背压机制:避免前端渲染卡死与内存暴涨的滑动窗口限流

流式背压机制:避免前端渲染卡死与内存暴涨的滑动窗口限流在大模型流式输出(Streaming)与智能体实时推流的架构中,生产环境中经常出现一种“上下游生产消费速率严重失衡”的极端情况: 生产端极速产出:大模型…

2026/9/5 2:45:13

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/5 2:30:42

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/5 2:46:50

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…