AI Tools Compared

Claude and ChatGPT can analyze SQLAlchemy models, Pydantic schemas, or raw SQL definitions and automatically generate complete pytest fixtures that handle table structure, foreign key relationships, and transaction management. By feeding your schema to these AI assistants, you can reduce hours of repetitive fixture boilerplate into minutes of AI-assisted code generation.

Understanding the Problem

Database-driven applications require test data that reflects your actual schema. A typical fixture generation workflow involves:

  1. Examining your SQLAlchemy models or raw SQL definitions

  2. Creating factory functions for each table

  3. Handling foreign key relationships

  4. Managing transaction rollbacks between tests

  5. Generating edge case data for boundary testing

For a medium-sized application with 15-20 tables, this can easily require several hours of fixture code. When schema changes occur, updating all related fixtures adds more overhead.

Which AI Tools Perform Best for Fixture Generation

Not all AI coding assistants handle database schema context equally well. Here is how the main tools compare on this specific task:

Tool Schema Input Relationship Handling Factory Pattern Async Support
Claude (Sonnet) Excellent — reads full model files Correctly resolves FK chains Generates factory_boy compatible code Yes, with pytest-asyncio
ChatGPT GPT-4o Good — handles SQLAlchemy and raw SQL Usually correct, misses multi-hop FKs Basic factory functions Yes
GitHub Copilot Good — autocomplete in conftest.py Infers from surrounding fixtures Limited Yes
Cursor Excellent — full file context via @-mentions Correct with @models context Good Yes

Claude is the strongest choice when your schema is large or has complex polymorphic relationships. Feed it your entire models/ directory and ask for a complete conftest.py—it reads full context and correctly sequences fixtures by dependency order.

Cursor matches Claude for fixture quality when you use @models.py to provide context. The in-editor workflow is faster because you see generated code immediately, making corrections easy.

GitHub Copilot is the fastest option if your project already has some fixtures—it extends existing patterns without requiring a separate prompt, keeping style consistent.

How AI Tools Approach Fixture Generation

Modern AI coding assistants can process your database schema definitions—whether written as SQLAlchemy models, Pydantic schemas, raw SQL DDL statements, or ORM class definitions—and produce working fixture code. The key is providing clear context about your tech stack and testing patterns.

Using SQLAlchemy Model Definitions

If your project uses SQLAlchemy, the most direct approach involves feeding your model definitions directly to an AI assistant. Consider this sample schema:

from sqlalchemy import Column, Integer, String, ForeignKey, DateTime
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
from datetime import datetime

Base = declarative_base()

class User(Base):
    __tablename__ = 'users'

    id = Column(Integer, primary_key=True)
    email = Column(String(255), unique=True, nullable=False)
    username = Column(String(100), nullable=False)
    created_at = Column(DateTime, default=datetime.utcnow)

    orders = relationship("Order", back_populates="customer")

class Order(Base):
    __tablename__ = 'orders'

    id = Column(Integer, primary_key=True)
    user_id = Column(Integer, ForeignKey('users.id'), nullable=False)
    total_amount = Column(Integer, nullable=False)
    status = Column(String(50), default='pending')
    created_at = Column(DateTime, default=datetime.utcnow)

    customer = relationship("User", back_populates="orders")

An AI tool can generate fixtures from this structure:

import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from models import Base, User, Order

@pytest.fixture(scope="function")
def db_session():
    """Create a fresh database session for each test."""
    engine = create_engine("sqlite:///:memory:")
    Base.metadata.create_all(engine)
    Session = sessionmaker(bind=engine)
    session = Session()
    yield session
    session.rollback()
    session.close()

@pytest.fixture
def sample_user(db_session):
    """Create a sample user for testing."""
    user = User(
        email="test@example.com",
        username="testuser"
    )
    db_session.add(user)
    db_session.commit()
    db_session.refresh(user)
    return user

@pytest.fixture
def sample_order(db_session, sample_user):
    """Create a sample order associated with a user."""
    order = Order(
        user_id=sample_user.id,
        total_amount=5000,
        status="pending"
    )
    db_session.add(order)
    db_session.commit()
    db_session.refresh(order)
    return order

The AI generates fixtures that handle the relationships correctly—it understands that sample_order depends on sample_user and structures the fixtures accordingly.

Generating Fixtures from Plain SQL Definitions

Projects using raw SQL or migration files can also benefit from AI-assisted fixture generation. Provide your CREATE TABLE statements:

CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    price DECIMAL(10,2) NOT NULL,
    category_id INTEGER REFERENCES categories(id),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE categories (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL UNIQUE
);

The AI produces equivalent pytest fixtures that create the necessary tables and insert sample data matching your constraints. For PostgreSQL-backed tests, AI tools also know to swap SERIAL for SQLAlchemy’s Sequence and to configure the engine string appropriately:

@pytest.fixture(scope="session")
def engine():
    """Session-scoped engine for PostgreSQL integration tests."""
    engine = create_engine(
        "postgresql://testuser:testpass@localhost:5432/testdb",
        echo=False
    )
    Base.metadata.create_all(engine)
    yield engine
    Base.metadata.drop_all(engine)

Handling Complex Scenarios

Beyond basic fixtures, AI tools excel at generating more sophisticated test data structures:

Factory patterns for mass data generation:

@pytest.fixture
def user_factory(db_session):
    """Factory for creating multiple users with varying attributes."""
    def _create_user(email=None, username=None, **kwargs):
        user = User(
            email=email or f"user_{uuid4().hex[:8]}@example.com",
            username=username or f"user_{uuid4().hex[:8]}",
            **kwargs
        )
        db_session.add(user)
        db_session.commit()
        db_session.refresh(user)
        return user
    return _create_user

Fixtures for testing edge cases:

@pytest.fixture
def users_with_different_states(db_session):
    """Create users in various states for comprehensive testing."""
    users = [
        User(email="active@example.com", username="active", status="active"),
        User(email="inactive@example.com", username="inactive", status="inactive"),
        User(email="suspended@example.com", username="suspended", status="suspended"),
    ]
    for user in users:
        db_session.add(user)
    db_session.commit()
    return users

factory_boy integration is a pattern AI tools handle well when you ask explicitly. Providing a schema and requesting factory_boy output yields factories that compose cleanly and support SubFactory for FK relationships:

import factory
from factory.alchemy import SQLAlchemyModelFactory

class UserFactory(SQLAlchemyModelFactory):
    class Meta:
        model = User
        sqlalchemy_session_persistence = "commit"

    email = factory.Sequence(lambda n: f"user{n}@example.com")
    username = factory.Sequence(lambda n: f"user{n}")

class OrderFactory(SQLAlchemyModelFactory):
    class Meta:
        model = Order
        sqlalchemy_session_persistence = "commit"

    customer = factory.SubFactory(UserFactory)
    total_amount = factory.Faker("pyint", min_value=100, max_value=100000)
    status = "pending"

Step-by-Step Workflow for Generating Fixtures with AI

Step 1 — Gather your schema files. Collect all model files, migration scripts, or DDL statements into a single context. If using SQLAlchemy, introspect an existing database with MetaData().reflect(engine) to dump the current table list.

Step 2 — Write a fixture brief. Note which fixtures your test suite needs and group them by dependency: leaf tables first, then tables that reference them. AI tools generate cleaner output when you specify dependency order explicitly.

Step 3 — Prompt with schema plus testing framework context. Always specify: pytest version, database backend, whether you use pytest-asyncio, and whether you prefer scope="function" or scope="module" sessions.

Step 4 — Review FK sequencing. The most common AI mistake is generating a fixture that creates an Order before the User it references. Read through the generated conftest.py and verify fixture arguments are wired correctly.

Step 5 — Run the fixtures against a test database. Use pytest -x --tb=short to catch errors early. SQLAlchemy constraint violations surface immediately and tell you which fixture needs correction.

Step 6 — Add parametrize fixtures for edge cases. Ask AI to extend fixtures with @pytest.mark.parametrize for boundary values—empty strings, max-length strings, zero and negative amounts—based on your column constraints.

Best Practices for Optimal Results

Providing the right context to AI tools dramatically improves fixture quality:

  1. Include your ORM or model definitions — SQLAlchemy, SQLModel, or Pydantic models help AI understand relationships

  2. Specify your testing database — Whether you use SQLite in-memory, PostgreSQL test containers, or mock objects

  3. Share existing fixture patterns — If your project follows specific conventions, show examples

  4. Mention constraint requirements — Unique constraints, foreign key relationships, and validation rules

  5. State your pytest-asyncio mode — If using asyncio_mode = "auto" in pytest.ini, tell the AI so it generates async def fixtures without explicit @pytest.mark.asyncio decorators

Limitations to Consider

AI-generated fixtures require review before use. Watch for:

Most issues are minor and easily corrected after the initial generation.

Integration with Test Workflows

After generating fixtures, integrate them into your testing workflow:

# conftest.py
import pytest
from fixtures import db_session, sample_user, sample_order, user_factory

def test_order_creation(db_session, sample_user):
    """Test that an order can be created for a valid user."""
    order = Order(
        user_id=sample_user.id,
        total_amount=2500,
        status="pending"
    )
    db_session.add(order)
    db_session.commit()

    assert order.id is not None
    assert order.status == "pending"
    assert order.customer.email == "test@example.com"

For CI environments, pair AI-generated fixtures with testcontainers-python to spin up a real PostgreSQL instance:

from testcontainers.postgres import PostgresContainer

@pytest.fixture(scope="session")
def postgres_container():
    with PostgresContainer("postgres:16") as pg:
        yield pg.get_connection_url()

This pattern gives you a fully automated test database setup that mirrors production schema without manual configuration.

FAQ

Q: Can AI tools handle Alembic migration files as input instead of model definitions? Yes, but with caveats. Paste your latest migration file alongside your model file. The AI reconciles the two and generates fixtures matching the current schema state. For fixtures that test migration paths, ask specifically for fixtures with alembic upgrade and alembic downgrade calls integrated.

Q: What is the best way to handle many-to-many relationships in fixtures? Provide the association table definition alongside both model definitions. Claude and ChatGPT correctly identify M2M relationships and generate intermediate table fixtures. Specify whether you want the relationship handled via SQLAlchemy’s append() on the relationship attribute or via direct inserts to the association table.

Q: How do I prevent unique constraint violations when running fixtures in parallel with pytest-xdist? Ask the AI to use factory.Sequence or uuid4() for all unique fields. Include “this suite runs with pytest-xdist” in your prompt and AI tools apply this pattern automatically.

Q: Can AI generate async fixtures for FastAPI tests? Yes. Specify pytest-asyncio and httpx.AsyncClient in your prompt. Claude generates fixtures using async def and async with syntax correctly, including the anyio backend configuration needed for FastAPI’s async test client.

Built by theluckystrike — More at zovo.one