pytest

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

pytest - 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
undefined
pytest是行业标准的Python测试框架,提供fixtures、参数化、标记(markers)、插件等强大功能,可与FastAPI、Django和Flask无缝集成。它为从单元测试到复杂集成场景的测试提供了一种简单、可扩展的方法。
核心特性:
  • Fixture系统用于依赖注入
  • 支持数据驱动测试的参数化
  • 丰富的断言自省(无需使用
    self.assertEqual
  • 插件生态系统(pytest-cov、pytest-asyncio、pytest-mock、pytest-django)
  • 支持Async/await异步测试
  • 借助pytest-xdist实现并行测试执行
  • 自动测试发现与组织
  • 详细的失败报告
安装:
bash
undefined

Basic 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
undefined
pip install pytest-asyncio aiosqlite
undefined

Basic Testing Patterns

基础测试模式

1. Simple Test Functions

1. 简单测试函数

python
undefined
python
undefined

test_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:**
```bash
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

**运行测试:**
```bash

Discover 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
undefined
pytest test_math.py::test_add
undefined

2. Test Classes for Organization

2. 使用测试类组织测试

python
undefined
python
undefined

test_calculator.py

test_calculator.py

class Calculator: def add(self, a, b): return a + b
def multiply(self, a, b):
    return a * b
class 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) == -2
undefined
class Calculator: def add(self, a, b): return a + b
def multiply(self, a, b):
    return a * b
class 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) == -2
undefined

3. Assertions and Expected Failures

3. 断言与预期失败

python
import pytest
python
import pytest

Test 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
undefined
def test_list_contains(): result = [1, 2, 3, 4] assert 3 in result assert len(result) == 4
undefined

Fixtures - Dependency Injection

Fixtures - 依赖注入

Basic Fixtures

基础Fixtures

python
undefined
python
undefined

conftest.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
undefined
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
undefined

Fixture Scopes

Fixture作用域

python
import pytest
python
import pytest

Function 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()
undefined

Fixture 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) == expected
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) == expected

Multiple 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 == expected
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 == expected

Parametrize 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 == expected
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 == expected

Indirect 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 pytest
python
import pytest

Skip 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
undefined

Custom Markers

自定义标记

python
undefined
python
undefined

pytest.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:**
```bash
import 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

**按标记运行测试:**
```bash

Run 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
undefined
pytest -m smoke
undefined

FastAPI Testing

FastAPI测试

Basic FastAPI Test Setup

基础FastAPI测试设置

python
undefined
python
undefined

app/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}
undefined
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}
undefined

FastAPI Test Client

FastAPI测试客户端

python
undefined
python
undefined

conftest.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
undefined
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
undefined

Async FastAPI Testing

异步FastAPI测试

python
undefined
python
undefined

conftest.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"
undefined
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"
undefined

FastAPI with Database Testing

带数据库的FastAPI测试

python
undefined
python
undefined

conftest.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()) == 2
undefined
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): # 先创建用户 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()) == 2
undefined

Django Testing

Django测试

Django pytest Configuration

Django pytest配置

python
undefined
python
undefined

pytest.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:', }
undefined
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:', }
undefined

Django Model Testing

Django模型测试

python
undefined
python
undefined

models.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")
undefined
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):  # 完整性错误
    User.objects.create(email="test@example.com", name="User 2")
undefined

Django View Testing

Django视图测试

python
undefined
python
undefined

views.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"]) == 2
undefined
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): # 创建测试数据 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"]) == 2
undefined

Django REST Framework Testing

Django REST Framework测试

python
undefined
python
undefined

serializers.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()
undefined
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()
undefined

Mocking and Patching

模拟与补丁

pytest-mock (pytest.fixture.mocker)

pytest-mock(pytest.fixture.mocker)

python
undefined
python
undefined

Install: 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"
undefined
def 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"
undefined

Mocking 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 == 2
python
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 == 2

Spy 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
undefined
bash
undefined

Install

安装

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
undefined
pytest --cov=app --cov-fail-under=80
undefined

pytest.ini Coverage Configuration

pytest.ini覆盖率配置

ini
undefined
ini
undefined

pytest.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_*
undefined

Coverage Reports

覆盖率报告

bash
undefined
bash
undefined

HTML 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
undefined
pytest --cov=app --cov-report=json
undefined

Async Testing

异步测试

pytest-asyncio

pytest-asyncio

python
undefined
python
undefined

Install: 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"
undefined
import 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"
undefined

Async 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
:
  • asyncio_mode = "auto"
    (default in mcp-browser, mcp-memory, claude-mpm, edgar)
  • addopts
    includes
    --strict-markers
    and
    --strict-config
    for CI consistency
  • Coverage flags:
    --cov=<package>
    ,
    --cov-report=term-missing
    ,
    --cov-report=xml
  • Selective ignores (mcp-vector-search):
    --ignore=tests/manual
    ,
    --ignore=tests/e2e
  • pythonpath = ["src"]
    for editable import resolution (mcp-ticketer)
Typical markers:
  • unit
    ,
    integration
    ,
    e2e
  • slow
    ,
    benchmark
    ,
    performance
  • requires_api
    (edgar)
Reference: see
pyproject.toml
in
claude-mpm
,
edgar
,
mcp-vector-search
,
mcp-ticketer
, and
kuzu-memory
for full lists.
来自项目
pyproject.toml
的常见设置:
  • asyncio_mode = "auto"
    (mcp-browser、mcp-memory、claude-mpm、edgar中的默认设置)
  • addopts
    包含
    --strict-markers
    --strict-config
    以确保CI一致性
  • 覆盖率标志:
    --cov=<package>
    --cov-report=term-missing
    --cov-report=xml
  • 选择性忽略(mcp-vector-search):
    --ignore=tests/manual
    --ignore=tests/e2e
  • pythonpath = ["src"]
    用于可编辑导入解析(mcp-ticketer)
典型标记:
  • unit
    integration
    e2e
  • slow
    benchmark
    performance
  • requires_api
    (edgar)
参考:查看
claude-mpm
edgar
mcp-vector-search
mcp-ticketer
kuzu-memory
中的
pyproject.toml
获取完整列表。

Best 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.ini
project/
├── 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.ini

2. Naming Conventions

2. 命名约定

python
undefined
python
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
undefined
def test_user1(): pass
def test_case2(): pass
undefined

3. 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 None
python
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 None

4. Use Fixtures for Common Setup

4. 使用Fixtures处理通用设置

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

5. Parametrize Similar Tests

5. 对相似测试使用参数化

python
undefined
python
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
undefined

6. Test One Thing Per Test

6. 每个测试只测试一件事

python
undefined
python
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 True
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 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
undefined
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
undefined

7. 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():
    pass
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():
    pass

8. Mock External Dependencies

8. 模拟外部依赖

python
undefined
python
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"
undefined
def test_fetch_user_data(): result = fetch_user_data(user_id=1) # 真实HTTP请求! assert result["name"] == "Alice"
undefined

Common Pitfalls

常见陷阱

❌ Anti-Pattern 1: Test Depends on Execution Order

❌ 反模式1:测试依赖执行顺序

python
undefined
python
undefined

WRONG: 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
undefined
python
undefined

WRONG: 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()  # Cleanup
def 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
undefined
python
undefined

WRONG: 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:**
```python
def test_user_service_uses_cache(): service = UserService() service.get_user(1) assert service._cache.has_key(1) # 测试内部缓存!

**正确做法:**
```python

Test behavior, not implementation

测试行为,而非实现

def test_user_service_returns_user(): service = UserService() user = service.get_user(1) assert user.id == 1
undefined
def 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
undefined
python
undefined

WRONG: Using unittest assertions

错误:使用unittest断言

import unittest
def test_addition(): result = add(2, 3) unittest.TestCase().assertEqual(result, 5)

**Correct:**
```python
import unittest
def test_addition(): result = add(2, 3) unittest.TestCase().assertEqual(result, 5)

**正确做法:**
```python

Use pytest's rich assertions

使用pytest的丰富断言

def test_addition(): assert add(2, 3) == 5
undefined
def test_addition(): assert add(2, 3) == 5
undefined

❌ Anti-Pattern 5: Overly Complex Fixtures

❌ 反模式5:过于复杂的Fixtures

python
undefined
python
undefined

WRONG: 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}

**正确做法:**
```python

Separate, 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)
undefined

Quick Reference

快速参考

Common Commands

常用命令

bash
undefined
bash
undefined

Run 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
undefined
pytest --ff
undefined

pytest.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
undefined
asyncio_mode = auto
undefined

conftest.py Template

conftest.py模板

python
undefined
python
undefined

conftest.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")
undefined
def pytest_configure(config): config.addinivalue_line("markers", "api: API测试") config.addinivalue_line("markers", "db: 数据库测试")
undefined

Resources

资源

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:
  1. RED Phase: Write Failing Test
    python
    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
  2. GREEN Phase: Make It Pass
    python
    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)
  3. REFACTOR Phase: Improve Code
    python
    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循环:
  1. 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()还不存在
  2. GREEN阶段:让测试通过
    python
    def authenticate(user):
        # 编写最少代码让测试通过
        if user.username == 'alice' and user.password == 'secret123':
            return AuthResult(is_authenticated=True)
        return AuthResult(is_authenticated=False)
  3. 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
undefined

Use 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**
```bash
def 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:验证解决方案**
```bash

Run 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测试的当前最佳实践。