
FastMCP 项目 Python 测试最佳实践基于 pytest 编写高效、可靠测试的完整指南【免费下载链接】fastmcp The fast, Pythonic way to build MCP servers and clients.项目地址: https://gitcode.com/GitHub_Trending/fa/fastmcp本指南以 FastMCP 仓库内的.claude/skills/python-tests/SKILL.md为骨架全面讲解在 FastMCP 项目MCP 服务器与客户端的高效 Pythonic 构建框架中如何编写与评估高质量 pytest 测试。你将掌握原子化测试设计、参数化、fixtures、mock 边界、异步测试、快照测试以及仓库特有的测试规范asyncio_mode auto、内存传输等并了解这些规则在真实源码与测试文件中的落地方式可直接用于编写、评审和调试本项目的测试代码。核心原则原子性、自包含、单一职责每个测试都应该验证单一功能single functionality并且是原子化atomic、自包含self-contained的。一个同时测试多个行为的测试一旦失败你无法立刻判断是哪个环节出了问题调试和维护成本都会成倍增加。这条原则贯穿 FastMCP 的整个测试体系。以 tests/conftest.py 中提供的fastmcp_serverfixture 为例它只负责一件事构建一个带有 tool、resource、prompt 的 FastMCP 测试服务器而tool_serverfixture 则专注于构造覆盖多种返回类型TextContent、ImageContent、EmbeddedResource等的工具集合。两个 fixture 职责分离测试代码按需取用这正是单一功能思想在 fixture 层面的体现。测试结构设计原子化单元测试每个测试验证一个行为测试函数名应当在你失败时直接告诉你是什么坏了。同一行为内部的多个断言是允许的但跨越多个行为的断言应当拆分到不同测试中# 好名称直接指出失败点 def test_user_creation_sets_defaults(): user User(nameAlice) assert user.role member assert user.id is not None assert user.created_at is not None # 坏如果失败是哪个行为坏了 def test_user(): user User(nameAlice) assert user.role member user.promote() assert user.role admin assert user.can_delete_others()第二个例子把创建用户默认值和用户提升权限两个独立行为塞进了同一个测试任何一步失败都会造成排查歧义。在 FastMCP 仓库中可以看到大量遵循该命名的真实测试例如 tests/tools/test_standalone_decorator.py 中的test_tool_without_parens、test_tool_with_name_arg、test_from_function_preserves_decorator_metadata以及 tests/tools/tool/test_tool.py 中的test_basic_function、test_meta_parameter、test_async_function。每个函数名都精确描述了被验证的行为场景。用参数化覆盖同一概念的多个变体当多个输入本质上属于同一概念的变体时使用pytest.mark.parametrize而不是复制粘贴测试函数import pytest pytest.mark.parametrize(input,expected, [ (hello, HELLO), (World, WORLD), (, ), (123, 123), ]) def test_uppercase_conversion(input, expected): assert input.upper() expected仓库中同样大量使用这一模式例如 tests/tools/test_standalone_decorator.py 中的test_component_import_works_in_fresh_interpreter接收不同的语句参数做参数化验证覆盖不同导入场景。不同功能用独立测试不要参数化不相关的行为。如果测试逻辑本身不同就写成独立的测试函数。参数化只适用于同一逻辑、不同输入的情况一旦分支逻辑出现拆分测试比塞进参数列表更清晰、更易维护。项目特有规则FastMCP 仓库在 pyproject.toml 中为 pytest 配置了一系列全局默认值这直接决定了测试写法。下面逐一说明这些规则及背后的实现依据。无需 async 标记asyncio_mode auto项目的[tool.pytest.ini_options]中设置了asyncio_mode auto与asyncio_default_fixture_loop_scope functionpyproject.toml。这意味着 pytest 会自动识别 async 测试函数并为其创建事件循环无需也不应添加pytest.mark.asyncio装饰器# 正确 async def test_async_operation(): result await some_async_function() assert result expected # 错误 —— 不要加这个 pytest.mark.asyncio async def test_async_operation(): ...这一约定在仓库测试中全面落地。例如 examples/testing_demo/tests/test_server.py 的 fixture 注释明确写着Nopytest.mark.asyncioneeded -asyncio_mode autohandles it其中的test_add_tool、test_greet_tool_default等测试均直接以async def编写。模块级导入所有 import 放在文件顶部。这保证测试运行时的导入顺序可预测、报错位置清晰也避免重复导入带来的性能损耗# 正确 import pytest from fastmcp import FastMCP from fastmcp.client import Client async def test_something(): mcp FastMCP(test) ... # 错误 —— 不要局部导入 async def test_something(): from fastmcp import FastMCP # Dont do this ...注意仓库的 pyproject.toml 配置了pythonpath [fastmcp_slim, fastmcp_remote, fastmcp_tasks]因此测试文件顶部可以直接from fastmcp import FastMCP或from fastmcp.client import Client导入仓库主包。而 tests/conftest.py 中部分 fixture 之所以使用函数内 import如from fastmcp import FastMCP是出于避免导入时副作用的专门考虑见 examples/testing_demo/tests/test_server.py 中的说明属于例外情形日常测试仍应遵循模块级导入。测试优先使用内存传输in-memory transport测试 MCP 服务器时直接把 FastMCP 服务器实例传给 Client走进程内内存传输而不是启动 HTTP 或 stdio 网络链路from fastmcp import FastMCP from fastmcp.client import Client mcp FastMCP(TestServer) mcp.tool def greet(name: str) - str: return fHello, {name}! async def test_greet_tool(): async with Client(mcp) as client: result await client.call_tool(greet, {name: World}) assert result[0].text Hello, World!只有当你显式测试网络特性时才使用 HTTP 传输。这一规则的实现依据来自 fastmcp_slim/fastmcp/client/client.py 的Client类其transport参数接受ClientTransport实例、FastMCP服务器对象、URL 字符串、Path、MCPConfig或字典等多种来源其中直接传入FastMCP即触发内存传输连接开销几乎为零、无需网络端口天然适合单元测试。复杂数据用内联快照inline-snapshot对于 JSON Schema 等复杂结构的断言使用inline-snapshot库首次运行自动填充期望值之后每次变更一目了然from inline_snapshot import snapshot def test_schema_generation(): schema generate_schema(MyModel) assert schema snapshot() # 首次运行会自动填充常用命令pytest --inline-snapshotcreate—— 填充空的快照pytest --inline-snapshotfix—— 在有意修改后更新快照仓库对此提供了完整的工程化支撑依赖声明为inline-snapshot[dirty-equals]0.27.2pyproject.toml当前锁定版本 0.35.2并在addopts中默认--inline-snapshotdisablepyproject.toml保证 CI 中快照不会被无意改写。真实用例可参考 tests/tools/tool/test_tool.pytest_basic_function将Tool.from_function(add)的model_dump(exclude_noneTrue)结果与snapshot({...})做全量比较期望值内联在测试文件中包含了 name、description、parameters、output_schema 等完整结构。类似的快照断言还广泛出现在 tests/server/middleware/test_caching.py、tests/tools/tool/test_output_schema.py、tests/utilities/openapi/test_models.py 等文件中。Fixtures优先使用函数级作用域的 fixture默认的 function 作用域足够覆盖绝大多数场景——每个测试获得独立的 fixture 实例避免状态串扰pytest.fixture def client(): return Client() async def test_with_client(client): result await client.ping() assert result is not None一个值得参考的完整范例在 examples/testing_demo/tests/test_server.py其中的clientfixture 用async with Client(mcp) as client:包装后yield client测试函数直接以async def test_add_tool(client: Client)形式使用。需要说明的是作用域选择应按需而非一刀切。仓库 tests/conftest.py 展示了何时应升级作用域_settings_home_root、rsa_key_pair、otel_trace_provider等标注为scopesession理由包括 RSA 密钥生成耗时数十毫秒、TracerProvider 每进程只能设置一次等属于明确的性能或约束驱动而isolate_settings_home因要隔离每个测试的settings.home目录避免 oauth-proxy 存储目录因相同的jwt_signing_key指纹冲突导致状态泄漏而保持 autouse function 级。默认 function 作用域仅在确有全局成本或全局约束时才提升作用域是这一节的最佳实践总结。文件操作用tmp_path需要读写文件的测试使用 pytest 内置的tmp_pathfixture每个测试获得独立的临时目录def test_file_writing(tmp_path): file tmp_path / test.txt file.write_text(content) assert file.read_text() content仓库中tmp_path被广泛使用例如 tests/utilities/test_version_check.py、tests/utilities/test_types.py 等。Mocking在边界处 mockMock 应当发生在系统边界——外部 API、网络调用、第三方服务你的业务代码内部不要 mockfrom unittest.mock import patch, AsyncMock async def test_external_api_call(): with patch(mymodule.external_client.fetch, new_callableAsyncMock) as mock: mock.return_value {data: test} result await my_function() assert result {data: test}异步函数必须使用AsyncMock或new_callableAsyncMock否则await会直接报错。FastMCP 仓库的 auth 提供者测试大量实践了这一模式例如 tests/server/auth/providers/test_discord.py、tests/server/auth/providers/test_propelauth.py 等对 OAuth 提供商的外部请求做 patch而自身服务逻辑使用真实实现。不要 mock 你拥有的代码测试自己的代码时尽量使用真实实现只 mock 外部服务而不是内部类。这也与前面的内存传输规则一脉相承FastMCP 的服务器与客户端可以直接在进程内真实交互见 tests/conftest.py 的fastmcp_serverfixture 与各类Client(mcp)用例无需 mock 协议层。测试命名使用能说明场景的描述性名称。好的测试名本身就是失败时的诊断信息# 好 def test_login_fails_with_invalid_password(): def test_user_can_update_own_profile(): def test_admin_can_delete_any_user(): # 坏 def test_login(): def test_update(): def test_delete():仓库中的命名规范与此一致——如 tests/tools/tool/test_tool.py 的test_meta_parameter验证 meta 参数正确处理、test_async_function验证异步函数注册与运行以及 tests/tools/test_standalone_decorator.py 的test_tool_with_name_kwarg。命名应当包含被测对象 场景/预期行为两个要素。错误测试用pytest.raises断言异常并通过match参数收紧匹配内容避免抛了错就算过的假阳性import pytest def test_raises_on_invalid_input(): with pytest.raises(ValueError, matchmust be positive): calculate(-1) async def test_async_raises(): with pytest.raises(ConnectionError): await connect_to_invalid_host()注意异步错误测试同样不需要任何 async 标记asyncio_mode auto会自动处理。运行测试项目使用uv作为包与运行环境管理常用命令如下uv run pytest -n auto # 并行运行全部测试 uv run pytest -n auto -x # 遇到首个失败即停止 uv run pytest path/to/test.py # 只运行指定文件 uv run pytest -k test_name # 按名称模式运行 uv run pytest -m not integration # 排除集成测试-n auto依赖 xdist 并行分发。仓库为并行场景做了专门设计除了 pyproject.toml 中定义的integration、client_process、subprocess_heavy、conformance四个 marker 外tests/conftest.py 还通过pytest_collection_modifyitems自动为integration_tests目录下的测试打上integration标记并提供了worker_id、free_port、free_port_factory等 fixture 支持 xdist 工作进程下的端口管理与隔离。建议并行运行前先了解这些 marker 的语义例如subprocess_heavy标记的测试会串行执行以避开并行争抢。其他值得注意的全局配置pyproject.tomlfilterwarnings将 coroutine was never awaited 等异步隐患升级为错误提前暴露资源泄漏timeout 5每个测试的全局超时秒防止测试卡死拖垮整个套件env注入FASTMCP_TEST_MODE1等测试环境变量testpaths [tests]默认只收集 tests 目录python_files/python_classes/python_functions默认收集test_*.py、Test*类、test_*函数。提交前自检清单在提交测试代码前逐项核对以下清单对应 .claude/skills/python-tests/SKILL.md 的原始要求每个测试只测一件事原子化没有pytest.mark.asyncio装饰器asyncio_mode auto所有 import 位于模块顶部测试名描述性、能定位失败点使用内存传输而非 HTTP除非在测试网络特性同一行为的变体用参数化不同行为写成独立测试附测试能力相关仓库资源索引以下是本仓库中与本指南相关的核心文件可继续深入研读测试规范原文.claude/skills/python-tests/SKILL.mdpytest 全局配置asyncio_mode、markers、timeout、addopts、pythonpathpyproject.toml全局 fixturesfastmcp_server、tool_server、rsa_key_pair、free_port、isolate_settings_home 等与自动打标逻辑tests/conftest.py快照断言的完整实战tests/tools/tool/test_tool.py完整可运行的测试示例项目含 async fixtures 与内存传输用法examples/testing_demo/tests/test_server.py 与 examples/testing_demo/README.mdClient的传输参数说明内存传输与 HTTP 的取舍依据fastmcp_slim/fastmcp/client/client.py【免费下载链接】fastmcp The fast, Pythonic way to build MCP servers and clients.项目地址: https://gitcode.com/GitHub_Trending/fa/fastmcp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考