testing-python

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Writing Effective Python Tests

编写高效的Python测试

Core Principles

核心原则

Every test should be atomic, self-contained, and test single functionality. A test that tests multiple things is harder to debug and maintain.
每个测试都应该是原子性的独立的,并且仅测试单一功能。一个测试多个内容的用例更难调试和维护。

Test Structure

测试结构

Atomic unit tests

原子单元测试

Each test should verify a single behavior. The test name should tell you what's broken when it fails. Multiple assertions are fine when they all verify the same behavior.
python
undefined
每个测试应验证单一行为。测试名称应能在失败时明确指出问题所在。当多个断言都验证同一行为时,使用多个断言是可行的。
python
undefined

Good: Name tells you what's broken

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
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?

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()
undefined
def test_user(): user = User(name="Alice") assert user.role == "member" user.promote() assert user.role == "admin" assert user.can_delete_others()
undefined

Use parameterization for variations of the same concept

对同一概念的不同变体使用参数化

python
import pytest

@pytest.mark.parametrize("input,expected", [
    ("hello", "HELLO"),
    ("World", "WORLD"),
    ("", ""),
    ("123", "123"),
])
def test_uppercase_conversion(input, expected):
    assert input.upper() == expected
python
import pytest

@pytest.mark.parametrize("input,expected", [
    ("hello", "HELLO"),
    ("World", "WORLD"),
    ("", ""),
    ("123", "123"),
])
def test_uppercase_conversion(input, expected):
    assert input.upper() == expected

Use separate tests for different functionality

为不同功能编写独立测试

Don't parameterize unrelated behaviors. If the test logic differs, write separate tests.
不要对不相关的行为使用参数化。如果测试逻辑不同,请编写独立的测试用例。

Project-Specific Rules

项目特定规则

No async markers needed

无需async标记

This project uses
asyncio_mode = "auto"
globally. Write async tests without decorators:
python
undefined
本项目全局使用
asyncio_mode = "auto"
。编写异步测试时无需添加装饰器:
python
undefined

Correct

Correct

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

Wrong - don't add this

Wrong - don't add this

@pytest.mark.asyncio async def test_async_operation(): ...
undefined
@pytest.mark.asyncio async def test_async_operation(): ...
undefined

Imports at module level

导入语句放在模块顶部

Put ALL imports at the top of the file:
python
undefined
将所有导入语句放在文件顶部:
python
undefined

Correct

Correct

import pytest from fastmcp import FastMCP from fastmcp.client import Client
async def test_something(): mcp = FastMCP("test") ...
import pytest from fastmcp import FastMCP from fastmcp.client import Client
async def test_something(): mcp = FastMCP("test") ...

Wrong - no local imports

Wrong - no local imports

async def test_something(): from fastmcp import FastMCP # Don't do this ...
undefined
async def test_something(): from fastmcp import FastMCP # Don't do this ...
undefined

Use in-memory transport for testing

测试时使用内存传输

Pass FastMCP servers directly to clients:
python
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!"
Only use HTTP transport when explicitly testing network features.
直接将FastMCP服务器传递给客户端:
python
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 snapshots for complex data

复杂数据使用内联快照

Use
inline-snapshot
for testing JSON schemas and complex structures:
python
from inline_snapshot import snapshot

def test_schema_generation():
    schema = generate_schema(MyModel)
    assert schema == snapshot()  # Will auto-populate on first run
Commands:
  • pytest --inline-snapshot=create
    - populate empty snapshots
  • pytest --inline-snapshot=fix
    - update after intentional changes
使用
inline-snapshot
测试JSON schema和复杂结构:
python
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
    - 在有意修改后更新快照

Fixtures

Fixtures

Prefer function-scoped fixtures

优先使用函数作用域的fixtures

python
@pytest.fixture
def client():
    return Client()

async def test_with_client(client):
    result = await client.ping()
    assert result is not None
python
@pytest.fixture
def client():
    return Client()

async def test_with_client(client):
    result = await client.ping()
    assert result is not None

Use
tmp_path
for file operations

文件操作使用
tmp_path

python
def test_file_writing(tmp_path):
    file = tmp_path / "test.txt"
    file.write_text("content")
    assert file.read_text() == "content"
python
def test_file_writing(tmp_path):
    file = tmp_path / "test.txt"
    file.write_text("content")
    assert file.read_text() == "content"

Mocking

Mocking

Mock at the boundary

在边界处进行Mock

python
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"}
python
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"}

Don't mock what you own

不要Mock自己的代码

Test your code with real implementations when possible. Mock external services, not internal classes.
尽可能使用真实实现测试你的代码。仅Mock外部服务,而非内部类。

Test Naming

测试命名

Use descriptive names that explain the scenario:
python
undefined
使用能说明场景的描述性名称:
python
undefined

Good

Good

def test_login_fails_with_invalid_password(): def test_user_can_update_own_profile(): def test_admin_can_delete_any_user():
def test_login_fails_with_invalid_password(): def test_user_can_update_own_profile(): def test_admin_can_delete_any_user():

Bad

Bad

def test_login(): def test_update(): def test_delete():
undefined
def test_login(): def test_update(): def test_delete():
undefined

Error Testing

错误测试

python
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()
python
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()

Running Tests

运行测试

bash
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
bash
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" # 排除集成测试

Checklist

检查清单

Before submitting tests:
  • Each test tests one thing
  • No
    @pytest.mark.asyncio
    decorators
  • Imports at module level
  • Descriptive test names
  • Using in-memory transport (not HTTP) unless testing networking
  • Parameterization for variations of same behavior
  • Separate tests for different behaviors
提交测试前请确认:
  • 每个测试仅验证一项内容
  • 没有使用
    @pytest.mark.asyncio
    装饰器
  • 导入语句在模块顶部
  • 测试名称具有描述性
  • 使用内存传输(而非HTTP),除非测试网络功能
  • 对同一行为的不同变体使用参数化
  • 为不同行为编写独立测试