testing-python
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseWriting 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
undefinedGood: 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()
undefineddef test_user():
user = User(name="Alice")
assert user.role == "member"
user.promote()
assert user.role == "admin"
assert user.can_delete_others()
undefinedUse 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() == expectedpython
import pytest
@pytest.mark.parametrize("input,expected", [
("hello", "HELLO"),
("World", "WORLD"),
("", ""),
("123", "123"),
])
def test_uppercase_conversion(input, expected):
assert input.upper() == expectedUse 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 globally. Write async tests without decorators:
asyncio_mode = "auto"python
undefined本项目全局使用。编写异步测试时无需添加装饰器:
asyncio_mode = "auto"python
undefinedCorrect
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():
...
undefinedImports at module level
导入语句放在模块顶部
Put ALL imports at the top of the file:
python
undefined将所有导入语句放在文件顶部:
python
undefinedCorrect
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
...
undefinedasync def test_something():
from fastmcp import FastMCP # Don't do this
...
undefinedUse 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 for testing JSON schemas and complex structures:
inline-snapshotpython
from inline_snapshot import snapshot
def test_schema_generation():
schema = generate_schema(MyModel)
assert schema == snapshot() # Will auto-populate on first runCommands:
- - populate empty snapshots
pytest --inline-snapshot=create - - update after intentional changes
pytest --inline-snapshot=fix
使用测试JSON schema和复杂结构:
inline-snapshotpython
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 Nonepython
@pytest.fixture
def client():
return Client()
async def test_with_client(client):
result = await client.ping()
assert result is not NoneUse tmp_path
for file operations
tmp_path文件操作使用tmp_path
tmp_pathpython
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
undefinedGood
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():
undefineddef test_login():
def test_update():
def test_delete():
undefinedError 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 testsbash
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 decorators
@pytest.mark.asyncio - 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),除非测试网络功能
- 对同一行为的不同变体使用参数化
- 为不同行为编写独立测试