setup-pytest-fixtures

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Setup Pytest Fixtures

配置Pytest Fixture

Purpose

用途

Creates pytest fixtures following project-watch-mcp patterns: factory fixtures for customization, async fixtures for async code, centralized organization in tests/utils/, and proper conftest.py hierarchy.
按照project-watch-mcp项目规范创建pytest夹具:支持用于自定义的工厂式夹具、适配异步代码的异步夹具、集中存放在tests/utils/中的组织方式,以及合理的conftest.py层级结构。

Quick Start

快速开始

Create a basic fixture:
python
import pytest

@pytest.fixture
def mock_settings():
    """Create mock settings for testing."""
    settings = MagicMock()
    settings.project = MagicMock()
    settings.project.project_name = "test_project"
    return settings
Create a factory fixture:
python
@pytest.fixture
def mock_settings_factory():
    """Factory for custom mock settings."""
    def create_settings(**kwargs):
        settings = MagicMock()
        settings.project = MagicMock()
        settings.project.project_name = kwargs.get("project_name", "test_project")
        return settings
    return create_settings
创建基础夹具:
python
import pytest

@pytest.fixture
def mock_settings():
    """Create mock settings for testing."""
    settings = MagicMock()
    settings.project = MagicMock()
    settings.project.project_name = "test_project"
    return settings
创建工厂式夹具:
python
@pytest.fixture
def mock_settings_factory():
    """Factory for custom mock settings."""
    def create_settings(**kwargs):
        settings = MagicMock()
        settings.project = MagicMock()
        settings.project.project_name = kwargs.get("project_name", "test_project")
        return settings
    return create_settings

Instructions

操作步骤

Step 1: Identify Fixture Type

步骤1:确定夹具类型

Determine which fixture pattern to use:
  1. Basic Fixture: Simple, reusable test data
    • Use for: Constant values, simple mocks
    • Example:
      mock_settings_minimal
  2. Factory Fixture: Customizable with parameters
    • Use for: Configurable test data, parameterized tests
    • Example:
      mock_settings_factory(**kwargs)
  3. Async Fixture: For async operations
    • Use for: Async setup/teardown, async resources
    • Example:
      @pytest_asyncio.fixture async def...
  4. Scoped Fixture: Shared across multiple tests
    • Use for: Expensive setup, session-wide resources
    • Example:
      @pytest.fixture(scope="session")
选择合适的夹具模式:
  1. 基础夹具:简单、可复用的测试数据
    • 适用场景:常量值、简单模拟对象
    • 示例:
      mock_settings_minimal
  2. 工厂式夹具:支持参数自定义
    • 适用场景:可配置的测试数据、参数化测试
    • 示例:
      mock_settings_factory(**kwargs)
  3. 异步夹具:适配异步操作
    • 适用场景:异步初始化/清理、异步资源
    • 示例:
      @pytest_asyncio.fixture async def...
  4. 作用域夹具:跨多个测试用例共享
    • 适用场景:开销较大的初始化、会话级资源
    • 示例:
      @pytest.fixture(scope="session")

Step 2: Choose Organization Strategy

步骤2:选择组织策略

Centralized Utilities (Recommended for reusable fixtures):
  • Location:
    tests/utils/
  • Files:
    mock_settings.py
    ,
    mock_services.py
    ,
    mock_drivers.py
    ,
    environmental_helpers.py
  • Import in:
    tests/conftest.py
    to make available project-wide
Local Conftest (For layer-specific fixtures):
  • Location:
    tests/unit/conftest.py
    ,
    tests/integration/conftest.py
    ,
    tests/e2e/conftest.py
  • Use for: Fixtures specific to that test layer
Test File (For test-specific fixtures):
  • Location: Same file as tests
  • Use for: One-off fixtures not reused elsewhere
集中式工具库(推荐用于可复用夹具):
  • 存放位置:
    tests/utils/
  • 文件示例:
    mock_settings.py
    ,
    mock_services.py
    ,
    mock_drivers.py
    ,
    environmental_helpers.py
  • 导入方式:在
    tests/conftest.py
    中导入,实现项目全局可用
本地Conftest(用于层级专属夹具):
  • 存放位置:
    tests/unit/conftest.py
    ,
    tests/integration/conftest.py
    ,
    tests/e2e/conftest.py
  • 适用场景:仅对应测试层级专属的夹具
测试文件内(用于测试专属夹具):
  • 存放位置:与测试用例同一文件
  • 适用场景:仅当前测试用例使用的一次性夹具

Step 3: Implement Fixture

步骤3:实现夹具

For Basic Fixtures:
python
import pytest
from unittest.mock import MagicMock

@pytest.fixture
def fixture_name():
    """Clear docstring explaining purpose."""
    # Setup
    obj = MagicMock()
    obj.attribute = "value"

    # Return (no yield for simple fixtures)
    return obj
For Factory Fixtures:
python
@pytest.fixture
def fixture_factory():
    """Factory for custom fixture."""
    def create_fixture(**kwargs):
        """Create fixture with custom attributes.

        Args:
            **kwargs: Attributes to customize

        Returns:
            MagicMock: Configured fixture
        """
        obj = MagicMock()
        obj.attribute = kwargs.get("attribute", "default_value")
        return obj
    return create_fixture
For Async Fixtures:
python
import pytest_asyncio
from collections.abc import AsyncGenerator

@pytest_asyncio.fixture(scope="function")
async def async_fixture() -> AsyncGenerator[Resource, None]:
    """Async fixture with setup and teardown."""
    # Setup
    resource = await create_resource()

    yield resource

    # Teardown
    await resource.close()
For Scoped Fixtures:
python
@pytest.fixture(scope="session")
def session_fixture():
    """Session-scoped fixture created once."""
    # Expensive setup
    resource = expensive_setup()

    yield resource

    # Cleanup after all tests
    resource.cleanup()
基础夹具实现:
python
import pytest
from unittest.mock import MagicMock

@pytest.fixture
def fixture_name():
    """Clear docstring explaining purpose."""
    # Setup
    obj = MagicMock()
    obj.attribute = "value"

    # Return (no yield for simple fixtures)
    return obj
工厂式夹具实现:
python
@pytest.fixture
def fixture_factory():
    """Factory for custom fixture."""
    def create_fixture(**kwargs):
        """Create fixture with custom attributes.

        Args:
            **kwargs: Attributes to customize

        Returns:
            MagicMock: Configured fixture
        """
        obj = MagicMock()
        obj.attribute = kwargs.get("attribute", "default_value")
        return obj
    return create_fixture
异步夹具实现:
python
import pytest_asyncio
from collections.abc import AsyncGenerator

@pytest_asyncio.fixture(scope="function")
async def async_fixture() -> AsyncGenerator[Resource, None]:
    """Async fixture with setup and teardown."""
    # Setup
    resource = await create_resource()

    yield resource

    # Teardown
    await resource.close()
作用域夹具实现:
python
@pytest.fixture(scope="session")
def session_fixture():
    """Session-scoped fixture created once."""
    # Expensive setup
    resource = expensive_setup()

    yield resource

    # Cleanup after all tests
    resource.cleanup()

Step 4: Add to Conftest Hierarchy

步骤4:添加到Conftest层级结构

In
tests/utils/mock_*.py
:
python
"""Reusable mock fixtures for [component] components."""
from unittest.mock import MagicMock
import pytest

@pytest.fixture
def fixture_name():
    """Fixture docstring."""
    return MagicMock()
In
tests/conftest.py
:
python
undefined
tests/utils/mock_*.py
中:
python
"""Reusable mock fixtures for [component] components."""
from unittest.mock import MagicMock
import pytest

@pytest.fixture
def fixture_name():
    """Fixture docstring."""
    return MagicMock()
tests/conftest.py
中:
python
undefined

Import to make available project-wide

Import to make available project-wide

from tests.utils.mock_settings import ( fixture_name, )
all = [ "fixture_name", ]

**In layer-specific conftest:**
```python
from tests.utils.mock_settings import ( fixture_name, )
all = [ "fixture_name", ]

**在层级专属Conftest中:**
```python

tests/unit/conftest.py

tests/unit/conftest.py

import pytest
@pytest.fixture def unit_specific_fixture(): """Fixture only for unit tests.""" return MagicMock()
undefined
import pytest
@pytest.fixture def unit_specific_fixture(): """Fixture only for unit tests.""" return MagicMock()
undefined

Step 5: Use Fixtures in Tests

步骤5:在测试中使用夹具

Basic usage:
python
def test_something(fixture_name):
    """Test using fixture."""
    assert fixture_name.attribute == "value"
Factory usage:
python
def test_with_factory(fixture_factory):
    """Test using factory fixture."""
    custom = fixture_factory(attribute="custom_value")
    assert custom.attribute == "custom_value"
Async usage:
python
@pytest.mark.asyncio
async def test_async(async_fixture):
    """Test using async fixture."""
    result = await async_fixture.operation()
    assert result == expected
Multiple fixtures:
python
def test_with_multiple(fixture_one, fixture_two, fixture_factory):
    """Test using multiple fixtures."""
    custom = fixture_factory(value="test")
    assert fixture_one.works_with(fixture_two, custom)
基础用法:
python
def test_something(fixture_name):
    """Test using fixture."""
    assert fixture_name.attribute == "value"
工厂式用法:
python
def test_with_factory(fixture_factory):
    """Test using factory fixture."""
    custom = fixture_factory(attribute="custom_value")
    assert custom.attribute == "custom_value"
异步用法:
python
@pytest.mark.asyncio
async def test_async(async_fixture):
    """Test using async fixture."""
    result = await async_fixture.operation()
    assert result == expected
多夹具用法:
python
def test_with_multiple(fixture_one, fixture_two, fixture_factory):
    """Test using multiple fixtures."""
    custom = fixture_factory(value="test")
    assert fixture_one.works_with(fixture_two, custom)

Examples

示例

Example 1: Basic Mock Fixture

示例1:基础模拟夹具

python
import pytest
from unittest.mock import MagicMock

@pytest.fixture
def mock_settings_minimal():
    """Create minimal mock settings for basic testing."""
    settings = MagicMock()
    settings.project = MagicMock()
    settings.project.project_name = "test_project"
    settings.neo4j = MagicMock()
    settings.neo4j.database_name = "test_db"
    return settings
python
import pytest
from unittest.mock import MagicMock

@pytest.fixture
def mock_settings_minimal():
    """Create minimal mock settings for basic testing."""
    settings = MagicMock()
    settings.project = MagicMock()
    settings.project.project_name = "test_project"
    settings.neo4j = MagicMock()
    settings.neo4j.database_name = "test_db"
    return settings

Example 2: Factory Fixture

示例2:工厂式夹具

python
@pytest.fixture
def mock_settings_factory():
    """Factory for custom mock settings."""
    def create_settings(**kwargs):
        settings = MagicMock()
        settings.project = MagicMock()
        settings.project.project_name = kwargs.get("project_name", "test_project")
        settings.neo4j = MagicMock()
        settings.neo4j.database_name = kwargs.get("database_name", "test_db")
        settings.chunking = MagicMock()
        settings.chunking.chunk_size = kwargs.get("chunk_size", 50)
        return settings
    return create_settings
python
@pytest.fixture
def mock_settings_factory():
    """Factory for custom mock settings."""
    def create_settings(**kwargs):
        settings = MagicMock()
        settings.project = MagicMock()
        settings.project.project_name = kwargs.get("project_name", "test_project")
        settings.neo4j = MagicMock()
        settings.neo4j.database_name = kwargs.get("database_name", "test_db")
        settings.chunking = MagicMock()
        settings.chunking.chunk_size = kwargs.get("chunk_size", 50)
        return settings
    return create_settings

Example 3: Async Fixture with Cleanup

示例3:带清理操作的异步夹具

python
import pytest_asyncio
from collections.abc import AsyncGenerator
from neo4j import AsyncGraphDatabase, AsyncDriver

@pytest_asyncio.fixture(scope="function")
async def real_neo4j_driver() -> AsyncGenerator[AsyncDriver, None]:
    """Create a real Neo4j driver with cleanup."""
    driver = AsyncGraphDatabase.driver(
        "bolt://localhost:7687",
        auth=("neo4j", "password")
    )

    # Setup: Clear test data
    async with driver.session(database="test_db") as session:
        await session.run("MATCH (n {project_name: 'test_project'}) DETACH DELETE n")

    yield driver

    # Teardown: Close driver
    await driver.close()
Script Files:
  • scripts/generate_fixture.py - Auto-generate pytest fixtures following project patterns
  • scripts/analyze_fixture_usage.py - Analyze pytest fixture usage and find optimization opportunities
  • scripts/organize_fixtures.py - Reorganize fixtures by layer and update imports
python
import pytest_asyncio
from collections.abc import AsyncGenerator
from neo4j import AsyncGraphDatabase, AsyncDriver

@pytest_asyncio.fixture(scope="function")
async def real_neo4j_driver() -> AsyncGenerator[AsyncDriver, None]:
    """Create a real Neo4j driver with cleanup."""
    driver = AsyncGraphDatabase.driver(
        "bolt://localhost:7687",
        auth=("neo4j", "password")
    )

    # Setup: Clear test data
    async with driver.session(database="test_db") as session:
        await session.run("MATCH (n {project_name: 'test_project'}) DETACH DELETE n")

    yield driver

    # Teardown: Close driver
    await driver.close()
脚本文件:
  • scripts/generate_fixture.py - 按照项目规范自动生成pytest夹具
  • scripts/analyze_fixture_usage.py - 分析pytest夹具的使用情况并找出优化空间
  • scripts/organize_fixtures.py - 按层级重新组织夹具并更新导入关系

Requirements

依赖要求

  • pytest installed:
    uv pip install pytest
  • pytest-asyncio for async fixtures:
    uv pip install pytest-asyncio
  • Understanding of project structure:
    • tests/utils/
      - Centralized reusable fixtures
    • tests/conftest.py
      - Project-wide fixture imports
    • tests/unit/conftest.py
      - Unit test specific fixtures
    • tests/integration/conftest.py
      - Integration test specific fixtures
    • tests/e2e/conftest.py
      - E2E test specific fixtures
  • 已安装pytest:
    uv pip install pytest
  • 异步夹具需要pytest-asyncio:
    uv pip install pytest-asyncio
  • 了解项目结构:
    • tests/utils/
      - 集中存放可复用夹具
    • tests/conftest.py
      - 项目全局夹具导入文件
    • tests/unit/conftest.py
      - 单元测试专属夹具
    • tests/integration/conftest.py
      - 集成测试专属夹具
    • tests/e2e/conftest.py
      - 端到端测试专属夹具

Fixture Best Practices

Fixture最佳实践

  1. Naming: Use descriptive names with
    mock_
    prefix for mocks
  2. Docstrings: Always document what the fixture provides
  3. Scope: Default to
    function
    scope, use
    session
    /
    module
    only when needed
  4. Cleanup: Use
    yield
    for fixtures that need teardown
  5. Factory Pattern: Return callable for customizable fixtures
  6. Type Hints: Add return type hints for better IDE support
  7. Reusability: Place reusable fixtures in
    tests/utils/
    , import in
    conftest.py
  8. Layer Isolation: Keep layer-specific fixtures in layer conftest files
  1. 命名规范:模拟对象夹具使用
    mock_
    前缀,名称需具有描述性
  2. 文档字符串:始终说明夹具提供的功能
  3. 作用域选择:默认使用
    function
    作用域,仅在必要时使用
    session
    /
    module
    作用域
  4. 清理操作:需要清理的夹具使用
    yield
    而非
    return
  5. 工厂模式:返回可调用对象以实现夹具自定义
  6. 类型提示:添加返回类型提示以提升IDE支持
  7. 复用性:可复用夹具放在
    tests/utils/
    中,在
    conftest.py
    中导入
  8. 层级隔离:层级专属夹具放在对应层级的conftest文件中

Common Patterns

常见模式

Settings Mock:
python
@pytest.fixture
def mock_settings():
    """Standard settings mock."""
    settings = MagicMock()
    settings.project = MagicMock()
    settings.neo4j = MagicMock()
    return settings
Service Result Mock:
python
@pytest.fixture
def mock_service_result():
    """Factory for ServiceResult mocks."""
    def create_result(success=True, data=None, error=None):
        result = MagicMock()
        result.is_success = success
        result.data = data
        result.error = error
        return result
    return create_result
Async Service Mock:
python
@pytest.fixture
def mock_embedding_service():
    """Async embedding service mock."""
    service = AsyncMock()
    service.generate_embeddings = AsyncMock(
        return_value=MagicMock(success=True, data=[[0.1] * 384])
    )
    return service
设置模拟:
python
@pytest.fixture
def mock_settings():
    """Standard settings mock."""
    settings = MagicMock()
    settings.project = MagicMock()
    settings.neo4j = MagicMock()
    return settings
服务结果模拟:
python
@pytest.fixture
def mock_service_result():
    """Factory for ServiceResult mocks."""
    def create_result(success=True, data=None, error=None):
        result = MagicMock()
        result.is_success = success
        result.data = data
        result.error = error
        return result
    return create_result
异步服务模拟:
python
@pytest.fixture
def mock_embedding_service():
    """Async embedding service mock."""
    service = AsyncMock()
    service.generate_embeddings = AsyncMock(
        return_value=MagicMock(success=True, data=[[0.1] * 384])
    )
    return service

Troubleshooting

故障排除

Fixture not found:
  • Check it's imported in
    tests/conftest.py
  • Verify file is in Python path
  • Ensure
    __all__
    includes fixture name
Async fixture errors:
  • Install
    pytest-asyncio
    :
    uv pip install pytest-asyncio
  • Use
    @pytest_asyncio.fixture
    not
    @pytest.fixture
  • Mark test with
    @pytest.mark.asyncio
Scope issues:
  • Session fixtures can't use function fixtures
  • Use broader scope for dependent fixtures
  • Consider fixture dependencies carefully
Cleanup not running:
  • Use
    yield
    not
    return
  • Ensure async cleanup uses
    await
  • Check for exceptions during test
找不到夹具:
  • 检查是否已在
    tests/conftest.py
    中导入
  • 验证文件是否在Python路径中
  • 确保
    __all__
    列表包含该夹具名称
异步夹具错误:
  • 安装
    pytest-asyncio
    uv pip install pytest-asyncio
  • 使用
    @pytest_asyncio.fixture
    而非
    @pytest.fixture
  • 测试用例添加
    @pytest.mark.asyncio
    标记
作用域问题:
  • 会话级夹具无法使用函数级夹具
  • 依赖夹具需使用更宽泛的作用域
  • 仔细考虑夹具之间的依赖关系
清理操作未执行:
  • 使用
    yield
    而非
    return
  • 异步清理操作需使用
    await
  • 检查测试过程中是否出现异常

Automation Scripts

自动化脚本

NEW: Powerful automation utilities for fixture management:
新增功能: 强大的夹具管理自动化工具:

scripts/generate_fixture.py

scripts/generate_fixture.py

Auto-generate fixtures with proper patterns:
bash
python .claude/skills/setup-pytest-fixtures/scripts/generate_fixture.py \
  --name mock_service --type factory --attributes host,port,timeout
按照规范自动生成夹具:
bash
python .claude/skills/setup-pytest-fixtures/scripts/generate_fixture.py \
  --name mock_service --type factory --attributes host,port,timeout

scripts/analyze_fixture_usage.py

scripts/analyze_fixture_usage.py

Analyze fixture usage and find optimization opportunities:
bash
python .claude/skills/setup-pytest-fixtures/scripts/analyze_fixture_usage.py --unused
分析夹具使用情况并找出优化空间:
bash
python .claude/skills/setup-pytest-fixtures/scripts/analyze_fixture_usage.py --unused

scripts/organize_fixtures.py

scripts/organize_fixtures.py

Reorganize fixtures by layer and update imports:
bash
python .claude/skills/setup-pytest-fixtures/scripts/organize_fixtures.py --validate
按层级重新组织夹具并更新导入关系:
bash
python .claude/skills/setup-pytest-fixtures/scripts/organize_fixtures.py --validate

See Also

相关链接

  • templates/fixture-templates.md - Copy-paste templates
  • tests/utils/mock_settings.py
    - Settings fixture reference
  • tests/utils/mock_services.py
    - Service fixture reference
  • tests/utils/mock_drivers.py
    - Driver fixture reference
  • tests/utils/environmental_helpers.py
    - Environment fixture reference
  • templates/fixture-templates.md - 可直接复制使用的夹具模板
  • tests/utils/mock_settings.py
    - 设置夹具参考示例
  • tests/utils/mock_services.py
    - 服务夹具参考示例
  • tests/utils/mock_drivers.py
    - 驱动夹具参考示例
  • tests/utils/environmental_helpers.py
    - 环境夹具参考示例