Factory Boy centraliza la creación de objetos válidos para pruebas. En lugar de repetir constructores largos, cada prueba sobrescribe solo el campo relacionado con el comportamiento que quiere verificar.
Crear una fábrica determinista
from dataclasses import dataclass
import factory
@dataclass
class Usuario:
nombre: str
email: str
activo: bool = True
class UsuarioFactory(factory.Factory):
class Meta:
model = Usuario
nombre = factory.Sequence(lambda n: f"Usuario {n}")
email = factory.LazyAttribute(
lambda obj: obj.nombre.lower().replace(" ", ".") + "@example.com"
)
En una prueba, UsuarioFactory(activo=False) deja clara la variación relevante. Usa SubFactory para relaciones y traits para estados recurrentes, pero no ocultes un grafo enorme detrás de una llamada sencilla.
Una fábrica debe producir el objeto válido más pequeño. Si cada creación escribe en la base de datos, las pruebas unitarias serán lentas y difíciles de aislar. Elige conscientemente entre construir o persistir y limpia las transacciones.
Evita aleatoriedad sin semilla. Un fallo que aparece solo con un nombre generado es difícil de investigar. Combina fábricas con fixtures de pytest para dependencias con ciclo de vida y reserva la fábrica para datos.
La documentación oficial de Factory Boy, consultada el 22 de julio de 2026, explica declaraciones, asociaciones, traits, estrategias e integraciones con ORMs.
Instalar el paquete y elegir la base
Instala Factory Boy como dependencia de desarrollo:
python -m pip install factory-boy
Usa factory.Factory para clases normales y dataclasses. Integraciones como DjangoModelFactory y SQLAlchemyModelFactory conocen la persistencia, pero también facilitan accesos accidentales a la base de datos. Elige la base según la necesidad de la prueba, no solo porque el proyecto utilice un ORM.
Una fábrica útil proporciona valores válidos y previsibles. La prueba aporta el valor que explica su escenario:
def test_usuario_inactivo_no_autentica() -> None:
usuario = UsuarioFactory(activo=False)
assert autenticar(usuario) is False
La sobrescritura comunica la intención de inmediato. Un constructor manual con diez campos ajenos a la regla ocultaría la diferencia importante.
Entender declaraciones y evaluación
Sequence genera valores únicos y deterministas dentro del proceso. LazyAttribute calcula un campo desde otros campos del objeto. LazyFunction llama una función sin recibir el objeto. Iterator recorre una secuencia controlada.
from datetime import UTC, datetime
class UsuarioFactory(factory.Factory):
class Meta:
model = Usuario
nombre = factory.Sequence(lambda n: f"usuario-{n}")
email = factory.LazyAttribute(lambda obj: f"{obj.nombre}@example.test")
creado_en = factory.LazyFunction(lambda: datetime.now(UTC))
rol = factory.Iterator(["lector", "editor"])
La hora actual todavía introduce variación. Si el comportamiento depende del tiempo, sobrescribe el valor o congela el reloj. Utiliza dominios reservados como example.test para impedir que direcciones generadas apunten a destinatarios reales.
Modelar relaciones con SubFactory
SubFactory expresa una relación obligatoria. RelatedFactory crea otro objeto después del principal cuando conviene la dirección inversa.
@dataclass
class Pedido:
cliente: Usuario
total_centavos: int
class PedidoFactory(factory.Factory):
class Meta:
model = Pedido
cliente = factory.SubFactory(UsuarioFactory)
total_centavos = 2500
PedidoFactory(cliente__activo=False) sobrescribe un campo en la subfábrica. La sintaxis con doble guion bajo es potente, pero las cadenas profundas son una alerta. Si un pedido crea además organización, permisos, suscripciones y mensajes, las pruebas se vuelven costosas y opacas. Mantén pequeño el grafo predeterminado y crea relaciones opcionales de forma explícita.
Representar estados recurrentes con traits
Los traits agrupan campos que describen juntos un estado:
class SuscripcionFactory(factory.Factory):
class Meta:
model = Suscripcion
activa = True
cancelada_en = None
class Params:
cancelada = factory.Trait(
activa=False,
cancelada_en=datetime(2026, 1, 15, tzinfo=UTC),
)
SuscripcionFactory(cancelada=True) es más claro que repetir dos cambios coordinados. Usa términos del dominio para los traits, no nombres vagos como especial. Mantén cada trait enfocado y valida los estados imposibles en el modelo.
Los parámetros también pueden controlar declaraciones sin convertirse en campos. Sirven para pocas variantes soportadas. Si interactúan decenas de traits, fábricas separadas o funciones constructoras pueden explicar mejor el dominio.
Elegir entre build y create
Factory Boy ofrece estrategias de construcción y creación. Con Factory, llamar a la clase construye el objeto. En integraciones ORM, create() normalmente persiste y build() se queda en memoria.
Usa objetos en memoria para pruebas unitarias que no requieren base de datos. En integración, persistir es razonable, pero el rollback y la limpieza pertenecen al framework. Una fábrica no reemplaza el aislamiento.
build_batch(3) y create_batch(3) crean lotes. Solicita solo la cantidad requerida. Para probar diez mil filas, un cargador específico suele ser mejor que construir un grafo enorme mediante fábricas.
Usar hooks posteriores con cuidado
PostGeneration y post_generation manejan valores que solo pueden aplicarse después de construir, como relaciones muchos a muchos. Son útiles para requisitos del framework, pero los efectos implícitos deben ser visibles.
class EquipoFactory(factory.Factory):
class Meta:
model = Equipo
nombre = factory.Sequence(lambda n: f"Equipo {n}")
@factory.post_generation
def miembros(self, create, extracted, **kwargs):
if extracted:
for miembro in extracted:
self.agregar_miembro(miembro)
La prueba puede usar EquipoFactory(miembros=[ana, sam]). Documenta si el hook exige persistencia y qué ocurre con build(). No realices llamadas de red, correos o trabajos en segundo plano en hooks. Sustituye esos efectos o crea objetos mediante una interfaz inferior.
Integrar fábricas con pytest
La factory crea datos y la fixture controla el ciclo de vida. Puede exponer la fábrica o devolver una función con valores específicos:
import pytest
@pytest.fixture
def usuario_factory():
creados: list[Usuario] = []
def crear_usuario(**cambios) -> Usuario:
usuario = UsuarioFactory(**cambios)
creados.append(usuario)
return usuario
yield crear_usuario
creados.clear()
Para bases de datos, confía en las fixtures transaccionales del framework. No añadas borrado manual que compita con el rollback. La guía de parametrización con pytest explica cómo ejecutar el mismo comportamiento con pocas variaciones relevantes.
Mantener reproducible la aleatoriedad
Factory Boy integra Faker y declaraciones fuzzy. La variedad puede descubrir supuestos sobre Unicode, longitudes y campos opcionales, pero el azar sin control produce fallos difíciles de repetir. Prefiere valores deterministas.
Cuando la generación aleatoria tenga propósito, fija y registra la semilla. Reduce el valor que falla antes de añadirlo como regresión permanente. Las pruebas basadas en propiedades pueden ser mejores si buscas generación sistemática y reducción automática.
Mantener las fábricas cuando cambia el modelo
Guarda las factories cerca de la suite y revísalas al cambiar campos obligatorios o validaciones. Un valor que elude una nueva regla permite estados que producción rechazaría. Incluir cada campo opcional en todas las fábricas también genera mantenimiento innecesario.
Evita importar servicios de aplicación en módulos de factories. Una fábrica describe construcción, no orquesta casos de uso. Si un agregado válido solo se crea mediante un servicio de dominio, llama ese servicio en pruebas de integración y reserva las fábricas para sus entradas.
Lista de revisión
Comprueba que la fábrica produzca el objeto válido más pequeño, que los valores sean deterministas, que las direcciones usen dominios seguros y que una llamada no cree un grafo sorprendente. Confirma si la estrategia escribe en la base, quién limpia las filas y si los hooks disparan efectos.
Las fábricas mejoran las pruebas cuando muestran la diferencia relevante. Si PedidoFactory(total_centavos=0) revela inmediatamente el límite probado, la abstracción ayuda. Si exige rastrear varios traits y hooks, simplifícala.
Cuándo no usar una factory
Para un objeto con dos campos, un constructor directo puede ser más claro. Los datos tabulares estáticos también pueden encajar mejor en pytest.mark.parametrize. Usa Factory Boy cuando valores válidos, relaciones o integración ORM eliminen repetición relevante.
No compartas instancias creadas entre pruebas para ahorrar tiempo. La ganancia aparente perjudica el aislamiento y la ejecución paralela. Si persistir resulta caro, investiga consultas, reduce el grafo predeterminado y reserva fixtures de alcance amplio para datos realmente inmutables.
Una buena factory es infraestructura de prueba pequeña y previsible. Debe facilitar la lectura del escenario, no imitar toda la aplicación.