test-implement-factory-fixtures

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Implement Factory Fixtures

实现工厂Fixture

Purpose

用途

Factory fixtures are pytest fixtures that return callable functions, enabling dynamic test setup with customizable parameters. This pattern eliminates test duplication while maintaining flexibility for edge cases and variations.
工厂Fixture是一种返回可调用函数的pytest Fixture,支持通过自定义参数实现动态测试初始化。该模式可消除测试代码重复,同时保持对边缘场景和变体场景的灵活性。

Quick Start

快速开始

python
undefined
python
undefined

Create a factory fixture

Create a factory fixture

@pytest.fixture def mock_service_factory(): def create_service(dimensions=384, success=True): service = AsyncMock() service.generate = AsyncMock(return_value=[0.1] * dimensions) return service return create_service
@pytest.fixture def mock_service_factory(): def create_service(dimensions=384, success=True): service = AsyncMock() service.generate = AsyncMock(return_value=[0.1] * dimensions) return service return create_service

Use in tests

Use in tests

def test_with_custom_dimensions(mock_service_factory): service = mock_service_factory(dimensions=1536) result = service.generate() assert len(result) == 1536
undefined
def test_with_custom_dimensions(mock_service_factory): service = mock_service_factory(dimensions=1536) result = service.generate() assert len(result) == 1536
undefined

Table of Contents

目录

Core Sections

核心章节

Examples

示例

Decision Guidance

决策指南

Common Pitfalls

常见陷阱

Supporting Resources

支持资源

Python Examples

Python示例

  • Convert Fixture to Factory - Convert existing pytest fixtures to factory pattern
  • Generate Factory Fixture - Auto-generate factory fixtures from class definitions
  • Validate Fixtures - Analyze fixtures and suggest factory pattern conversions
  • 将Fixture转换为工厂 - 将现有pytest Fixture转换为工厂模式
  • 生成工厂Fixture - 从类定义自动生成工厂Fixture
  • 验证Fixture - 分析Fixture并建议转换为工厂模式

Instructions

操作步骤

Step 1: Identify the Need for Factory Fixtures

步骤1:确定是否需要工厂Fixture

Factory fixtures are ideal when:
  • Multiple test variations needed: Same mock with different configurations
  • Parameterization insufficient: pytest.mark.parametrize doesn't capture all variations
  • Edge cases require customization: Success/failure modes, different data shapes
  • Real objects with mock dependencies: Creating real instances with injected mocks
Example Scenarios:
  • Mock service returning different dimension embeddings (384, 1536, etc.)
  • Settings objects with various configuration combinations
  • Real service instances with mocked dependencies
  • Query results with different data structures
工厂Fixture适用于以下场景:
  • 需要多种测试变体:相同Mock对象需不同配置
  • 参数化方式不足:pytest.mark.parametrize无法覆盖所有变体
  • 边缘场景需要定制:成功/失败模式、不同数据结构
  • 带有Mock依赖的真实对象:创建注入Mock依赖的真实实例
示例场景:
  • 返回不同维度嵌入向量的Mock服务(384、1536等)
  • 具备多种配置组合的配置对象
  • 带有Mock依赖的真实服务实例
  • 不同数据结构的查询结果

Step 2: Design the Factory Function

步骤2:设计工厂函数

Factory fixtures have three components:
  1. Outer fixture: Declares dependencies and returns factory function
  2. Inner factory function: Accepts parameters and creates instances
  3. Default values: Sensible defaults for common cases
Standard Pattern:
python
@pytest.fixture
def mock_thing_factory():
    """Create a factory for custom mock things.

    Returns:
        callable: Function that creates things with custom parameters

    Example:
        def test_something(mock_thing_factory):
            thing = mock_thing_factory(param1=value1, param2=value2)
            # Use thing in test
    """
    def create_thing(param1=default1, param2=default2):
        """Create custom mock thing.

        Args:
            param1: Description (default: default1)
            param2: Description (default: default2)

        Returns:
            Type: Custom thing instance
        """
        # Implementation
        return thing

    return create_thing
工厂Fixture包含三个组成部分:
  1. 外层Fixture:声明依赖并返回工厂函数
  2. 内层工厂函数:接收参数并创建实例
  3. 默认值:为常见场景设置合理默认值
标准模式:
python
@pytest.fixture
def mock_thing_factory():
    """Create a factory for custom mock things.

    Returns:
        callable: Function that creates things with custom parameters

    Example:
        def test_something(mock_thing_factory):
            thing = mock_thing_factory(param1=value1, param2=value2)
            # Use thing in test
    """
    def create_thing(param1=default1, param2=default2):
        """Create custom mock thing.

        Args:
            param1: Description (default: default1)
            param2: Description (default: default2)

        Returns:
            Type: Custom thing instance
        """
        # Implementation
        return thing

    return create_thing

Step 3: Implement Common Factory Patterns

步骤3:实现常见工厂模式

Pattern A: Mock Service Factory

模式A:Mock服务工厂

For mocks with varying behavior:
python
@pytest.fixture
def mock_embedding_service_factory():
    def create_service(dimensions=384, success=True, error_message=None):
        service = AsyncMock()

        if success:
            service.generate_embeddings = AsyncMock(
                return_value=MagicMock(
                    success=True,
                    data=[[0.1] * dimensions],
                )
            )
        else:
            service.generate_embeddings = AsyncMock(
                return_value=MagicMock(
                    success=False,
                    error=error_message or "Embedding service error"
                )
            )

        return service

    return create_service
Use for: Services with success/failure modes, varying return types, different dimensions.
适用于行为可变的Mock对象:
python
@pytest.fixture
def mock_embedding_service_factory():
    def create_service(dimensions=384, success=True, error_message=None):
        service = AsyncMock()

        if success:
            service.generate_embeddings = AsyncMock(
                return_value=MagicMock(
                    success=True,
                    data=[[0.1] * dimensions],
                )
            )
        else:
            service.generate_embeddings = AsyncMock(
                return_value=MagicMock(
                    success=False,
                    error=error_message or "Embedding service error"
                )
            )

        return service

    return create_service
适用场景:具备成功/失败模式、返回类型可变、不同维度的服务。

Pattern B: Settings Factory

模式B:配置工厂

For configuration objects with many attributes:
python
@pytest.fixture
def mock_settings_factory():
    def create_settings(**kwargs):
        settings = MagicMock()

        # Set default structure
        settings.project = MagicMock()
        settings.neo4j = MagicMock()
        settings.chunking = MagicMock()

        # Apply defaults with kwargs overrides
        settings.project.project_name = kwargs.get("project_name", "test_project")
        settings.chunking.chunk_size = kwargs.get("chunk_size", 50)
        settings.neo4j.database_name = kwargs.get("database_name", "test_db")

        return settings

    return create_settings
Use for: Complex configuration objects, hierarchical settings, many attributes.
适用于包含多个属性的配置对象:
python
@pytest.fixture
def mock_settings_factory():
    def create_settings(**kwargs):
        settings = MagicMock()

        # Set default structure
        settings.project = MagicMock()
        settings.neo4j = MagicMock()
        settings.chunking = MagicMock()

        # Apply defaults with kwargs overrides
        settings.project.project_name = kwargs.get("project_name", "test_project")
        settings.chunking.chunk_size = kwargs.get("chunk_size", 50)
        settings.neo4j.database_name = kwargs.get("database_name", "test_db")

        return settings

    return create_settings
适用场景:复杂配置对象、层级化配置、多属性配置。

Pattern C: Real Instance Factory

模式C:真实实例工厂

For creating real objects with mock dependencies:
python
@pytest.fixture
def mock_indexing_module_factory(
    mock_neo4j_driver,
    mock_embedding_service_factory,
    mock_settings_factory
):
    def create_module(**kwargs):
        from project_watch_mcp.infrastructure.neo4j.graphrag.indexing import IndexingModule

        settings = mock_settings_factory(**kwargs)
        embedder = mock_embedding_service_factory(
            dimensions=kwargs.get("embedding_dimensions", 384)
        )

        return IndexingModule(
            settings=settings,
            db=mock_neo4j_driver,
            embedder=embedder,
            project_name=kwargs.get("project_name", "test_project"),
            database=kwargs.get("database", "test_db"),
        )

    return create_module
Use for: Testing real business logic with controlled dependencies.
适用于创建带有Mock依赖的真实对象:
python
@pytest.fixture
def mock_indexing_module_factory(
    mock_neo4j_driver,
    mock_embedding_service_factory,
    mock_settings_factory
):
    def create_module(**kwargs):
        from project_watch_mcp.infrastructure.neo4j.graphrag.indexing import IndexingModule

        settings = mock_settings_factory(**kwargs)
        embedder = mock_embedding_service_factory(
            dimensions=kwargs.get("embedding_dimensions", 384)
        )

        return IndexingModule(
            settings=settings,
            db=mock_neo4j_driver,
            embedder=embedder,
            project_name=kwargs.get("project_name", "test_project"),
            database=kwargs.get("database", "test_db"),
        )

    return create_module
适用场景:测试带有受控依赖的真实业务逻辑。

Pattern D: Result/Data Factory

模式D:结果/数据工厂

For creating test data structures:
python
@pytest.fixture
def mock_service_result():
    def create_result(success=True, data=None, error=None):
        result = MagicMock()
        result.is_success = success
        result.is_failure = not success

        if success:
            result.data = data
            result.unwrap = MagicMock(return_value=data)
            result.error = None
        else:
            result.data = None
            result.error = error or "Operation failed"
            result.unwrap = MagicMock(side_effect=ValueError(result.error))

        return result

    return create_result
Use for: ServiceResult mocks, query results, response objects.
适用于创建测试数据结构:
python
@pytest.fixture
def mock_service_result():
    def create_result(success=True, data=None, error=None):
        result = MagicMock()
        result.is_success = success
        result.is_failure = not success

        if success:
            result.data = data
            result.unwrap = MagicMock(return_value=data)
            result.error = None
        else:
            result.data = None
            result.error = error or "Operation failed"
            result.unwrap = MagicMock(side_effect=ValueError(result.error))

        return result

    return create_result
适用场景:ServiceResult Mock、查询结果、响应对象。

Step 4: Organize Factory Fixtures

步骤4:组织工厂Fixture

File Organization:
tests/utils/
├── mock_services.py      # Service-layer factories
├── mock_settings.py      # Configuration factories
├── mock_drivers.py       # Infrastructure factories
└── __init__.py           # Exports (if needed)
Naming Convention:
  • mock_{thing}_factory
    for mock factories
  • mock_real_{thing}_factory
    for real instances with mocks
  • Clear docstrings with usage examples
文件组织:
tests/utils/
├── mock_services.py      # Service层工厂
├── mock_settings.py      # 配置工厂
├── mock_drivers.py       # 基础设施层工厂
└── __init__.py           # 导出(如有需要)
命名规范:
  • mock_{thing}_factory
    用于Mock工厂
  • mock_real_{thing}_factory
    用于带有Mock依赖的真实实例工厂
  • 清晰的文档字符串及使用示例

Step 5: Use Factory Fixtures in Tests

步骤5:在测试中使用工厂Fixture

Basic Usage:
python
def test_with_custom_dimensions(mock_embedding_service_factory):
    service = mock_embedding_service_factory(dimensions=1536)
    result = await service.generate_embeddings(["text"])
    assert len(result.data[0]) == 1536
Testing Failure Cases:
python
def test_handles_service_error(mock_embedding_service_factory):
    service = mock_embedding_service_factory(
        success=False,
        error_message="API rate limit"
    )
    result = await service.generate_embeddings(["text"])
    assert result.is_failure
    assert "rate limit" in result.error
Combining Factories:
python
def test_indexing_with_custom_settings(
    mock_indexing_module_factory,
    mock_settings_factory
):
    module = mock_indexing_module_factory(
        chunk_size=100,
        project_name="custom_project",
        embedding_dimensions=1536
    )
    # Test with custom configuration
基础用法:
python
def test_with_custom_dimensions(mock_embedding_service_factory):
    service = mock_embedding_service_factory(dimensions=1536)
    result = await service.generate_embeddings(["text"])
    assert len(result.data[0]) == 1536
测试失败场景:
python
def test_handles_service_error(mock_embedding_service_factory):
    service = mock_embedding_service_factory(
        success=False,
        error_message="API rate limit"
    )
    result = await service.generate_embeddings(["text"])
    assert result.is_failure
    assert "rate limit" in result.error
组合多个工厂:
python
def test_indexing_with_custom_settings(
    mock_indexing_module_factory,
    mock_settings_factory
):
    module = mock_indexing_module_factory(
        chunk_size=100,
        project_name="custom_project",
        embedding_dimensions=1536
    )
    # 使用自定义配置进行测试

Examples

示例

Example 1: Embedding Service Factory (From Project)

示例1:嵌入服务工厂(来自项目)

Location:
tests/utils/mock_services.py
python
@pytest.fixture
def mock_embedding_service_factory():
    """Create a factory for custom mock embedding services.

    Returns:
        callable: Function that creates embedding services with custom dimensions

    Example:
        def test_something(mock_embedding_service_factory):
            service = mock_embedding_service_factory(dimensions=1536)
            # Use service in test
    """
    def create_service(dimensions=384, success=True, error_message=None):
        """Create custom mock embedding service.

        Args:
            dimensions: Number of dimensions for embeddings
            success: Whether the service should return success
            error_message: Error message for failure cases

        Returns:
            AsyncMock: Custom embedding service
        """
        service = AsyncMock()

        if success:
            service.generate_embeddings = AsyncMock(
                return_value=MagicMock(
                    success=True,
                    data=[[0.1] * dimensions],
                )
            )
            service.get_embeddings = AsyncMock(return_value=[[0.1] * dimensions])
            service.get_embedding = AsyncMock(return_value=[0.1] * dimensions)
        else:
            service.generate_embeddings = AsyncMock(
                return_value=MagicMock(
                    success=False, error=error_message or "Embedding service error"
                )
            )
            service.get_embeddings = AsyncMock(side_effect=Exception(error_message or "Error"))
            service.get_embedding = AsyncMock(side_effect=Exception(error_message or "Error"))

        return service

    return create_service
Usage in Tests:
python
def test_with_384_dimensions(mock_embedding_service_factory):
    service = mock_embedding_service_factory(dimensions=384)
    result = await service.generate_embeddings(["text"])
    assert len(result.data[0]) == 384

def test_with_1536_dimensions(mock_embedding_service_factory):
    service = mock_embedding_service_factory(dimensions=1536)
    result = await service.generate_embeddings(["text"])
    assert len(result.data[0]) == 1536

def test_service_failure(mock_embedding_service_factory):
    service = mock_embedding_service_factory(
        success=False,
        error_message="Rate limit exceeded"
    )
    result = await service.generate_embeddings(["text"])
    assert not result.success
    assert "Rate limit" in result.error
位置:
tests/utils/mock_services.py
python
@pytest.fixture
def mock_embedding_service_factory():
    """Create a factory for custom mock embedding services.

    Returns:
        callable: Function that creates embedding services with custom dimensions

    Example:
        def test_something(mock_embedding_service_factory):
            service = mock_embedding_service_factory(dimensions=1536)
            # Use service in test
    """
    def create_service(dimensions=384, success=True, error_message=None):
        """Create custom mock embedding service.

        Args:
            dimensions: Number of dimensions for embeddings
            success: Whether the service should return success
            error_message: Error message for failure cases

        Returns:
            AsyncMock: Custom embedding service
        """
        service = AsyncMock()

        if success:
            service.generate_embeddings = AsyncMock(
                return_value=MagicMock(
                    success=True,
                    data=[[0.1] * dimensions],
                )
            )
            service.get_embeddings = AsyncMock(return_value=[[0.1] * dimensions])
            service.get_embedding = AsyncMock(return_value=[0.1] * dimensions)
        else:
            service.generate_embeddings = AsyncMock(
                return_value=MagicMock(
                    success=False, error=error_message or "Embedding service error"
                )
            )
            service.get_embeddings = AsyncMock(side_effect=Exception(error_message or "Error"))
            service.get_embedding = AsyncMock(side_effect=Exception(error_message or "Error"))

        return service

    return create_service
测试中使用:
python
def test_with_384_dimensions(mock_embedding_service_factory):
    service = mock_embedding_service_factory(dimensions=384)
    result = await service.generate_embeddings(["text"])
    assert len(result.data[0]) == 384

def test_with_1536_dimensions(mock_embedding_service_factory):
    service = mock_embedding_service_factory(dimensions=1536)
    result = await service.generate_embeddings(["text"])
    assert len(result.data[0]) == 1536

def test_service_failure(mock_embedding_service_factory):
    service = mock_embedding_service_factory(
        success=False,
        error_message="Rate limit exceeded"
    )
    result = await service.generate_embeddings(["text"])
    assert not result.success
    assert "Rate limit" in result.error

Example 2: Settings Factory with Kwargs (From Project)

示例2:带关键字参数的配置工厂(来自项目)

Location:
tests/utils/mock_settings.py
python
@pytest.fixture
def mock_settings_factory():
    """Create a factory for custom mock settings.

    Returns:
        callable: Function that creates settings with custom attributes

    Example:
        def test_something(mock_settings_factory):
            settings = mock_settings_factory(
                project_name="custom_project",
                chunk_size=100,
                database_name="custom_db"
            )
            # Use settings in test
    """
    def create_settings(**kwargs):
        """Create custom mock settings with specified attributes.

        Args:
            **kwargs: Attributes to set on the settings object.
                     Use dot notation for nested attributes.

        Returns:
            MagicMock: Custom settings object
        """
        settings = MagicMock()

        # Set default structure
        settings.project = MagicMock()
        settings.neo4j = MagicMock()
        settings.chunking = MagicMock()
        settings.indexing = MagicMock()

        # Apply defaults
        settings.project.project_name = kwargs.get("project_name", "test_project")
        settings.project.repository_path = Path(kwargs.get("repository_path", "/test/repo"))

        settings.neo4j.database_name = kwargs.get("database_name", "test_db")
        settings.neo4j.uri = kwargs.get("neo4j_uri", "bolt://192.168.68.122:7687")

        settings.chunking.chunk_size = kwargs.get("chunk_size", 50)
        settings.chunking.chunk_overlap = kwargs.get("chunk_overlap", 5)

        settings.indexing.batch_size = kwargs.get("batch_size", 100)
        settings.indexing.max_workers = kwargs.get("max_workers", 4)

        return settings

    return create_settings
Usage in Tests:
python
def test_with_custom_chunk_size(mock_settings_factory):
    settings = mock_settings_factory(chunk_size=100)
    assert settings.chunking.chunk_size == 100

def test_with_multiple_overrides(mock_settings_factory):
    settings = mock_settings_factory(
        project_name="my_project",
        chunk_size=200,
        batch_size=50,
        database_name="prod_db"
    )
    assert settings.project.project_name == "my_project"
    assert settings.chunking.chunk_size == 200
    assert settings.indexing.batch_size == 50
    assert settings.neo4j.database_name == "prod_db"
位置:
tests/utils/mock_settings.py
python
@pytest.fixture
def mock_settings_factory():
    """Create a factory for custom mock settings.

    Returns:
        callable: Function that creates settings with custom attributes

    Example:
        def test_something(mock_settings_factory):
            settings = mock_settings_factory(
                project_name="custom_project",
                chunk_size=100,
                database_name="custom_db"
            )
            # Use settings in test
    """
    def create_settings(**kwargs):
        """Create custom mock settings with specified attributes.

        Args:
            **kwargs: Attributes to set on the settings object.
                     Use dot notation for nested attributes.

        Returns:
            MagicMock: Custom settings object
        """
        settings = MagicMock()

        # Set default structure
        settings.project = MagicMock()
        settings.neo4j = MagicMock()
        settings.chunking = MagicMock()
        settings.indexing = MagicMock()

        # Apply defaults
        settings.project.project_name = kwargs.get("project_name", "test_project")
        settings.project.repository_path = Path(kwargs.get("repository_path", "/test/repo"))

        settings.neo4j.database_name = kwargs.get("database_name", "test_db")
        settings.neo4j.uri = kwargs.get("neo4j_uri", "bolt://192.168.68.122:7687")

        settings.chunking.chunk_size = kwargs.get("chunk_size", 50)
        settings.chunking.chunk_overlap = kwargs.get("chunk_overlap", 5)

        settings.indexing.batch_size = kwargs.get("batch_size", 100)
        settings.indexing.max_workers = kwargs.get("max_workers", 4)

        return settings

    return create_settings
测试中使用:
python
def test_with_custom_chunk_size(mock_settings_factory):
    settings = mock_settings_factory(chunk_size=100)
    assert settings.chunking.chunk_size == 100

def test_with_multiple_overrides(mock_settings_factory):
    settings = mock_settings_factory(
        project_name="my_project",
        chunk_size=200,
        batch_size=50,
        database_name="prod_db"
    )
    assert settings.project.project_name == "my_project"
    assert settings.chunking.chunk_size == 200
    assert settings.indexing.batch_size == 50
    assert settings.neo4j.database_name == "prod_db"

Example 3: Real Instance Factory with Mock Dependencies

示例3:带有Mock依赖的真实实例工厂

Location:
tests/utils/mock_services.py
python
@pytest.fixture
def mock_indexing_module_factory(
    mock_neo4j_driver,
    mock_embedding_service_factory,
    mock_settings_factory
):
    """Create a factory for real IndexingModule instances with custom settings.

    Args:
        mock_neo4j_driver: Mock Neo4j driver fixture
        mock_embedding_service_factory: Mock embedding service factory
        mock_settings_factory: Mock settings factory

    Returns:
        callable: Function that creates IndexingModule instances

    Example:
        def test_something(mock_indexing_module_factory):
            module = mock_indexing_module_factory(
                chunk_size=100,
                project_name="custom_project"
            )
            # Use module in test
    """
    def create_module(**kwargs):
        """Create IndexingModule with custom settings.

        Args:
            **kwargs: Settings to override

        Returns:
            IndexingModule: Real IndexingModule instance with mocks
        """
        from project_watch_mcp.infrastructure.neo4j.graphrag.indexing import IndexingModule

        settings = mock_settings_factory(**kwargs)
        embedder = mock_embedding_service_factory(
            dimensions=kwargs.get("embedding_dimensions", 384)
        )

        return IndexingModule(
            settings=settings,
            db=mock_neo4j_driver,
            embedder=embedder,
            project_name=kwargs.get("project_name", "test_project"),
            database=kwargs.get("database", "test_db"),
        )

    return create_module
Usage in Tests:
python
def test_indexing_with_large_chunks(mock_indexing_module_factory):
    module = mock_indexing_module_factory(chunk_size=500)
    # Test real IndexingModule logic with large chunks

def test_indexing_with_custom_embeddings(mock_indexing_module_factory):
    module = mock_indexing_module_factory(
        embedding_dimensions=1536,
        project_name="ml_project"
    )
    # Test with different embedding dimensions
位置:
tests/utils/mock_services.py
python
@pytest.fixture
def mock_indexing_module_factory(
    mock_neo4j_driver,
    mock_embedding_service_factory,
    mock_settings_factory
):
    """Create a factory for real IndexingModule instances with custom settings.

    Args:
        mock_neo4j_driver: Mock Neo4j driver fixture
        mock_embedding_service_factory: Mock embedding service factory
        mock_settings_factory: Mock settings factory

    Returns:
        callable: Function that creates IndexingModule instances

    Example:
        def test_something(mock_indexing_module_factory):
            module = mock_indexing_module_factory(
                chunk_size=100,
                project_name="custom_project"
            )
            # Use module in test
    """
    def create_module(**kwargs):
        """Create IndexingModule with custom settings.

        Args:
            **kwargs: Settings to override

        Returns:
            IndexingModule: Real IndexingModule instance with mocks
        """
        from project_watch_mcp.infrastructure.neo4j.graphrag.indexing import IndexingModule

        settings = mock_settings_factory(**kwargs)
        embedder = mock_embedding_service_factory(
            dimensions=kwargs.get("embedding_dimensions", 384)
        )

        return IndexingModule(
            settings=settings,
            db=mock_neo4j_driver,
            embedder=embedder,
            project_name=kwargs.get("project_name", "test_project"),
            database=kwargs.get("database", "test_db"),
        )

    return create_module
测试中使用:
python
def test_indexing_with_large_chunks(mock_indexing_module_factory):
    module = mock_indexing_module_factory(chunk_size=500)
    # 测试大分块的真实IndexingModule逻辑

def test_indexing_with_custom_embeddings(mock_indexing_module_factory):
    module = mock_indexing_module_factory(
        embedding_dimensions=1536,
        project_name="ml_project"
    )
    # 使用不同嵌入维度进行测试

Requirements

依赖要求

  • pytest (installed via project dependencies)
  • unittest.mock (Python standard library)
  • Understanding of pytest fixture scope
  • Familiarity with closure patterns in Python
Installation:
bash
undefined
  • pytest(通过项目依赖安装)
  • unittest.mock(Python标准库)
  • 了解pytest Fixture作用域
  • 熟悉Python闭包模式
安装:
bash
undefined

Already included in project dependencies

已包含在项目依赖中

uv pip install -e .
undefined
uv pip install -e .
undefined

Factory vs Parametrize: When to Choose

工厂模式 vs 参数化:如何选择

Use Factory Fixtures When:

选择工厂Fixture的场景:

  • ✅ Need multiple parameters with complex interactions
  • ✅ Creating real instances with mock dependencies
  • ✅ Success/failure modes require different mock setups
  • ✅ Edge cases need special configuration
  • ✅ Same fixture used across multiple test files
  • ✅ 需要多个参数且存在复杂交互
  • ✅ 创建带有Mock依赖的真实实例
  • ✅ 成功/失败模式需要不同Mock配置
  • ✅ 边缘场景需要特殊配置
  • ✅ 同一Fixture需在多个测试文件中使用

Use pytest.mark.parametrize When:

选择pytest.mark.parametrize的场景:

  • ✅ Testing same code with different input values
  • ✅ Simple parameter variations (1-3 parameters)
  • ✅ All test cases follow same pattern
  • ✅ Parameters are independent
Example Comparison:
python
undefined
  • ✅ 同一代码需使用不同输入值测试
  • ✅ 简单参数变体(1-3个参数)
  • ✅ 所有测试用例遵循相同模式
  • ✅ 参数相互独立
对比示例:
python
undefined

❌ WRONG: Parametrize for complex factory-like needs

❌ 错误:用参数化实现工厂类需求

@pytest.mark.parametrize("dimensions,success,error", [ (384, True, None), (1536, True, None), (384, False, "Error"), ]) def test_embedding_service(dimensions, success, error): # Complex mock setup repeated in test service = create_complex_mock(dimensions, success, error) # ...
@pytest.mark.parametrize("dimensions,success,error", [ (384, True, None), (1536, True, None), (384, False, "Error"), ]) def test_embedding_service(dimensions, success, error): # 测试中重复复杂Mock初始化 service = create_complex_mock(dimensions, success, error) # ...

✅ CORRECT: Factory fixture for complex setup

✅ 正确:用工厂Fixture实现复杂初始化

def test_embedding_service_384(mock_embedding_service_factory): service = mock_embedding_service_factory(dimensions=384) # ...
def test_embedding_service_failure(mock_embedding_service_factory): service = mock_embedding_service_factory(success=False, error="Error") # ...
undefined
def test_embedding_service_384(mock_embedding_service_factory): service = mock_embedding_service_factory(dimensions=384) # ...
def test_embedding_service_failure(mock_embedding_service_factory): service = mock_embedding_service_factory(success=False, error="Error") # ...
undefined

Common Pitfalls

常见陷阱

Pitfall 1: Over-Parameterization

陷阱1:过度参数化

❌ WRONG: Too many parameters
python
def create_service(
    param1, param2, param3, param4, param5,
    param6, param7, param8, param9, param10
):
    # Too complex!
✅ CORRECT: Use kwargs or multiple factories
python
def create_service(**kwargs):
    # Flexible and readable
❌ 错误:参数过多
python
def create_service(
    param1, param2, param3, param4, param5,
    param6, param7, param8, param9, param10
):
    # 过于复杂!
✅ 正确:使用关键字参数或多个工厂
python
def create_service(**kwargs):
    # 灵活且可读性高

Pitfall 2: Missing Defaults

陷阱2:缺少默认值

❌ WRONG: All parameters required
python
def create_service(dimensions, success, error_message):
    # Forces every test to specify everything
✅ CORRECT: Sensible defaults
python
def create_service(dimensions=384, success=True, error_message=None):
    # Common case works with no args
❌ 错误:所有参数为必填
python
def create_service(dimensions, success, error_message):
    # 强制所有测试必须指定所有参数
✅ 正确:设置合理默认值
python
def create_service(dimensions=384, success=True, error_message=None):
    # 常见场景无需传参即可使用

Pitfall 3: Stateful Factories

陷阱3:有状态工厂

❌ WRONG: Factory maintains state
python
@pytest.fixture
def mock_service_factory():
    call_count = 0  # Shared across calls!
    def create_service():
        nonlocal call_count
        call_count += 1
        # State leaks between tests
✅ CORRECT: Stateless factory
python
@pytest.fixture
def mock_service_factory():
    def create_service(call_count=0):  # Pass as parameter
        service = AsyncMock()
        service.call_count = call_count
        return service
    return create_service
❌ 错误:工厂保持状态
python
@pytest.fixture
def mock_service_factory():
    call_count = 0  # 所有调用共享!
    def create_service():
        nonlocal call_count
        call_count += 1
        # 测试间存在状态泄漏
✅ 正确:无状态工厂
python
@pytest.fixture
def mock_service_factory():
    def create_service(call_count=0):  # 作为参数传入
        service = AsyncMock()
        service.call_count = call_count
        return service
    return create_service

Pitfall 4: Not Using Type Hints

陷阱4:未使用类型提示

❌ WRONG: No type hints
python
def create_service(dimensions=384):
    # What type is returned?
✅ CORRECT: Clear return types
python
def create_service(dimensions: int = 384) -> AsyncMock:
    """Create custom mock embedding service.

    Args:
        dimensions: Number of dimensions for embeddings

    Returns:
        AsyncMock: Custom embedding service
    """
❌ 错误:无类型提示
python
def create_service(dimensions=384):
    # 返回类型是什么?
✅ 正确:明确返回类型
python
def create_service(dimensions: int = 384) -> AsyncMock:
    """Create custom mock embedding service.

    Args:
        dimensions: Number of dimensions for embeddings

    Returns:
        AsyncMock: Custom embedding service
    """

See Also

相关链接