Módulo 6: Mocking, Fixtures y Validation Loops

pytest Fixtures Avanzadas

pytest Fixtures Avanzadas

Descripción de la cápsula

Ya conoces las fixtures básicas: una función con @pytest.fixture que prepara datos o recursos para tus tests. Pero cuando el proyecto crece — decenas de tests, varios tipos (unit, integration, E2E) — las fixtures se convierten en un caos si no las organizas bien. Un test que usa una db compartida puede contaminar otro. Una fixture que crea un usuario en cada test ralentiza todo. Un conftest.py de 500 líneas es imposible de mantener.

Esta cápsula te enseña fixtures avanzadas: scope para controlar cuántas veces se crea un recurso, composition para encadenar fixtures, factory fixtures para datos variables, organización profesional con conftest.py por directorio, autouse cuando necesitas setup global, y parametrized fixtures para pruebas contra múltiples backends. Al final verás cómo generar fixtures realistas con Claude Code. Con esto tendrás la base para un proyecto con tests mantenibles y escalables.


Fixture Scope: Controlar el Ciclo de Vida

El problema: recursos caros por test

Por defecto, pytest crea una fixture nueva para cada test que la usa. Si tienes 50 tests que piden db, pytest ejecuta el setup de db 50 veces. Para una base de datos in-memory está bien. Para una conexión PostgreSQL, un cliente HTTP, o un servicio que tarda 2 segundos en arrancar, es un desperdicio.

El scope controla cuántas veces se instancia la fixture:

ScopeMomento de creaciónMomento de teardownUso típico
functionCada testDespués de cada testDB vacía, datos aislados, recursos baratos
classUna vez por clase de testsDespués de la claseTests agrupados que comparten setup
moduleUna vez por archivoDespués del móduloRecursos caros usados por todos los tests del archivo
sessionUna vez en toda la sesiónAl finalConfig global, cliente HTTP compartido

function (default): fixture nueva por test

# test_example.py
import pytest


@pytest.fixture  # scope="function" es el default
def db():
    """Cada test recibe una db nueva."""
    return {"items": [], "next_id": 1}


def test_a(db):
    db["items"].append("a")
    assert len(db["items"]) == 1


def test_b(db):
    # db está vacía — test_a no la contaminó
    assert len(db["items"]) == 0

Cada test tiene aislamiento total. Es lo correcto para bases de datos y datos mutables.

module: compartir entre tests del mismo archivo

# test_example.py
import pytest


def create_expensive_resource():
    """Simula un recurso que tarda en crearse."""
    return {"initialized": True}


def cleanup(resource):
    """Libera recursos."""
    pass


@pytest.fixture(scope="module")
def expensive_resource():
    resource = create_expensive_resource()
    yield resource
    cleanup(resource)


def test_uses_resource_1(expensive_resource):
    assert expensive_resource["initialized"] is True


def test_uses_resource_2(expensive_resource):
    # Misma instancia que test_uses_resource_1
    assert expensive_resource["initialized"] is True

Con scope="module", la fixture se crea una sola vez para todo el archivo. El teardown corre al final del módulo.

session: compartir en toda la suite

# conftest.py
import pytest


@pytest.fixture(scope="session")
def app_config():
    """Config que no cambia durante toda la sesión."""
    return {"debug": True, "env": "test"}


def test_in_file_a(app_config):
    assert app_config["env"] == "test"


# test_otro.py
def test_in_file_b(app_config):
    # Misma instancia que en cualquier otro archivo
    assert app_config["debug"] is True

Usa session solo para recursos verdaderamente inmutables. Si la fixture mantiene estado mutable, los tests pueden contaminarse entre sí.

class: compartir dentro de una clase

import pytest


@pytest.fixture(scope="class")
def shared_client():
    """Un cliente HTTP compartido por la clase."""
    return {"base_url": "https://api.test.com"}


class TestUserEndpoints:
    def test_list_users(self, shared_client):
        assert shared_client["base_url"] == "https://api.test.com"

    def test_get_user(self, shared_client):
        # Misma instancia que test_list_users
        assert "base_url" in shared_client

Útil cuando agrupas tests relacionados y quieres compartir setup sin llegar a module.

Cuándo elegir cada scope

EscenarioScope recomendadoRazón
Base de datos con datos mutablesfunctionAislamiento total, cada test arranca limpio
Cliente HTTP sin estado (read-only)module o sessionEvitar crear conexión 50 veces
Config dict/list que se modificafunctionEvitar contaminación entre tests
Config inmutable (solo lectura)sessionUna instancia para toda la suite
Tests en clase que comparten setupclassBalance entre reutilización y aislamiento
Conexión a PostgreSQL realmodule mínimoCrear conexión es caro; cuidado con mutación

Regla práctica: empieza con function. Solo cambia a module o session cuando midas y confirmes que el setup es un cuello de botella.


Fixture Composition: Encadenar Dependencias

Las fixtures pueden depender de otras fixtures. pytest resuelve el grafo de dependencias automáticamente.

Composición básica

# conftest.py o test_*.py
import pytest


@pytest.fixture
def db():
    """Base de datos vacía."""
    return create_db()


@pytest.fixture
def user(db):
    """Usuario creado en la db."""
    return db.create_user("test@test.com")


@pytest.fixture
def auth_token(user):
    """Token generado para el usuario."""
    return generate_token(user.id)


def test_protected_endpoint(auth_token):
    # pytest ejecuta: db → user → auth_token
    headers = {"Authorization": f"Bearer {auth_token}"}
    response = client.get("/me", headers=headers)
    assert response.status_code == 200

auth_token depende de user, que depende de db. pytest crea db, luego user(db), luego auth_token(user). No necesitas indicar el orden.

Ejemplo completo con código ejecutable

# database.py
def create_db():
    return {"users": [], "next_id": 1}


class FakeDB:
    def __init__(self):
        self.data = create_db()

    def create_user(self, email: str):
        uid = self.data["next_id"]
        self.data["next_id"] += 1
        user = {"id": uid, "email": email}
        self.data["users"].append(user)
        return user

    def get_user(self, uid: int):
        for u in self.data["users"]:
            if u["id"] == uid:
                return u
        return None


# auth_utils.py
def generate_token(user_id: int) -> str:
    return f"token_for_{user_id}"


# test_auth.py
import pytest
from database import FakeDB
from auth_utils import generate_token


@pytest.fixture
def db():
    return FakeDB()


@pytest.fixture
def user(db):
    return db.create_user("test@test.com")


@pytest.fixture
def auth_token(user):
    return generate_token(user["id"])


def test_auth_token_format(auth_token):
    assert auth_token.startswith("token_for_")


def test_user_exists_in_db(db, user):
    u = db.get_user(user["id"])
    assert u is not None
    assert u["email"] == "test@test.com"

Cada test que pide auth_token obtiene un usuario y token frescos, porque db tiene scope function por defecto.

Orden de ejecución y dependencias circulares

pytest construye un DAG (grafo acíclico dirigido) de fixtures. Si A depende de B y B de C, el orden de ejecución es C → B → A. Las dependencias circulares no están permitidas:

# ❌ Esto falla
@pytest.fixture
def a(b):
    return b

@pytest.fixture
def b(a):
    return a

Si necesitas compartir lógica entre dos fixtures que se referencian mutuamente, extrae esa lógica a una función helper o a una tercera fixture que ambas usen.


Factory Fixtures: Datos Variables por Test

A veces necesitas crear varias instancias con distintos atributos. En vez de una fixture que retorna un solo objeto, retornas una función que crea objetos bajo demanda.

Patrón básico

@pytest.fixture
def make_user():
    def _make_user(name="Test", email="test@test.com", role="user"):
        return {"name": name, "email": email, "role": role}
    return _make_user


def test_admin(make_user):
    admin = make_user(name="Admin", role="admin")
    assert admin["role"] == "admin"


def test_guest(make_user):
    guest = make_user(name="Guest", role="guest")
    assert guest["role"] == "guest"


def test_default_user(make_user):
    user = make_user()  # Usa defaults
    assert user["name"] == "Test"
    assert user["role"] == "user"

make_user es una fixture que retorna una función. Cada test llama a esa función con los parámetros que necesita.

Factory con dependencias

@pytest.fixture
def db():
    return FakeDB()


@pytest.fixture
def make_user(db):
    def _make_user(name="Test", email="test@test.com", role="user"):
        user = db.create_user(email)
        user["name"] = name
        user["role"] = role
        return user
    return _make_user


def test_create_admin_and_guest(make_user, db):
    admin = make_user(name="Admin", role="admin")
    guest = make_user(name="Guest", role="guest")
    assert db.get_user(admin["id"])["role"] == "admin"
    assert db.get_user(guest["id"])["role"] == "guest"

La factory puede usar otras fixtures. Aquí make_user usa db para persistir.

Factory vs fixture simple

EnfoqueCuándo usarloEjemplo
Fixture simpleUn solo objeto con datos fijosuser que retorna siempre el mismo admin
Factory fixtureVarias instancias con distintos atributosmake_user(role="admin"), make_user(role="guest")
Fixture compuestaDatos derivados de otro recursoauth_token(user)
Factory + compuestaVarias instancias que dependen de un recursomake_user(db) con db inyectada

La factory es ideal cuando cada test necesita datos ligeramente distintos: diferentes roles, emails, cantidades. Evitas crear 10 fixtures (admin_user, guest_user, premium_user, ...) y tienes una sola make_user parametrizable.


conftest.py: Organización por Directorio

Pytest busca fixtures en conftest.py de forma jerárquica. Cada directorio puede tener su propio conftest.py. Las fixtures se descubren automáticamente.

Estructura recomendada

tests/
├── conftest.py           # Global: client, db, auth_token
├── unit/
│   ├── conftest.py       # Unit-specific: mock data, fixtures simples
│   └── test_logic.py
├── integration/
│   ├── conftest.py       # Integration-specific: TestClient, db fixtures
│   └── test_endpoints.py
└── e2e/
    ├── conftest.py       # E2E-specific: db con seed, auth config
    └── test_flows.py

conftest.py global

# tests/conftest.py
import pytest
from database import FakeDB


@pytest.fixture
def db():
    """Base de datos para cualquier test que la necesite."""
    return FakeDB()


@pytest.fixture(scope="session")
def app_config():
    """Config compartida en toda la sesión."""
    return {"env": "test", "debug": True}

conftest.py para unit

# tests/unit/conftest.py
import pytest


@pytest.fixture
def sample_user():
    """Usuario de ejemplo sin persistencia."""
    return {"id": 1, "name": "Test", "email": "test@test.com"}


@pytest.fixture
def make_user():
    def _make(name="Test", email="test@test.com"):
        return {"name": name, "email": email}
    return _make

Los tests en tests/unit/ ven las fixtures de tests/conftest.py y de tests/unit/conftest.py.

conftest.py para integration

# tests/integration/conftest.py
import pytest
from fastapi.testclient import TestClient
from main import app


@pytest.fixture
def client(db):
    """TestClient con la db inyectada."""
    def override_get_db():
        yield db

    app.dependency_overrides[get_db] = override_get_db
    with TestClient(app) as c:
        yield c
    app.dependency_overrides.clear()

Los tests de integración usan client y db sin importarlos explícitamente.

conftest.py para E2E

# tests/e2e/conftest.py
import pytest


@pytest.fixture
def seeded_db(db):
    """DB con datos de prueba para flujos completos."""
    db.create_user("admin@test.com")
    db.create_user("user@test.com")
    return db

Cada nivel añade lo que necesita. Los tests E2E pueden usar seeded_db además de db y app_config heredados.

Jerarquía de descubrimiento

pytest carga fixtures en este orden (de más específico a más general):

  1. El archivo de test mismo (si define fixtures)
  2. conftest.py del mismo directorio
  3. conftest.py del directorio padre
  4. Así sucesivamente hasta la raíz

Si tests/unit/conftest.py define una fixture sample_data y tests/conftest.py también la define, gana la del directorio más cercano (unit). Las fixtures no se sobrescriben por herencia: la más cercana al test tiene prioridad. Por eso conviene tener en el global solo lo verdaderamente compartido, y en cada subdirectorio lo específico.


autouse Fixtures: Setup Automático

Una fixture con autouse=True se ejecuta en todos los tests del scope donde está definida, sin que la declares como parámetro.

Cuándo usarla

  • Resetear estado global antes de cada test
  • Configurar variables de entorno temporales
  • Limpiar recursos compartidos

Ejemplo

@pytest.fixture(autouse=True)
def reset_state():
    """Limpia estado global antes y después de cada test."""
    reset_database()
    yield
    reset_database()


def test_something():
    # reset_state se ejecutó aunque no la pediste
    assert get_global_state() == {}

Cuándo no usarla

  • ⚠️ Si la fixture es cara: se ejecutará en tests que no la necesitan
  • ⚠️ Si el efecto es implícito: puede confundir a quien lee el test
  • ✅ Mejor para fixtures ligeras de reset o configuración

autouse con scope

Puedes combinar autouse=True con scope para controlar el alcance:

@pytest.fixture(autouse=True, scope="module")
def setup_module_logging():
    """Configura logging una vez por módulo."""
    import logging
    logging.basicConfig(level=logging.DEBUG)
    yield
    logging.getLogger().handlers = []

Con scope="module" el setup corre una vez al entrar al archivo, no en cada test.


Parametrized Fixtures: Múltiples Backends

Una fixture puede ejecutarse múltiples veces con distintos parámetros. Cada test que la usa se ejecuta para cada valor.

Sintaxis

@pytest.fixture(params=["sqlite", "postgres", "mysql"])
def database(request):
    return create_database(request.param)


def test_connection(database):
    # Se ejecuta 3 veces: una por cada backend
    assert database.is_connected()

request.param contiene el valor actual ("sqlite", "postgres", "mysql").

Ejemplo con backends simulados

# test_storage.py
import pytest


def create_database(backend: str):
    """Simula creación según backend."""
    return {"backend": backend, "connected": True}


@pytest.fixture(params=["sqlite", "postgres"])
def database(request):
    db = create_database(request.param)
    yield db
    # Teardown si fuera necesario
    db["connected"] = False


def test_insert(database):
    assert database["connected"] is True
    assert database["backend"] in ("sqlite", "postgres")

Al ejecutar pytest -v verás:

test_insert[sqlite] PASSED
test_insert[postgres] PASSED

Combinar con parametrize

@pytest.fixture(params=["sqlite", "postgres"])
def database(request):
    return create_database(request.param)


@pytest.mark.parametrize("value", [1, 2, 3])
def test_something(database, value):
    # 2 backends × 3 valores = 6 ejecuciones
    assert database.insert("key", value) is not None

Generar Fixtures con Claude Code

Claude Code puede generar fixtures realistas si le das contexto claro. El prompt debe especificar qué datos necesitas y qué estructura tienen.

Prompt base

Genera fixtures para mis tests de [módulo/servicio].
Necesito:
- [describe datos: usuarios, items, permisos, etc.]
- [estructura: dict, clase, ORM model]
- [relaciones: user tiene N items, etc.]
- [valores por defecto y variantes]

Ejemplo de prompt

Genera fixtures para mis tests del módulo `users`.

Necesito:
1. Fixture `db`: base de datos FakeDB vacía
2. Fixture `make_user(db)`: factory que crea usuarios con name, email, role (default: "user")
3. Fixture `admin_user`: usuario con role="admin" ya creado en db
4. Fixture `auth_token(user)`: token JWT para el user (puede ser un string dummy)
5. Los usuarios deben tener id, name, email, role
6. Usa estructura dict para simplicidad

Ejemplo de respuesta de Claude Code

# conftest.py
import pytest
from database import FakeDB


@pytest.fixture
def db():
    return FakeDB()


@pytest.fixture
def make_user(db):
    def _make_user(name="Test User", email="test@test.com", role="user"):
        user = db.create_user(email)
        user["name"] = name
        user["role"] = role
        return user
    return _make_user


@pytest.fixture
def admin_user(db):
    return db.create_user("admin@test.com", role="admin")


@pytest.fixture
def auth_token(user):
    return f"Bearer mock_token_{user['id']}"

Ajusta el output si los nombres no coinciden con tu código. El ciclo es: generar → ejecutar tests → corregir → repetir.

Iterar cuando la generación falla

Si Claude Code genera fixtures que no compilan o no coinciden con tu API:

  1. Pega el error de pytest: "fixture 'db' not found" o "get_db is not defined"
  2. Especifica el contrato: "La función create_user en FakeDB recibe solo email, no role. El role se asigna después."
  3. Pide fixtures mínimas: "Solo necesito db, make_user y auth_token. Nada más."

Cuanto más específico seas en el prompt, menos iteraciones necesitarás.


Ejercicios

Ejercicio 1: Fixture con scope module

Crea una fixture config con scope module que retorne {"env": "test"}. Escribe dos tests que la usen y verifica que reciben la misma instancia (puedes guardar el id() en una variable de módulo para comparar).

Ver solución
# test_scope.py
import pytest

_config_ids = []


@pytest.fixture(scope="module")
def config():
    return {"env": "test"}


def test_config_1(config):
    _config_ids.append(id(config))
    assert config["env"] == "test"


def test_config_2(config):
    _config_ids.append(id(config))
    assert config["env"] == "test"


def test_same_instance():
    assert len(_config_ids) >= 2
    assert _config_ids[0] == _config_ids[1]

Nota: La comparación de id() en un tercer test es un poco frágil (orden de ejecución). Alternativa: usar una variable de módulo dentro de la fixture para contar cuántas veces se creó:

_creation_count = 0

@pytest.fixture(scope="module")
def config():
    global _creation_count
    _creation_count += 1
    return {"env": "test"}

def test_config_created_once(config):
    assert _creation_count == 1

Ejercicio 2: Composition en cadena

Crea las fixtures db, user(db) y auth_token(user) para un sistema donde user tiene id y email, y auth_token retorna f"token_{user['id']}". Escribe un test que use auth_token y verifique que el token contiene el id del usuario.

Ver solución
# test_composition.py
import pytest


def create_db():
    return {"users": [], "next_id": 1}


@pytest.fixture
def db():
    return {"users": [], "next_id": 1}


@pytest.fixture
def user(db):
    uid = db["next_id"]
    db["next_id"] += 1
    user_data = {"id": uid, "email": "test@test.com"}
    db["users"].append(user_data)
    return user_data


@pytest.fixture
def auth_token(user):
    return f"token_{user['id']}"


def test_auth_token_contains_user_id(auth_token, user):
    assert auth_token == f"token_{user['id']}"
    assert user["id"] in auth_token

Ejercicio 3: Factory fixture

Crea una fixture make_item que retorne una función para crear items con name y price (defaults: "Item", 10.0). Escribe un test que cree dos items con distintos valores y verifique ambos.

Ver solución
# test_factory.py
import pytest


@pytest.fixture
def make_item():
    def _make_item(name="Item", price=10.0):
        return {"name": name, "price": price}
    return _make_item


def test_two_items_different_values(make_item):
    item1 = make_item(name="Laptop", price=999.99)
    item2 = make_item(name="Mouse", price=29.99)
    assert item1["name"] == "Laptop" and item1["price"] == 999.99
    assert item2["name"] == "Mouse" and item2["price"] == 29.99


def test_default_item(make_item):
    item = make_item()
    assert item["name"] == "Item"
    assert item["price"] == 10.0

Ejercicio 4: conftest.py por directorio

Crea la estructura tests/unit/conftest.py y tests/integration/conftest.py. En unit define sample_data (dict con una key "value": 42). En integration define client que retorna un dict {"base_url": "http://test"}. Escribe un test en cada carpeta que use su fixture local.

Ver solución
# tests/conftest.py
import pytest


@pytest.fixture
def db():
    return {"items": []}


# tests/unit/conftest.py
import pytest


@pytest.fixture
def sample_data():
    return {"value": 42}


# tests/unit/test_logic.py
def test_sample_data(sample_data):
    assert sample_data["value"] == 42


# tests/integration/conftest.py
import pytest


@pytest.fixture
def client():
    return {"base_url": "http://test"}


# tests/integration/test_api.py
def test_client(client):
    assert client["base_url"] == "http://test"

Ejercicio 5: autouse fixture

Crea una fixture autouse=True que resetee una variable global _counter a 0 antes de cada test. Escribe dos tests que incrementen _counter y verifica que cada test ve _counter == 1 (porque arrancaron desde 0).

Ver solución
# test_autouse.py
import pytest

_counter = 0


@pytest.fixture(autouse=True)
def reset_counter():
    global _counter
    _counter = 0
    yield
    _counter = 0


def test_increment_once():
    global _counter
    _counter += 1
    assert _counter == 1


def test_increment_once_again():
    global _counter
    _counter += 1
    assert _counter == 1  # Reset previo, así que 0+1=1

Ejercicio 6: Parametrized fixture

Crea una fixture storage con params=["memory", "file"] que retorne {"backend": request.param}. Escribe un test que verifique que storage["backend"] está en la lista de params.

Ver solución
# test_parametrized_fixture.py
import pytest


@pytest.fixture(params=["memory", "file"])
def storage(request):
    return {"backend": request.param}


def test_storage_backend(storage):
    assert storage["backend"] in ("memory", "file")

Al ejecutar pytest -v:

test_storage_backend[memory] PASSED
test_storage_backend[file] PASSED

Troubleshooting

Problema 1: Fixture con scope module contamina tests

Síntoma: Los tests fallan cuando se ejecutan juntos pero pasan individualmente.

Causa: Una fixture con scope="module" o scope="session" mantiene estado mutable que un test modifica y otro lee.

Solución: Para datos mutables (db, listas, dicts), usa scope="function". Reserva module/session para recursos inmutables o de solo lectura (config, client HTTP sin estado).


Problema 2: Fixture no encontrada en subdirectorio

Síntoma: fixture 'X' not found al correr tests en tests/unit/.

Causa: La fixture está en conftest.py de otro directorio y no se hereda, o el conftest.py del directorio actual no la define.

Solución: Las fixtures en tests/conftest.py se heredan por todos los subdirectorios. Si defines la fixture en tests/unit/conftest.py, solo está disponible en tests/unit/. Verifica que el archivo se llame exactamente conftest.py y que pytest descubre el directorio.


Problema 3: Dependencia circular entre fixtures

Síntoma: Error de "recursion" o "maximum recursion depth" al ejecutar tests.

Causa: Fixture A depende de B, B depende de C, C depende de A.

Solución: Rompe el ciclo. Extrae la lógica común a una tercera fixture o a una función helper que varias fixtures llamen sin crear dependencia circular.


Problema 4: autouse fixture ralentiza todos los tests

Síntoma: La suite tarda mucho más de lo esperado.

Causa: La fixture autouse=True hace algo costoso (crear DB, llamar API) en cada test, incluso en los que no la necesitan.

Solución: Quita autouse y pasa la fixture explícitamente solo a los tests que la necesitan. O usa scope="session" si el setup puede compartirse de forma segura.


Problema 5: Parametrized fixture genera demasiados tests

Síntoma: params=[a, b, c, d, e] combinado con @pytest.mark.parametrize genera decenas de ejecuciones.

Causa: Producto cartesiano: 5 params × 5 parametrize = 25 tests.

Solución: Reduce los params a los backends o variantes realmente necesarios. Si solo quieres verificar compatibilidad básica, 2-3 valores suelen bastar.


Conexión con Proyecto

El proyecto de validation pipeline de este módulo requiere un conftest.py profesional con fixtures organizadas:

  • ✅ Fixtures globales en tests/conftest.py: db, client (TestClient con db inyectada), auth_token
  • ✅ Fixtures por tipo de test: unit con datos mock simples, integration con TestClient y dependency overrides, E2E con db pre-poblada
  • ✅ Factory fixtures para crear usuarios, items, o respuestas de API con distintos atributos
  • ✅ Scope adecuado: function para db y datos mutables, session para config inmutable
  • ✅ Fixtures parametrizadas si necesitas probar contra varios backends (ej. sqlite vs postgres)

Todo lo que practiques aquí se usa directamente en el proyecto de la cápsula 06.


Resumen

  • ✅ Scope: function (default) para aislamiento, module/session para recursos caros e inmutables
  • ✅ Composition: Las fixtures pueden depender de otras; pytest resuelve el grafo automáticamente
  • ✅ Factory fixtures: Retornan una función para crear datos variables por test
  • ✅ conftest.py: Organiza fixtures por directorio (global, unit, integration, e2e)
  • ✅ autouse: Ejecuta setup/teardown en todos los tests; usar con moderación
  • ✅ Parametrized fixtures: params=[...] para ejecutar tests contra múltiples variantes
  • ✅ Claude Code: Prompts con estructura de datos y dependencias generan fixtures realistas

Próxima cápsula: Validation loops con Claude Code — ciclo automático test→fix→re-test.


Recursos Adicionales

  1. pytest Fixtures — Documentación oficial - Referencia de fixtures
  2. pytest Fixture Scope - function, class, module, session
  3. Parametrizing fixtures - Fixtures con params
  4. conftest.py: sharing fixtures - Organización de fixtures
  5. Factory Boy - Factory patterns para datos de test
  6. Testing Best Practices (Real Python) - Buenas prácticas de testing

Módulo 6, Cápsula 03 — Testing with Claude Code Guide