pytest
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesepytest - Professional Python Testing
pytest - 专业Python测试框架
Overview
概述
pytest is the industry-standard Python testing framework, offering powerful features like fixtures, parametrization, markers, plugins, and seamless integration with FastAPI, Django, and Flask. It provides a simple, scalable approach to testing from unit tests to complex integration scenarios.
Key Features:
- Fixture system for dependency injection
- Parametrization for data-driven tests
- Rich assertion introspection (no need for )
self.assertEqual - Plugin ecosystem (pytest-cov, pytest-asyncio, pytest-mock, pytest-django)
- Async/await support
- Parallel test execution with pytest-xdist
- Test discovery and organization
- Detailed failure reporting
Installation:
bash
undefinedpytest是行业标准的Python测试框架,提供fixtures、参数化、标记(markers)、插件等强大功能,可与FastAPI、Django和Flask无缝集成。它为从单元测试到复杂集成场景的测试提供了一种简单、可扩展的方法。
核心特性:
- Fixture系统用于依赖注入
- 支持数据驱动测试的参数化
- 丰富的断言自省(无需使用)
self.assertEqual - 插件生态系统(pytest-cov、pytest-asyncio、pytest-mock、pytest-django)
- 支持Async/await异步测试
- 借助pytest-xdist实现并行测试执行
- 自动测试发现与组织
- 详细的失败报告
安装:
bash
undefinedBasic pytest
基础pytest安装
pip install pytest
pip install pytest
With common plugins
安装常用插件
pip install pytest pytest-cov pytest-asyncio pytest-mock
pip install pytest pytest-cov pytest-asyncio pytest-mock
For FastAPI testing
用于FastAPI测试
pip install pytest httpx pytest-asyncio
pip install pytest httpx pytest-asyncio
For Django testing
用于Django测试
pip install pytest pytest-django
pip install pytest pytest-django
For async databases
用于异步数据库
pip install pytest-asyncio aiosqlite
undefinedpip install pytest-asyncio aiosqlite
undefinedBasic Testing Patterns
基础测试模式
1. Simple Test Functions
1. 简单测试函数
python
undefinedpython
undefinedtest_math.py
test_math.py
def add(a, b):
return a + b
def test_add():
assert add(2, 3) == 5
assert add(-1, 1) == 0
assert add(0, 0) == 0
def test_add_negative():
assert add(-2, -3) == -5
**Run tests:**
```bashdef add(a, b):
return a + b
def test_add():
assert add(2, 3) == 5
assert add(-1, 1) == 0
assert add(0, 0) == 0
def test_add_negative():
assert add(-2, -3) == -5
**运行测试:**
```bashDiscover and run all tests
发现并运行所有测试
pytest
pytest
Verbose output
详细输出
pytest -v
pytest -v
Show print statements
显示打印语句
pytest -s
pytest -s
Run specific test file
运行指定测试文件
pytest test_math.py
pytest test_math.py
Run specific test function
运行指定测试函数
pytest test_math.py::test_add
undefinedpytest test_math.py::test_add
undefined2. Test Classes for Organization
2. 使用测试类组织测试
python
undefinedpython
undefinedtest_calculator.py
test_calculator.py
class Calculator:
def add(self, a, b):
return a + b
def multiply(self, a, b):
return a * bclass TestCalculator:
def test_add(self):
calc = Calculator()
assert calc.add(2, 3) == 5
def test_multiply(self):
calc = Calculator()
assert calc.multiply(4, 5) == 20
def test_add_negative(self):
calc = Calculator()
assert calc.add(-1, -1) == -2undefinedclass Calculator:
def add(self, a, b):
return a + b
def multiply(self, a, b):
return a * bclass TestCalculator:
def test_add(self):
calc = Calculator()
assert calc.add(2, 3) == 5
def test_multiply(self):
calc = Calculator()
assert calc.multiply(4, 5) == 20
def test_add_negative(self):
calc = Calculator()
assert calc.add(-1, -1) == -2undefined3. Assertions and Expected Failures
3. 断言与预期失败
python
import pytestpython
import pytestTest exception raising
测试异常抛出
def divide(a, b):
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
def test_divide_by_zero():
with pytest.raises(ValueError, match="Cannot divide by zero"):
divide(10, 0)
def test_divide_success():
assert divide(10, 2) == 5.0
def divide(a, b):
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
def test_divide_by_zero():
with pytest.raises(ValueError, match="Cannot divide by zero"):
divide(10, 0)
def test_divide_success():
assert divide(10, 2) == 5.0
Test approximate equality
测试近似相等
def test_float_comparison():
assert 0.1 + 0.2 == pytest.approx(0.3)
def test_float_comparison():
assert 0.1 + 0.2 == pytest.approx(0.3)
Test containment
测试包含关系
def test_list_contains():
result = [1, 2, 3, 4]
assert 3 in result
assert len(result) == 4
undefineddef test_list_contains():
result = [1, 2, 3, 4]
assert 3 in result
assert len(result) == 4
undefinedFixtures - Dependency Injection
Fixtures - 依赖注入
Basic Fixtures
基础Fixtures
python
undefinedpython
undefinedconftest.py
conftest.py
import pytest
@pytest.fixture
def sample_data():
"""Provide sample data for tests."""
return {"name": "Alice", "age": 30, "email": "alice@example.com"}
@pytest.fixture
def empty_list():
"""Provide an empty list."""
return []
import pytest
@pytest.fixture
def sample_data():
"""为测试提供示例数据。"""
return {"name": "Alice", "age": 30, "email": "alice@example.com"}
@pytest.fixture
def empty_list():
"""提供一个空列表。"""
return []
test_fixtures.py
test_fixtures.py
def test_sample_data(sample_data):
assert sample_data["name"] == "Alice"
assert sample_data["age"] == 30
def test_empty_list(empty_list):
empty_list.append(1)
assert len(empty_list) == 1
undefineddef test_sample_data(sample_data):
assert sample_data["name"] == "Alice"
assert sample_data["age"] == 30
def test_empty_list(empty_list):
empty_list.append(1)
assert len(empty_list) == 1
undefinedFixture Scopes
Fixture作用域
python
import pytestpython
import pytestFunction scope (default) - runs for each test
函数作用域(默认)- 每个测试运行一次
@pytest.fixture(scope="function")
def user():
return {"id": 1, "name": "Alice"}
@pytest.fixture(scope="function")
def user():
return {"id": 1, "name": "Alice"}
Class scope - runs once per test class
类作用域 - 每个测试类运行一次
@pytest.fixture(scope="class")
def database():
db = setup_database()
yield db
db.close()
@pytest.fixture(scope="class")
def database():
db = setup_database()
yield db
db.close()
Module scope - runs once per test module
模块作用域 - 每个测试模块运行一次
@pytest.fixture(scope="module")
def api_client():
client = APIClient()
yield client
client.shutdown()
@pytest.fixture(scope="module")
def api_client():
client = APIClient()
yield client
client.shutdown()
Session scope - runs once for entire test session
会话作用域 - 整个测试会话运行一次
@pytest.fixture(scope="session")
def app_config():
return load_config()
undefined@pytest.fixture(scope="session")
def app_config():
return load_config()
undefinedFixture Setup and Teardown
Fixture的设置与清理
python
import pytest
import tempfile
import shutil
@pytest.fixture
def temp_directory():
"""Create a temporary directory for test."""
temp_dir = tempfile.mkdtemp()
print(f"
Setup: Created {temp_dir}")
yield temp_dir # Provide directory to test
# Teardown: cleanup after test
shutil.rmtree(temp_dir)
print(f"
Teardown: Removed {temp_dir}")
def test_file_creation(temp_directory):
file_path = f"{temp_directory}/test.txt"
with open(file_path, "w") as f:
f.write("test content")
assert os.path.exists(file_path)python
import pytest
import tempfile
import shutil
@pytest.fixture
def temp_directory():
"""为测试创建临时目录。"""
temp_dir = tempfile.mkdtemp()
print(f"
设置: 创建了 {temp_dir}")
yield temp_dir # 向测试提供目录
# 清理: 测试后清理
shutil.rmtree(temp_dir)
print(f"
清理: 删除了 {temp_dir}")
def test_file_creation(temp_directory):
file_path = f"{temp_directory}/test.txt"
with open(file_path, "w") as f:
f.write("test content")
assert os.path.exists(file_path)Fixture Dependencies
Fixture依赖
python
import pytest
@pytest.fixture
def database_connection():
"""Database connection."""
conn = connect_to_db()
yield conn
conn.close()
@pytest.fixture
def database_session(database_connection):
"""Database session depends on connection."""
session = create_session(database_connection)
yield session
session.rollback()
session.close()
@pytest.fixture
def user_repository(database_session):
"""User repository depends on session."""
return UserRepository(database_session)
def test_create_user(user_repository):
user = user_repository.create(name="Alice", email="alice@example.com")
assert user.name == "Alice"python
import pytest
@pytest.fixture
def database_connection():
"""数据库连接。"""
conn = connect_to_db()
yield conn
conn.close()
@pytest.fixture
def database_session(database_connection):
"""数据库会话依赖于连接。"""
session = create_session(database_connection)
yield session
session.rollback()
session.close()
@pytest.fixture
def user_repository(database_session):
"""用户仓库依赖于会话。"""
return UserRepository(database_session)
def test_create_user(user_repository):
user = user_repository.create(name="Alice", email="alice@example.com")
assert user.name == "Alice"Parametrization - Data-Driven Testing
参数化 - 数据驱动测试
Basic Parametrization
基础参数化
python
import pytest
@pytest.mark.parametrize("a,b,expected", [
(2, 3, 5),
(5, 7, 12),
(-1, 1, 0),
(0, 0, 0),
(100, 200, 300),
])
def test_add_parametrized(a, b, expected):
assert add(a, b) == expectedpython
import pytest
@pytest.mark.parametrize("a,b,expected", [
(2, 3, 5),
(5, 7, 12),
(-1, 1, 0),
(0, 0, 0),
(100, 200, 300),
])
def test_add_parametrized(a, b, expected):
assert add(a, b) == expectedMultiple Parameters
多参数化
python
@pytest.mark.parametrize("operation,a,b,expected", [
("add", 2, 3, 5),
("subtract", 10, 5, 5),
("multiply", 4, 5, 20),
("divide", 10, 2, 5),
])
def test_calculator_operations(operation, a, b, expected):
calc = Calculator()
result = getattr(calc, operation)(a, b)
assert result == expectedpython
@pytest.mark.parametrize("operation,a,b,expected", [
("add", 2, 3, 5),
("subtract", 10, 5, 5),
("multiply", 4, 5, 20),
("divide", 10, 2, 5),
])
def test_calculator_operations(operation, a, b, expected):
calc = Calculator()
result = getattr(calc, operation)(a, b)
assert result == expectedParametrize with IDs
带ID的参数化
python
@pytest.mark.parametrize("input_data,expected", [
pytest.param({"name": "Alice"}, "Alice", id="valid_name"),
pytest.param({"name": ""}, None, id="empty_name"),
pytest.param({}, None, id="missing_name"),
], ids=lambda x: x if isinstance(x, str) else None)
def test_extract_name(input_data, expected):
result = extract_name(input_data)
assert result == expectedpython
@pytest.mark.parametrize("input_data,expected", [
pytest.param({"name": "Alice"}, "Alice", id="valid_name"),
pytest.param({"name": ""}, None, id="empty_name"),
pytest.param({}, None, id="missing_name"),
], ids=lambda x: x if isinstance(x, str) else None)
def test_extract_name(input_data, expected):
result = extract_name(input_data)
assert result == expectedIndirect Parametrization (Fixtures)
间接参数化(与Fixtures结合)
python
@pytest.fixture
def user_data(request):
"""Create user based on parameter."""
return {"name": request.param, "email": f"{request.param}@example.com"}
@pytest.mark.parametrize("user_data", ["Alice", "Bob", "Charlie"], indirect=True)
def test_user_creation(user_data):
assert "@example.com" in user_data["email"]python
@pytest.fixture
def user_data(request):
"""根据参数创建用户。"""
return {"name": request.param, "email": f"{request.param}@example.com"}
@pytest.mark.parametrize("user_data", ["Alice", "Bob", "Charlie"], indirect=True)
def test_user_creation(user_data):
assert "@example.com" in user_data["email"]Test Markers
测试标记
Built-in Markers
内置标记
python
import pytestpython
import pytestSkip test
跳过测试
@pytest.mark.skip(reason="Not implemented yet")
def test_future_feature():
pass
@pytest.mark.skip(reason="尚未实现")
def test_future_feature():
pass
Skip conditionally
条件跳过
@pytest.mark.skipif(sys.platform == "win32", reason="Unix-only test")
def test_unix_specific():
pass
@pytest.mark.skipif(sys.platform == "win32", reason="仅Unix系统测试")
def test_unix_specific():
pass
Expected failure
预期失败
@pytest.mark.xfail(reason="Known bug #123")
def test_known_bug():
assert False
@pytest.mark.xfail(reason="已知Bug #123")
def test_known_bug():
assert False
Slow test marker
慢测试标记
@pytest.mark.slow
def test_expensive_operation():
time.sleep(5)
assert True
undefined@pytest.mark.slow
def test_expensive_operation():
time.sleep(5)
assert True
undefinedCustom Markers
自定义标记
python
undefinedpython
undefinedpytest.ini
pytest.ini
[pytest]
markers =
slow: marks tests as slow (deselect with '-m "not slow"')
integration: marks tests as integration tests
unit: marks tests as unit tests
smoke: marks tests as smoke tests
[pytest]
markers =
slow: 标记慢测试(使用'-m "not slow"'排除)
integration: 标记集成测试
unit: 标记单元测试
smoke: 标记冒烟测试
test_custom_markers.py
test_custom_markers.py
import pytest
@pytest.mark.unit
def test_fast_unit():
assert True
@pytest.mark.integration
@pytest.mark.slow
def test_slow_integration():
# Integration test with database
pass
@pytest.mark.smoke
def test_critical_path():
# Smoke test for critical functionality
pass
**Run tests by marker:**
```bashimport pytest
@pytest.mark.unit
def test_fast_unit():
assert True
@pytest.mark.integration
@pytest.mark.slow
def test_slow_integration():
# 带数据库的集成测试
pass
@pytest.mark.smoke
def test_critical_path():
# 关键路径的冒烟测试
pass
**按标记运行测试:**
```bashRun only unit tests
仅运行单元测试
pytest -m unit
pytest -m unit
Run all except slow tests
运行所有测试除了慢测试
pytest -m "not slow"
pytest -m "not slow"
Run integration tests
运行集成测试
pytest -m integration
pytest -m integration
Run unit AND integration
运行单元和集成测试
pytest -m "unit or integration"
pytest -m "unit or integration"
Run smoke tests only
仅运行冒烟测试
pytest -m smoke
undefinedpytest -m smoke
undefinedFastAPI Testing
FastAPI测试
Basic FastAPI Test Setup
基础FastAPI测试设置
python
undefinedpython
undefinedapp/main.py
app/main.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
@app.get("/")
def read_root():
return {"message": "Hello World"}
@app.get("/items/{item_id}")
def read_item(item_id: int):
if item_id == 0:
raise HTTPException(status_code=404, detail="Item not found")
return {"item_id": item_id, "name": f"Item {item_id}"}
@app.post("/items")
def create_item(item: Item):
return {"name": item.name, "price": item.price, "id": 123}
undefinedfrom fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
@app.get("/")
def read_root():
return {"message": "Hello World"}
@app.get("/items/{item_id}")
def read_item(item_id: int):
if item_id == 0:
raise HTTPException(status_code=404, detail="Item not found")
return {"item_id": item_id, "name": f"Item {item_id}"}
@app.post("/items")
def create_item(item: Item):
return {"name": item.name, "price": item.price, "id": 123}
undefinedFastAPI Test Client
FastAPI测试客户端
python
undefinedpython
undefinedconftest.py
conftest.py
import pytest
from fastapi.testclient import TestClient
from app.main import app
@pytest.fixture
def client():
"""FastAPI test client."""
return TestClient(app)
import pytest
from fastapi.testclient import TestClient
from app.main import app
@pytest.fixture
def client():
"""FastAPI测试客户端。"""
return TestClient(app)
test_api.py
test_api.py
def test_read_root(client):
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"message": "Hello World"}
def test_read_item(client):
response = client.get("/items/1")
assert response.status_code == 200
assert response.json() == {"item_id": 1, "name": "Item 1"}
def test_read_item_not_found(client):
response = client.get("/items/0")
assert response.status_code == 404
assert response.json() == {"detail": "Item not found"}
def test_create_item(client):
response = client.post(
"/items",
json={"name": "Widget", "price": 9.99}
)
assert response.status_code == 200
data = response.json()
assert data["name"] == "Widget"
assert data["price"] == 9.99
assert "id" in data
undefineddef test_read_root(client):
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"message": "Hello World"}
def test_read_item(client):
response = client.get("/items/1")
assert response.status_code == 200
assert response.json() == {"item_id": 1, "name": "Item 1"}
def test_read_item_not_found(client):
response = client.get("/items/0")
assert response.status_code == 404
assert response.json() == {"detail": "Item not found"}
def test_create_item(client):
response = client.post(
"/items",
json={"name": "Widget", "price": 9.99}
)
assert response.status_code == 200
data = response.json()
assert data["name"] == "Widget"
assert data["price"] == 9.99
assert "id" in data
undefinedAsync FastAPI Testing
异步FastAPI测试
python
undefinedpython
undefinedconftest.py
conftest.py
import pytest
from httpx import AsyncClient
from app.main import app
@pytest.fixture
async def async_client():
"""Async test client for FastAPI."""
async with AsyncClient(app=app, base_url="http://test") as client:
yield client
import pytest
from httpx import AsyncClient
from app.main import app
@pytest.fixture
async def async_client():
"""FastAPI异步测试客户端。"""
async with AsyncClient(app=app, base_url="http://test") as client:
yield client
test_async_api.py
test_async_api.py
import pytest
@pytest.mark.asyncio
async def test_read_root_async(async_client):
response = await async_client.get("/")
assert response.status_code == 200
assert response.json() == {"message": "Hello World"}
@pytest.mark.asyncio
async def test_create_item_async(async_client):
response = await async_client.post(
"/items",
json={"name": "Gadget", "price": 19.99}
)
assert response.status_code == 200
assert response.json()["name"] == "Gadget"
undefinedimport pytest
@pytest.mark.asyncio
async def test_read_root_async(async_client):
response = await async_client.get("/")
assert response.status_code == 200
assert response.json() == {"message": "Hello World"}
@pytest.mark.asyncio
async def test_create_item_async(async_client):
response = await async_client.post(
"/items",
json={"name": "Gadget", "price": 19.99}
)
assert response.status_code == 200
assert response.json()["name"] == "Gadget"
undefinedFastAPI with Database Testing
带数据库的FastAPI测试
python
undefinedpython
undefinedconftest.py
conftest.py
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from app.database import Base, get_db
from app.main import app
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from app.database import Base, get_db
from app.main import app
Test database
测试数据库
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
@pytest.fixture(scope="function")
def test_db():
"""Create test database."""
Base.metadata.create_all(bind=engine)
yield
Base.metadata.drop_all(bind=engine)
@pytest.fixture
def client(test_db):
"""Override database dependency."""
def override_get_db():
try:
db = TestingSessionLocal()
yield db
finally:
db.close()
app.dependency_overrides[get_db] = override_get_db
with TestClient(app) as test_client:
yield test_client
app.dependency_overrides.clear()SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
@pytest.fixture(scope="function")
def test_db():
"""创建测试数据库。"""
Base.metadata.create_all(bind=engine)
yield
Base.metadata.drop_all(bind=engine)
@pytest.fixture
def client(test_db):
"""覆盖数据库依赖。"""
def override_get_db():
try:
db = TestingSessionLocal()
yield db
finally:
db.close()
app.dependency_overrides[get_db] = override_get_db
with TestClient(app) as test_client:
yield test_client
app.dependency_overrides.clear()test_users.py
test_users.py
def test_create_user(client):
response = client.post(
"/users",
json={"email": "test@example.com", "password": "secret"}
)
assert response.status_code == 200
assert response.json()["email"] == "test@example.com"
def test_read_users(client):
# Create user first
client.post("/users", json={"email": "user1@example.com", "password": "pass1"})
client.post("/users", json={"email": "user2@example.com", "password": "pass2"})
# Read users
response = client.get("/users")
assert response.status_code == 200
assert len(response.json()) == 2undefineddef test_create_user(client):
response = client.post(
"/users",
json={"email": "test@example.com", "password": "secret"}
)
assert response.status_code == 200
assert response.json()["email"] == "test@example.com"
def test_read_users(client):
# 先创建用户
client.post("/users", json={"email": "user1@example.com", "password": "pass1"})
client.post("/users", json={"email": "user2@example.com", "password": "pass2"})
# 读取用户
response = client.get("/users")
assert response.status_code == 200
assert len(response.json()) == 2undefinedDjango Testing
Django测试
Django pytest Configuration
Django pytest配置
python
undefinedpython
undefinedpytest.ini
pytest.ini
[pytest]
DJANGO_SETTINGS_MODULE = myproject.settings
python_files = tests.py test_*.py *_tests.py
[pytest]
DJANGO_SETTINGS_MODULE = myproject.settings
python_files = tests.py test_*.py *_tests.py
conftest.py
conftest.py
import pytest
from django.conf import settings
@pytest.fixture(scope='session')
def django_db_setup():
settings.DATABASES['default'] = {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:',
}
undefinedimport pytest
from django.conf import settings
@pytest.fixture(scope='session')
def django_db_setup():
settings.DATABASES['default'] = {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:',
}
undefinedDjango Model Testing
Django模型测试
python
undefinedpython
undefinedmodels.py
models.py
from django.db import models
class User(models.Model):
email = models.EmailField(unique=True)
name = models.CharField(max_length=100)
is_active = models.BooleanField(default=True)
from django.db import models
class User(models.Model):
email = models.EmailField(unique=True)
name = models.CharField(max_length=100)
is_active = models.BooleanField(default=True)
test_models.py
test_models.py
import pytest
from myapp.models import User
@pytest.mark.django_db
def test_create_user():
user = User.objects.create(
email="test@example.com",
name="Test User"
)
assert user.email == "test@example.com"
assert user.is_active is True
@pytest.mark.django_db
def test_user_unique_email():
User.objects.create(email="test@example.com", name="User 1")
with pytest.raises(Exception): # IntegrityError
User.objects.create(email="test@example.com", name="User 2")undefinedimport pytest
from myapp.models import User
@pytest.mark.django_db
def test_create_user():
user = User.objects.create(
email="test@example.com",
name="Test User"
)
assert user.email == "test@example.com"
assert user.is_active is True
@pytest.mark.django_db
def test_user_unique_email():
User.objects.create(email="test@example.com", name="User 1")
with pytest.raises(Exception): # 完整性错误
User.objects.create(email="test@example.com", name="User 2")undefinedDjango View Testing
Django视图测试
python
undefinedpython
undefinedviews.py
views.py
from django.http import JsonResponse
from django.views import View
class UserListView(View):
def get(self, request):
users = User.objects.all()
return JsonResponse({
"users": list(users.values("id", "email", "name"))
})
from django.http import JsonResponse
from django.views import View
class UserListView(View):
def get(self, request):
users = User.objects.all()
return JsonResponse({
"users": list(users.values("id", "email", "name"))
})
test_views.py
test_views.py
import pytest
from django.test import Client
from myapp.models import User
@pytest.fixture
def client():
return Client()
@pytest.mark.django_db
def test_user_list_view(client):
# Create test data
User.objects.create(email="user1@example.com", name="User 1")
User.objects.create(email="user2@example.com", name="User 2")
# Test view
response = client.get("/users/")
assert response.status_code == 200
data = response.json()
assert len(data["users"]) == 2undefinedimport pytest
from django.test import Client
from myapp.models import User
@pytest.fixture
def client():
return Client()
@pytest.mark.django_db
def test_user_list_view(client):
# 创建测试数据
User.objects.create(email="user1@example.com", name="User 1")
User.objects.create(email="user2@example.com", name="User 2")
# 测试视图
response = client.get("/users/")
assert response.status_code == 200
data = response.json()
assert len(data["users"]) == 2undefinedDjango REST Framework Testing
Django REST Framework测试
python
undefinedpython
undefinedserializers.py
serializers.py
from rest_framework import serializers
from myapp.models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'email', 'name', 'is_active']
from rest_framework import serializers
from myapp.models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'email', 'name', 'is_active']
views.py
views.py
from rest_framework import viewsets
from myapp.models import User
from myapp.serializers import UserSerializer
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
from rest_framework import viewsets
from myapp.models import User
from myapp.serializers import UserSerializer
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
test_api.py
test_api.py
import pytest
from rest_framework.test import APIClient
from myapp.models import User
@pytest.fixture
def api_client():
return APIClient()
@pytest.mark.django_db
def test_list_users(api_client):
User.objects.create(email="user1@example.com", name="User 1")
User.objects.create(email="user2@example.com", name="User 2")
response = api_client.get("/api/users/")
assert response.status_code == 200
assert len(response.data) == 2@pytest.mark.django_db
def test_create_user(api_client):
data = {"email": "new@example.com", "name": "New User"}
response = api_client.post("/api/users/", data)
assert response.status_code == 201
assert User.objects.filter(email="new@example.com").exists()undefinedimport pytest
from rest_framework.test import APIClient
from myapp.models import User
@pytest.fixture
def api_client():
return APIClient()
@pytest.mark.django_db
def test_list_users(api_client):
User.objects.create(email="user1@example.com", name="User 1")
User.objects.create(email="user2@example.com", name="User 2")
response = api_client.get("/api/users/")
assert response.status_code == 200
assert len(response.data) == 2@pytest.mark.django_db
def test_create_user(api_client):
data = {"email": "new@example.com", "name": "New User"}
response = api_client.post("/api/users/", data)
assert response.status_code == 201
assert User.objects.filter(email="new@example.com").exists()undefinedMocking and Patching
模拟与补丁
pytest-mock (pytest.fixture.mocker)
pytest-mock(pytest.fixture.mocker)
python
undefinedpython
undefinedInstall: pip install pytest-mock
安装: pip install pytest-mock
service.py
service.py
import requests
def get_user_data(user_id):
response = requests.get(f"https://api.example.com/users/{user_id}")
return response.json()
import requests
def get_user_data(user_id):
response = requests.get(f"https://api.example.com/users/{user_id}")
return response.json()
test_service.py
test_service.py
def test_get_user_data(mocker):
# Mock requests.get
mock_response = mocker.Mock()
mock_response.json.return_value = {"id": 1, "name": "Alice"}
mocker.patch("requests.get", return_value=mock_response)
result = get_user_data(1)
assert result["name"] == "Alice"undefineddef test_get_user_data(mocker):
# 模拟requests.get
mock_response = mocker.Mock()
mock_response.json.return_value = {"id": 1, "name": "Alice"}
mocker.patch("requests.get", return_value=mock_response)
result = get_user_data(1)
assert result["name"] == "Alice"undefinedMocking Class Methods
模拟类方法
python
class UserService:
def get_user(self, user_id):
# Database call
return database.fetch_user(user_id)
def get_user_name(self, user_id):
user = self.get_user(user_id)
return user["name"]
def test_get_user_name(mocker):
service = UserService()
# Mock the get_user method
mocker.patch.object(
service,
"get_user",
return_value={"id": 1, "name": "Alice"}
)
result = service.get_user_name(1)
assert result == "Alice"python
class UserService:
def get_user(self, user_id):
# 数据库调用
return database.fetch_user(user_id)
def get_user_name(self, user_id):
user = self.get_user(user_id)
return user["name"]
def test_get_user_name(mocker):
service = UserService()
# 模拟get_user方法
mocker.patch.object(
service,
"get_user",
return_value={"id": 1, "name": "Alice"}
)
result = service.get_user_name(1)
assert result == "Alice"Mocking with Side Effects
带副作用的模拟
python
def test_retry_on_failure(mocker):
# First call fails, second succeeds
mock_api = mocker.patch("requests.get")
mock_api.side_effect = [
requests.exceptions.Timeout(), # First call
mocker.Mock(json=lambda: {"status": "ok"}) # Second call
]
result = api_call_with_retry()
assert result["status"] == "ok"
assert mock_api.call_count == 2python
def test_retry_on_failure(mocker):
# 第一次调用失败,第二次成功
mock_api = mocker.patch("requests.get")
mock_api.side_effect = [
requests.exceptions.Timeout(), # 第一次调用
mocker.Mock(json=lambda: {"status": "ok"}) # 第二次调用
]
result = api_call_with_retry()
assert result["status"] == "ok"
assert mock_api.call_count == 2Spy on Calls
监控调用
python
def test_function_called_correctly(mocker):
spy = mocker.spy(module, "function_name")
# Call code that uses the function
module.run_workflow()
# Verify it was called
assert spy.call_count == 1
spy.assert_called_once_with(arg1="value", arg2=42)python
def test_function_called_correctly(mocker):
spy = mocker.spy(module, "function_name")
# 调用使用该函数的代码
module.run_workflow()
# 验证是否被调用
assert spy.call_count == 1
spy.assert_called_once_with(arg1="value", arg2=42)Coverage and Reporting
覆盖率与报告
pytest-cov Configuration
pytest-cov配置
bash
undefinedbash
undefinedInstall
安装
pip install pytest-cov
pip install pytest-cov
Run with coverage
带覆盖率运行测试
pytest --cov=app --cov-report=html --cov-report=term
pytest --cov=app --cov-report=html --cov-report=term
Generate coverage report
生成覆盖率报告
pytest --cov=app --cov-report=term-missing
pytest --cov=app --cov-report=term-missing
Coverage with minimum threshold
带最小阈值的覆盖率
pytest --cov=app --cov-fail-under=80
undefinedpytest --cov=app --cov-fail-under=80
undefinedpytest.ini Coverage Configuration
pytest.ini覆盖率配置
ini
undefinedini
undefinedpytest.ini
pytest.ini
[pytest]
addopts =
--cov=app
--cov-report=html
--cov-report=term-missing
--cov-fail-under=80
-v
testpaths = tests
python_files = test_.py
python_classes = Test
python_functions = test_*
undefined[pytest]
addopts =
--cov=app
--cov-report=html
--cov-report=term-missing
--cov-fail-under=80
-v
testpaths = tests
python_files = test_.py
python_classes = Test
python_functions = test_*
undefinedCoverage Reports
覆盖率报告
bash
undefinedbash
undefinedHTML report (opens in browser)
HTML报告(在浏览器中打开)
pytest --cov=app --cov-report=html
open htmlcov/index.html
pytest --cov=app --cov-report=html
open htmlcov/index.html
Terminal report with missing lines
带缺失行的终端报告
pytest --cov=app --cov-report=term-missing
pytest --cov=app --cov-report=term-missing
XML report (for CI/CD)
XML报告(用于CI/CD)
pytest --cov=app --cov-report=xml
pytest --cov=app --cov-report=xml
JSON report
JSON报告
pytest --cov=app --cov-report=json
undefinedpytest --cov=app --cov-report=json
undefinedAsync Testing
异步测试
pytest-asyncio
pytest-asyncio
python
undefinedpython
undefinedInstall: pip install pytest-asyncio
安装: pip install pytest-asyncio
conftest.py
conftest.py
import pytest
import pytest
Enable asyncio mode
启用asyncio模式
pytest_plugins = ('pytest_asyncio',)
pytest_plugins = ('pytest_asyncio',)
async_service.py
async_service.py
import asyncio
import aiohttp
async def fetch_data(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.json()
import asyncio
import aiohttp
async def fetch_data(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.json()
test_async_service.py
test_async_service.py
import pytest
@pytest.mark.asyncio
async def test_fetch_data(mocker):
# Mock aiohttp response
mock_response = mocker.AsyncMock()
mock_response.json.return_value = {"data": "test"}
mock_session = mocker.AsyncMock()
mock_session.__aenter__.return_value.get.return_value.__aenter__.return_value = mock_response
mocker.patch("aiohttp.ClientSession", return_value=mock_session)
result = await fetch_data("https://api.example.com/data")
assert result["data"] == "test"undefinedimport pytest
@pytest.mark.asyncio
async def test_fetch_data(mocker):
# 模拟aiohttp响应
mock_response = mocker.AsyncMock()
mock_response.json.return_value = {"data": "test"}
mock_session = mocker.AsyncMock()
mock_session.__aenter__.return_value.get.return_value.__aenter__.return_value = mock_response
mocker.patch("aiohttp.ClientSession", return_value=mock_session)
result = await fetch_data("https://api.example.com/data")
assert result["data"] == "test"undefinedAsync Fixtures
异步Fixtures
python
@pytest.fixture
async def async_db_session():
"""Async database session."""
async with async_engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
async with AsyncSession(async_engine) as session:
yield session
async with async_engine.begin() as conn:
await conn.run_sync(Base.metadata.drop_all)
@pytest.mark.asyncio
async def test_create_user_async(async_db_session):
user = User(email="test@example.com", name="Test")
async_db_session.add(user)
await async_db_session.commit()
result = await async_db_session.execute(
select(User).where(User.email == "test@example.com")
)
assert result.scalar_one().name == "Test"python
@pytest.fixture
async def async_db_session():
"""异步数据库会话。"""
async with async_engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
async with AsyncSession(async_engine) as session:
yield session
async with async_engine.begin() as conn:
await conn.run_sync(Base.metadata.drop_all)
@pytest.mark.asyncio
async def test_create_user_async(async_db_session):
user = User(email="test@example.com", name="Test")
async_db_session.add(user)
await async_db_session.commit()
result = await async_db_session.execute(
select(User).where(User.email == "test@example.com")
)
assert result.scalar_one().name == "Test"Local pytest Profiles (Your Repos)
本地pytest配置文件(你的仓库)
Common settings from your projects' :
pyproject.toml- (default in mcp-browser, mcp-memory, claude-mpm, edgar)
asyncio_mode = "auto" - includes
addoptsand--strict-markersfor CI consistency--strict-config - Coverage flags: ,
--cov=<package>,--cov-report=term-missing--cov-report=xml - Selective ignores (mcp-vector-search): ,
--ignore=tests/manual--ignore=tests/e2e - for editable import resolution (mcp-ticketer)
pythonpath = ["src"]
Typical markers:
- ,
unit,integratione2e - ,
slow,benchmarkperformance - (edgar)
requires_api
Reference: see in , , , , and for full lists.
pyproject.tomlclaude-mpmedgarmcp-vector-searchmcp-ticketerkuzu-memory来自项目的常见设置:
pyproject.toml- (mcp-browser、mcp-memory、claude-mpm、edgar中的默认设置)
asyncio_mode = "auto" - 包含
addopts和--strict-markers以确保CI一致性--strict-config - 覆盖率标志:、
--cov=<package>、--cov-report=term-missing--cov-report=xml - 选择性忽略(mcp-vector-search):、
--ignore=tests/manual--ignore=tests/e2e - 用于可编辑导入解析(mcp-ticketer)
pythonpath = ["src"]
典型标记:
- 、
unit、integratione2e - 、
slow、benchmarkperformance - (edgar)
requires_api
参考:查看、、、和中的获取完整列表。
claude-mpmedgarmcp-vector-searchmcp-ticketerkuzu-memorypyproject.tomlBest Practices
最佳实践
1. Test Organization
1. 测试组织
project/
├── app/
│ ├── __init__.py
│ ├── main.py
│ ├── models.py
│ └── services.py
├── tests/
│ ├── __init__.py
│ ├── conftest.py # Shared fixtures
│ ├── test_models.py # Model tests
│ ├── test_services.py # Service tests
│ ├── test_api.py # API tests
│ └── integration/
│ ├── __init__.py
│ └── test_workflows.py
└── pytest.iniproject/
├── app/
│ ├── __init__.py
│ ├── main.py
│ ├── models.py
│ └── services.py
├── tests/
│ ├── __init__.py
│ ├── conftest.py # 共享Fixtures
│ ├── test_models.py # 模型测试
│ ├── test_services.py # 服务测试
│ ├── test_api.py # API测试
│ └── integration/
│ ├── __init__.py
│ └── test_workflows.py
└── pytest.ini2. Naming Conventions
2. 命名约定
python
undefinedpython
undefined✅ GOOD: Clear test names
✅ 良好:清晰的测试名称
def test_user_creation_with_valid_email():
pass
def test_user_creation_raises_error_for_duplicate_email():
pass
def test_user_creation_with_valid_email():
pass
def test_user_creation_raises_error_for_duplicate_email():
pass
❌ BAD: Vague names
❌ 糟糕:模糊的名称
def test_user1():
pass
def test_case2():
pass
undefineddef test_user1():
pass
def test_case2():
pass
undefined3. Arrange-Act-Assert Pattern
3. 准备-执行-断言模式
python
def test_user_service_creates_user():
# Arrange: Setup test data and dependencies
service = UserService(database=mock_db)
user_data = {"email": "test@example.com", "name": "Test"}
# Act: Perform the action being tested
result = service.create_user(user_data)
# Assert: Verify the outcome
assert result.email == "test@example.com"
assert result.id is not Nonepython
def test_user_service_creates_user():
# 准备:设置测试数据和依赖
service = UserService(database=mock_db)
user_data = {"email": "test@example.com", "name": "Test"}
# 执行:执行被测试的操作
result = service.create_user(user_data)
# 断言:验证结果
assert result.email == "test@example.com"
assert result.id is not None4. Use Fixtures for Common Setup
4. 使用Fixtures处理通用设置
python
undefinedpython
undefined❌ BAD: Repeated setup
❌ 糟糕:重复的设置
def test_user_creation():
db = setup_database()
user = create_user(db)
assert user.id is not None
db.close()
def test_user_deletion():
db = setup_database()
user = create_user(db)
delete_user(db, user.id)
db.close()
def test_user_creation():
db = setup_database()
user = create_user(db)
assert user.id is not None
db.close()
def test_user_deletion():
db = setup_database()
user = create_user(db)
delete_user(db, user.id)
db.close()
✅ GOOD: Fixture-based setup
✅ 良好:基于Fixture的设置
@pytest.fixture
def db():
database = setup_database()
yield database
database.close()
@pytest.fixture
def user(db):
return create_user(db)
def test_user_creation(user):
assert user.id is not None
def test_user_deletion(db, user):
delete_user(db, user.id)
assert not user_exists(db, user.id)
undefined@pytest.fixture
def db():
database = setup_database()
yield database
database.close()
@pytest.fixture
def user(db):
return create_user(db)
def test_user_creation(user):
assert user.id is not None
def test_user_deletion(db, user):
delete_user(db, user.id)
assert not user_exists(db, user.id)
undefined5. Parametrize Similar Tests
5. 对相似测试使用参数化
python
undefinedpython
undefined❌ BAD: Duplicate test code
❌ 糟糕:重复的测试代码
def test_add_positive():
assert add(2, 3) == 5
def test_add_negative():
assert add(-2, -3) == -5
def test_add_zero():
assert add(0, 0) == 0
def test_add_positive():
assert add(2, 3) == 5
def test_add_negative():
assert add(-2, -3) == -5
def test_add_zero():
assert add(0, 0) == 0
✅ GOOD: Parametrized tests
✅ 良好:参数化测试
@pytest.mark.parametrize("a,b,expected", [
(2, 3, 5),
(-2, -3, -5),
(0, 0, 0),
])
def test_add(a, b, expected):
assert add(a, b) == expected
undefined@pytest.mark.parametrize("a,b,expected", [
(2, 3, 5),
(-2, -3, -5),
(0, 0, 0),
])
def test_add(a, b, expected):
assert add(a, b) == expected
undefined6. Test One Thing Per Test
6. 每个测试只测试一件事
python
undefinedpython
undefined❌ BAD: Testing multiple things
❌ 糟糕:测试多个内容
def test_user_workflow():
user = create_user()
assert user.id is not None
updated = update_user(user.id, name="New Name")
assert updated.name == "New Name"
deleted = delete_user(user.id)
assert deleted is Truedef test_user_workflow():
user = create_user()
assert user.id is not None
updated = update_user(user.id, name="New Name")
assert updated.name == "New Name"
deleted = delete_user(user.id)
assert deleted is True✅ GOOD: Separate tests
✅ 良好:分离的测试
def test_user_creation():
user = create_user()
assert user.id is not None
def test_user_update():
user = create_user()
updated = update_user(user.id, name="New Name")
assert updated.name == "New Name"
def test_user_deletion():
user = create_user()
result = delete_user(user.id)
assert result is True
undefineddef test_user_creation():
user = create_user()
assert user.id is not None
def test_user_update():
user = create_user()
updated = update_user(user.id, name="New Name")
assert updated.name == "New Name"
def test_user_deletion():
user = create_user()
result = delete_user(user.id)
assert result is True
undefined7. Use Markers for Test Organization
7. 使用标记组织测试
python
@pytest.mark.unit
def test_pure_function():
pass
@pytest.mark.integration
@pytest.mark.slow
def test_database_integration():
pass
@pytest.mark.smoke
def test_critical_path():
passpython
@pytest.mark.unit
def test_pure_function():
pass
@pytest.mark.integration
@pytest.mark.slow
def test_database_integration():
pass
@pytest.mark.smoke
def test_critical_path():
pass8. Mock External Dependencies
8. 模拟外部依赖
python
undefinedpython
undefined✅ GOOD: Mock external API
✅ 良好:模拟外部API
def test_fetch_user_data(mocker):
mocker.patch("requests.get", return_value=mock_response)
result = fetch_user_data(user_id=1)
assert result["name"] == "Alice"
def test_fetch_user_data(mocker):
mocker.patch("requests.get", return_value=mock_response)
result = fetch_user_data(user_id=1)
assert result["name"] == "Alice"
❌ BAD: Real API call in test
❌ 糟糕:测试中调用真实API
def test_fetch_user_data():
result = fetch_user_data(user_id=1) # Real HTTP request!
assert result["name"] == "Alice"
undefineddef test_fetch_user_data():
result = fetch_user_data(user_id=1) # 真实HTTP请求!
assert result["name"] == "Alice"
undefinedCommon Pitfalls
常见陷阱
❌ Anti-Pattern 1: Test Depends on Execution Order
❌ 反模式1:测试依赖执行顺序
python
undefinedpython
undefinedWRONG: Tests should be independent
错误:测试应该独立
class TestUserWorkflow:
user_id = None
def test_create_user(self):
user = create_user()
TestUserWorkflow.user_id = user.id
def test_update_user(self):
# Fails if test_create_user didn't run first!
update_user(TestUserWorkflow.user_id, name="New")
**Correct:**
```python
@pytest.fixture
def created_user():
return create_user()
def test_create_user(created_user):
assert created_user.id is not None
def test_update_user(created_user):
update_user(created_user.id, name="New")class TestUserWorkflow:
user_id = None
def test_create_user(self):
user = create_user()
TestUserWorkflow.user_id = user.id
def test_update_user(self):
# 如果test_create_user没有先运行就会失败!
update_user(TestUserWorkflow.user_id, name="New")
**正确做法:**
```python
@pytest.fixture
def created_user():
return create_user()
def test_create_user(created_user):
assert created_user.id is not None
def test_update_user(created_user):
update_user(created_user.id, name="New")❌ Anti-Pattern 2: Not Cleaning Up Resources
❌ 反模式2:不清理资源
python
undefinedpython
undefinedWRONG: Database not cleaned up
错误:数据库未清理
def test_user_creation():
db = setup_database()
user = create_user(db)
assert user.id is not None
# Database connection not closed!
**Correct:**
```python
@pytest.fixture
def db():
database = setup_database()
yield database
database.close() # Cleanupdef test_user_creation():
db = setup_database()
user = create_user(db)
assert user.id is not None
# 数据库连接未关闭!
**正确做法:**
```python
@pytest.fixture
def db():
database = setup_database()
yield database
database.close() # 清理❌ Anti-Pattern 3: Testing Implementation Details
❌ 反模式3:测试实现细节
python
undefinedpython
undefinedWRONG: Testing internal implementation
错误:测试内部实现
def test_user_service_uses_cache():
service = UserService()
service.get_user(1)
assert service._cache.has_key(1) # Testing internal cache!
**Correct:**
```pythondef test_user_service_uses_cache():
service = UserService()
service.get_user(1)
assert service._cache.has_key(1) # 测试内部缓存!
**正确做法:**
```pythonTest behavior, not implementation
测试行为,而非实现
def test_user_service_returns_user():
service = UserService()
user = service.get_user(1)
assert user.id == 1
undefineddef test_user_service_returns_user():
service = UserService()
user = service.get_user(1)
assert user.id == 1
undefined❌ Anti-Pattern 4: Not Using pytest Features
❌ 反模式4:不使用pytest特性
python
undefinedpython
undefinedWRONG: Using unittest assertions
错误:使用unittest断言
import unittest
def test_addition():
result = add(2, 3)
unittest.TestCase().assertEqual(result, 5)
**Correct:**
```pythonimport unittest
def test_addition():
result = add(2, 3)
unittest.TestCase().assertEqual(result, 5)
**正确做法:**
```pythonUse pytest's rich assertions
使用pytest的丰富断言
def test_addition():
assert add(2, 3) == 5
undefineddef test_addition():
assert add(2, 3) == 5
undefined❌ Anti-Pattern 5: Overly Complex Fixtures
❌ 反模式5:过于复杂的Fixtures
python
undefinedpython
undefinedWRONG: Fixture does too much
错误:Fixture做太多事情
@pytest.fixture
def everything():
db = setup_db()
user = create_user(db)
session = login(user)
cache = setup_cache()
# ... too many things!
return {"db": db, "user": user, "session": session, "cache": cache}
**Correct:**
```python@pytest.fixture
def everything():
db = setup_db()
user = create_user(db)
session = login(user)
cache = setup_cache()
# ... 太多内容!
return {"db": db, "user": user, "session": session, "cache": cache}
**正确做法:**
```pythonSeparate, composable fixtures
分离的、可组合的Fixtures
@pytest.fixture
def db():
return setup_db()
@pytest.fixture
def user(db):
return create_user(db)
@pytest.fixture
def session(user):
return login(user)
undefined@pytest.fixture
def db():
return setup_db()
@pytest.fixture
def user(db):
return create_user(db)
@pytest.fixture
def session(user):
return login(user)
undefinedQuick Reference
快速参考
Common Commands
常用命令
bash
undefinedbash
undefinedRun all tests
运行所有测试
pytest
pytest
Verbose output
详细输出
pytest -v
pytest -v
Show print statements
显示打印语句
pytest -s
pytest -s
Run specific file
运行指定文件
pytest tests/test_api.py
pytest tests/test_api.py
Run specific test
运行指定测试
pytest tests/test_api.py::test_create_user
pytest tests/test_api.py::test_create_user
Run by marker
按标记运行
pytest -m unit
pytest -m "not slow"
pytest -m unit
pytest -m "not slow"
Run with coverage
带覆盖率运行
pytest --cov=app --cov-report=html
pytest --cov=app --cov-report=html
Parallel execution
并行执行
pytest -n auto # Requires pytest-xdist
pytest -n auto # 需要pytest-xdist
Stop on first failure
遇到第一个失败就停止
pytest -x
pytest -x
Show local variables on failure
失败时显示局部变量
pytest -l
pytest -l
Run last failed tests
运行上次失败的测试
pytest --lf
pytest --lf
Run failed tests first
先运行失败的测试
pytest --ff
undefinedpytest --ff
undefinedpytest.ini Template
pytest.ini模板
ini
[pytest]ini
[pytest]Minimum pytest version
最低pytest版本
minversion = 7.0
minversion = 7.0
Test discovery patterns
测试发现模式
python_files = test_.py _test.py
python_classes = Test
python_functions = test_
python_files = test_.py _test.py
python_classes = Test
python_functions = test_
Test paths
测试路径
testpaths = tests
testpaths = tests
Command line options
命令行选项
addopts =
-v
--strict-markers
--cov=app
--cov-report=html
--cov-report=term-missing
--cov-fail-under=80
addopts =
-v
--strict-markers
--cov=app
--cov-report=html
--cov-report=term-missing
--cov-fail-under=80
Markers
标记
markers =
unit: Unit tests
integration: Integration tests
slow: Slow-running tests
smoke: Smoke tests for critical paths
markers =
unit: 单元测试
integration: 集成测试
slow: 慢测试
smoke: 关键路径冒烟测试
Django settings (if using Django)
Django设置(如果使用Django)
DJANGO_SETTINGS_MODULE = myproject.settings
DJANGO_SETTINGS_MODULE = myproject.settings
Asyncio mode
Asyncio模式
asyncio_mode = auto
undefinedasyncio_mode = auto
undefinedconftest.py Template
conftest.py模板
python
undefinedpython
undefinedconftest.py
conftest.py
import pytest
from fastapi.testclient import TestClient
from app.main import app
import pytest
from fastapi.testclient import TestClient
from app.main import app
FastAPI client fixture
FastAPI客户端Fixture
@pytest.fixture
def client():
return TestClient(app)
@pytest.fixture
def client():
return TestClient(app)
Database fixture
数据库Fixture
@pytest.fixture(scope="function")
def db():
database = setup_test_database()
yield database
database.close()
@pytest.fixture(scope="function")
def db():
database = setup_test_database()
yield database
database.close()
Mock user fixture
模拟用户Fixture
@pytest.fixture
def mock_user():
return {"id": 1, "email": "test@example.com", "name": "Test User"}
@pytest.fixture
def mock_user():
return {"id": 1, "email": "test@example.com", "name": "Test User"}
Custom pytest configuration
自定义pytest配置
def pytest_configure(config):
config.addinivalue_line("markers", "api: API tests")
config.addinivalue_line("markers", "db: Database tests")
undefineddef pytest_configure(config):
config.addinivalue_line("markers", "api: API测试")
config.addinivalue_line("markers", "db: 数据库测试")
undefinedResources
资源
- Official Documentation: https://docs.pytest.org/
- pytest-asyncio: https://pytest-asyncio.readthedocs.io/
- pytest-cov: https://pytest-cov.readthedocs.io/
- pytest-mock: https://pytest-mock.readthedocs.io/
- pytest-django: https://pytest-django.readthedocs.io/
- FastAPI Testing: https://fastapi.tiangolo.com/tutorial/testing/
- 官方文档: https://docs.pytest.org/
- pytest-asyncio: https://pytest-asyncio.readthedocs.io/
- pytest-cov: https://pytest-cov.readthedocs.io/
- pytest-mock: https://pytest-mock.readthedocs.io/
- pytest-django: https://pytest-django.readthedocs.io/
- FastAPI测试: https://fastapi.tiangolo.com/tutorial/testing/
Related Skills
相关技能
When using pytest, consider these complementary skills:
- fastapi-local-dev: FastAPI development server patterns and test fixtures
- test-driven-development: Complete TDD workflow (RED/GREEN/REFACTOR cycle)
- systematic-debugging: Root cause investigation for failing tests
使用pytest时,可考虑这些互补技能:
- fastapi-local-dev: FastAPI开发服务器模式和测试Fixtures
- test-driven-development: 完整的TDD工作流(RED/GREEN/REFACTOR循环)
- systematic-debugging: 失败测试的根本原因调查
Quick TDD Workflow Reference (Inlined for Standalone Use)
快速TDD工作流参考(独立使用)
RED → GREEN → REFACTOR Cycle:
-
RED Phase: Write Failing Testpython
def test_should_authenticate_user_when_credentials_valid(): # Test that describes desired behavior user = User(username='alice', password='secret123') result = authenticate(user) assert result.is_authenticated is True # This test will fail because authenticate() doesn't exist yet -
GREEN Phase: Make It Passpython
def authenticate(user): # Minimum code to pass the test if user.username == 'alice' and user.password == 'secret123': return AuthResult(is_authenticated=True) return AuthResult(is_authenticated=False) -
REFACTOR Phase: Improve Codepython
def authenticate(user): # Clean up while keeping tests green hashed_password = hash_password(user.password) stored_user = database.get_user(user.username) return AuthResult( is_authenticated=(stored_user.password_hash == hashed_password) )
Test Structure: Arrange-Act-Assert (AAA)
python
def test_user_creation():
# Arrange: Set up test data
user_data = {'username': 'alice', 'email': 'alice@example.com'}
# Act: Perform the action
user = create_user(user_data)
# Assert: Verify outcome
assert user.username == 'alice'
assert user.email == 'alice@example.com'RED → GREEN → REFACTOR循环:
-
RED阶段:编写失败的测试python
def test_should_authenticate_user_when_credentials_valid(): # 描述期望行为的测试 user = User(username='alice', password='secret123') result = authenticate(user) assert result.is_authenticated is True # 这个测试会失败,因为authenticate()还不存在 -
GREEN阶段:让测试通过python
def authenticate(user): # 编写最少代码让测试通过 if user.username == 'alice' and user.password == 'secret123': return AuthResult(is_authenticated=True) return AuthResult(is_authenticated=False) -
REFACTOR阶段:改进代码python
def authenticate(user): # 清理代码同时保持测试通过 hashed_password = hash_password(user.password) stored_user = database.get_user(user.username) return AuthResult( is_authenticated=(stored_user.password_hash == hashed_password) )
测试结构:准备-执行-断言(AAA)
python
def test_user_creation():
# 准备:设置测试数据
user_data = {'username': 'alice', 'email': 'alice@example.com'}
# 执行:执行操作
user = create_user(user_data)
# 断言:验证结果
assert user.username == 'alice'
assert user.email == 'alice@example.com'Quick Debugging Reference (Inlined for Standalone Use)
快速调试参考(独立使用)
Phase 1: Root Cause Investigation
- Read error messages completely (stack traces, line numbers)
- Reproduce consistently (document exact steps)
- Check recent changes (git log, git diff)
- Understand what changed and why it might cause failure
Phase 2: Isolate the Problem
python
undefined阶段1:根本原因调查
- 完整阅读错误信息(堆栈跟踪、行号)
- 一致重现问题(记录确切步骤)
- 检查最近的变更(git log、git diff)
- 理解变更内容及可能导致失败的原因
阶段2:隔离问题
python
undefinedUse pytest's built-in debugging
使用pytest内置调试
pytest tests/test_auth.py -vv --pdb # Drop into debugger on failure
pytest tests/test_auth.py -x # Stop on first failure
pytest tests/test_auth.py -k "auth" # Run only auth-related tests
pytest tests/test_auth.py -vv --pdb # 失败时进入调试器
pytest tests/test_auth.py -x # 遇到第一个失败就停止
pytest tests/test_auth.py -k "auth" # 仅运行与auth相关的测试
Add strategic print/logging
添加策略性打印/日志
def test_complex_workflow():
user = create_user({'username': 'test'})
print(f"DEBUG: Created user {user.id}") # Visible with pytest -s
result = process_user(user)
print(f"DEBUG: Result status {result.status}")
assert result.success
**Phase 3: Fix Root Cause**
- Fix the underlying problem, not symptoms
- Add regression test to prevent recurrence
- Verify fix doesn't break other tests
**Phase 4: Verify Solution**
```bashdef test_complex_workflow():
user = create_user({'username': 'test'})
print(f"调试:创建用户 {user.id}") # 使用pytest -s可见
result = process_user(user)
print(f"调试:结果状态 {result.status}")
assert result.success
**阶段3:修复根本原因**
- 修复底层问题,而非症状
- 添加回归测试防止再次出现
- 验证修复不会破坏其他测试
**阶段4:验证解决方案**
```bashRun full test suite
运行完整测试套件
pytest
pytest
Run with coverage
带覆盖率运行
pytest --cov=src --cov-report=html
pytest --cov=src --cov-report=html
Verify specific test patterns
验证特定测试模式
pytest -k "auth or login" -v
[Full TDD and debugging workflows available in respective skills if deployed together]
---
**pytest Version Compatibility:** This skill covers pytest 7.0+ and reflects current best practices for Python testing in 2025.pytest -k "auth or login" -v
[完整的TDD和调试工作流在相关技能中提供(如果一起部署)]
---
**pytest版本兼容性:** 本技能覆盖pytest 7.0+,反映了2025年Python测试的当前最佳实践。