mcpField
T
testing-python

testing-python

github.com/prefecthq测试与质量28.3k122 次安装1 天前更新
代码仓库

关于 testing-python

面向 Python 开发者的测试编写技能,说明项目专用的测试约定和运行方式,涵盖异步测试与内存传输。

主要功能

  1. 01编写原子化的单一行为测试
  2. 02为相关输入变化使用参数化
  3. 03使用内存传输进行测试
  4. 04遵循夹具与模拟指导
  5. 05使用文档所列 pytest 命令运行测试

适用场景

  1. 01围绕单一行为组织单元测试
  2. 02不加异步标记测试异步代码
  3. 03使用 tmp_path 检查文件操作
  4. 04验证错误处理和外部服务调用

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/prefecthq/fastmcp --skill testing-python

安装后在 Claude Code 中输入/testing-python确认已加载。

详细介绍

编写有效的 Python 测试

核心原则

每个测试都应当原子化、自包含,并且只测试单一功能。一个测试如果测试多项内容,就更难调试和维护。

测试结构

原子化单元测试

每个测试都应验证单一行为。测试失败时,测试名称应能说明哪里出了问题。如果多个断言都在验证同一行为,那么使用多个断言也没有问题。

# Good: Name tells you what's broken
def test_user_creation_sets_defaults():
    user = User(name="Alice")
    assert user.role == "member"
    assert user.id is not None
    assert user.created_at is not None

# Bad: If this fails, what behavior is broken?
def test_user():
    user = User(name="Alice")
    assert user.role == "member"
    user.promote()
    assert user.role == "admin"
    assert user.can_delete_others()
对同一概念的不同变体使用参数化
import pytest

@pytest.mark.parametrize("input,expected", [
    ("hello", "HELLO"),
    ("World", "WORLD"),
    ("", ""),
    ("123", "123"),
])
def test_uppercase_conversion(input, expected):
    assert input.upper() == expected
对不同功能编写独立测试

不要对互不相关的行为进行参数化。如果测试逻辑不同,就编写独立测试。

项目特定规则

无需 async 标记

本项目全局使用 asyncio_mode = "auto"。编写异步测试时无需添加装饰器:

# Correct
async def test_async_operation():
    result = await some_async_function()
    assert result == expected

# Wrong - don't add this
@pytest.mark.asyncio
async def test_async_operation():
    ...
在模块级别导入

将所有导入语句放在文件顶部:

# Correct
import pytest
from fastmcp import FastMCP
from fastmcp.client import Client

async def test_something():
    mcp = FastMCP("test")
    ...

# Wrong - no local imports
async def test_something():
    from fastmcp import FastMCP  # Don't do this
    ...
使用内存传输进行测试

将 FastMCP 服务直接传递给客户端:

from fastmcp import FastMCP
from fastmcp.client import Client

mcp = FastMCP("TestServer")

@mcp.tool
def greet(name: str) -> str:
    return f"Hello, {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 传输。

对复杂数据使用内联快照

使用 inline-snapshot 测试 JSON 架构和复杂结构:

from inline_snapshot import snapshot

def test_schema_generation():
    schema = generate_schema(MyModel)
    assert schema == snapshot()  # Will auto-populate on first run

命令:

  • pytest --inline-snapshot=create - 填充空快照
  • pytest --inline-snapshot=fix - 在有意修改后更新快照

夹具

优先使用函数作用域的夹具
@pytest.fixture
def client():
    return Client()

async def test_with_client(client):
    result = await client.ping()
    assert result is not None
文件操作使用 tmp_path
def test_file_writing(tmp_path):
    file = tmp_path / "test.txt"
    file.write_text("content")
    assert file.read_text() == "content"

模拟

在边界处模拟
from unittest.mock import patch, AsyncMock

async def test_external_api_call():
    with patch("mymodule.external_client.fetch", new_callable=AsyncMock) as mock:
        mock.return_value = {"data": "test"}
        result = await my_function()
        assert result == {"data": "test"}
不要模拟自己编写的内容

尽可能使用真实实现测试代码。模拟外部服务,而不是内部类。

测试命名

使用能说明场景的描述性名称:

# Good
def test_login_fails_with_invalid_password():
def test_user_can_update_own_profile():
def test_admin_can_delete_any_user():

# Bad
def test_login():
def test_update():
def test_delete():

错误测试

import pytest

def test_raises_on_invalid_input():
    with pytest.raises(ValueError, match="must be positive"):
        calculate(-1)

async def test_async_raises():
    with pytest.raises(ConnectionError):
        await connect_to_invalid_host()

运行测试

uv run pytest -n auto              # Run all tests in parallel
uv run pytest -n auto -x           # Stop on first failure
uv run pytest path/to/test.py      # Run specific file
uv run pytest -k "test_name"       # Run tests matching pattern
uv run pytest -m "not integration" # Exclude integration tests

检查清单

提交测试前:

  • 每个测试只测试一项内容
  • 不使用 @pytest.mark.asyncio 装饰器
  • 在模块级别导入
  • 使用描述性测试名称
  • 使用内存传输(测试网络功能时除外)
  • 对同一行为的不同变体使用参数化
  • 对不同的行为编写独立测试

评论

支持 Markdown