Generar Datos de Test (Faker + Factories)
Cómo generar datos de test realistas y deterministas con Faker, factory-boy y generadores type-aware para suites de test confiables en Python, JavaScript y Java.
Descripción General
Los datos de test hardcodeados (name = "John", email = "test@test.com") se vuelven obsoletos rápido, no exponen casos edge y no se parecen a las distribuciones de producción. Los generadores producen datos realistas, variados y deterministas: los tests se vuelven más confiables mientras el mantenimiento de fixtures se reduce.
Cuándo Usar
- Mantienes docenas de objetos de test hardcodeados que divergen del esquema de producción
- Los casos edge (strings vacíos, Unicode, valores muy largos) nunca se testean porque son tediosos de escribir
- Los tests de integración necesitan una base de datos sembrada con cientos de filas realistas
- Quieres que los tests ejerciten reglas de validación con distribuciones de entrada variadas
- Los tests de carga necesitan grandes volúmenes de datos plausibles
Si el problema son los fixtures en sí — setup compartido, teardown, estado de la base de datos — combina esto con la configuración de fixtures de test.
Cuándo NO Usar
- El test requiere un escenario muy específico y conocido — hardcodéalo explícitamente
- El determinismo entre ejecuciones importa más que la variedad — siembra el generador pero mantén valores mínimos
- El esquema de datos es trivial (2-3 campos) — un objeto literal es más claro
- Estás testeando una librería tipo Faker en sí misma — usa entradas controladas y predecibles
Implementación Paso a Paso
Python
from faker import Faker
from dataclasses import dataclass
from typing import List
import factory
from factory import Faker as FactoryFaker
fake = Faker()
Faker.seed(12345) # Determinístico entre ejecuciones
# Uso básico de Faker
fake.name() # 'John Smith'
fake.email() # 'john.smith@example.com'
fake.ipv4() # '192.168.1.45'
fake.uuid4() # '550e8400-e29b-41d4-a716-446655440000'
# factory-boy para objetos ORM
@dataclass
class User:
id: int
name: str
email: str
age: int
is_active: bool
class UserFactory(factory.Factory):
class Meta:
model = User
id = factory.Sequence(lambda n: n)
name = FactoryFaker('name')
email = FactoryFaker('email')
age = factory.Faker('random_int', min=18, max=90)
is_active = True
# Uso
user = UserFactory() # Instancia única
users = UserFactory.build_batch(100) # 100 instancias
admin = UserFactory(name="Admin User", age=30)
# Provider propio para datos del dominio
from faker.providers import BaseProvider
class ProductProvider(BaseProvider):
def sku(self):
categories = ['ELEC', 'BOOK', 'HOME', 'TOY']
return f"{self.random_element(categories)}-{self.random_int(1000, 9999)}"
fake.add_provider(ProductProvider)
fake.sku() # 'ELEC-4521'
# Dataset determinista para property-based tests
import hypothesis.strategies as st
user_strategy = st.builds(
User,
id=st.integers(min_value=1),
name=st.text(min_size=1, max_size=100),
email=st.emails(),
age=st.integers(min_value=0, max_value=120),
is_active=st.booleans()
)
JavaScript
import { faker } from '@faker-js/faker';
// Semilla para determinismo
faker.seed(12345);
// Generadores básicos
faker.person.fullName(); // 'John Smith'
faker.internet.email(); // 'john.smith@example.com'
faker.number.int({ min: 18, max: 65 }); // 34
// Función factory
function createUser(overrides = {}) {
return {
id: faker.string.uuid(),
name: faker.person.fullName(),
email: faker.internet.email(),
age: faker.number.int({ min: 18, max: 90 }),
avatar: faker.image.avatar(),
isActive: true,
...overrides
};
}
// Generar lote
const users = Array.from({ length: 100 }, () => createUser());
// Helpers de faker específicos del dominio
const createOrder = (overrides = {}) => ({
id: faker.string.uuid(),
customerId: faker.string.uuid(),
items: Array.from({ length: faker.number.int({ min: 1, max: 5 }) }, () => ({
sku: `SKU-${faker.string.alphanumeric(6).toUpperCase()}`,
quantity: faker.number.int({ min: 1, max: 10 }),
price: faker.commerce.price({ min: 5, max: 500 })
})),
status: faker.helpers.arrayElement(['pending', 'paid', 'shipped', 'delivered']),
createdAt: faker.date.past(),
...overrides
});
// Datos deterministas para snapshots
faker.seed(42);
const snapshotUser = createUser({ name: 'Snapshot User' });
Java
import net.datafaker.Faker;
import java.util.List;
import java.util.stream.IntStream;
public class TestDataGenerator {
private static final Faker faker = new Faker();
public static User createUser() {
return User.builder()
.id(faker.number().randomNumber())
.name(faker.name().fullName())
.email(faker.internet().emailAddress())
.age(faker.number().numberBetween(18, 90))
.isActive(true)
.build();
}
public static List<User> createUsers(int count) {
return IntStream.range(0, count)
.mapToObj(i -> createUser())
.toList();
}
// JUnit 5 parametrizado con datos generados
public static Stream<Arguments> emailProvider() {
return Stream.generate(() -> Arguments.of(faker.internet().emailAddress()))
.limit(50);
}
}
// Instancio para generación type-aware
import org.instancio.Instancio;
import org.instancio.Select;
User user = Instancio.of(User.class)
.set(Select.field("role"), "admin")
.generate(Select.field("age"), gen -> gen.ints().range(18, 90))
.create();
List<User> users = Instancio.ofList(User.class).size(100).create();
Elegir un Enfoque
Cuatro herramientas cubren la mayoría de necesidades, y resuelven problemas distintos:
Faker solo te da valores de campo realistas pero sin estructura. Las factories construyen objetos completos (o filas ORM) con defaults sensatos y overrides por test. Los generadores property-based como Hypothesis exploran el espacio de entrada por ti y reducen los fallos al caso mínimo — úsalos cuando verificas invariantes, no escenarios concretos. La receta de property-based testing profundiza en ese camino.
| Enfoque | Ideal para | Herramientas |
|---|---|---|
| Literal hardcodeado | Un escenario exacto, tests snapshot/golden | Ninguna — escribe el objeto |
| Faker con semilla | Valores de campo realistas, fuzzing de validación | faker (Py), @faker-js/faker, DataFaker |
| Factory | Objetos completos, filas ORM, overrides por test | factory-boy, Instancio, funciones factory |
| Property-based | Invariantes, descubrimiento de edge cases, shrinking | hypothesis, fast-check |
Las suites maduras acaban combinando dos o tres: factories para el grafo de objetos, Faker dentro de ellas para los valores, y una semilla común para que cada ejecución vea los mismos datos.
Lo Que Funciona
- Siembra siempre el generador aleatorio. Sin semilla, un test que falla en CI puede pasar en local porque los datos eran distintos. Pon
Faker.seed()ofaker.seed()en un archivo de setup global. - Sobrescribe campos concretos para tests de escenario:
createUser({ role: 'admin' })le gana a esperar que el generador aleatorio produzca un admin. - Restringe los rangos al dominio. Una edad aleatoria entre 0 y 120 produce mayormente datos inválidos; 18-90 encaja en usuarios adultos.
- Genera los datos cerca del test. Un fixture
users.jsoncompartido diverge del esquema; la generación programática actualiza todos los tests al añadir un campo. - Incluye casos edge a propósito: strings vacíos, longitudes máximas, Unicode y nulos junto a los datos generados del camino feliz.
Errores Comunes
- Datos aleatorios sin semilla — los tests fallan de forma intermitente porque un email aleatorio chocó con una constraint de unicidad, o un string aleatorio pareció SQL injection por accidente.
- Rangos demasiado permisivos — los defaults de
faker.number.int()son lo bastante anchos para violar reglas de negocio (precios negativos, nombres de 200 caracteres). - Mezclar datos generados y hardcodeados sin criterio — media suite usa Faker, la otra mitad usa literales, y nadie sabe cuál usar.
- Exportar un fixture JSON una vez y nunca regenerarlo — los datos obsoletos no ejercitan las reglas de validación añadidas después.
- Generadores que dependen entre sí —
createOrder()llamando internamente acreateUser()oculta el usuario y hace imposible afirmar sobre la relación.
Ver También
- Documentación de Faker: providers, locales y semillas para Python.
- Documentación de factory_boy: factories ORM para Django, SQLAlchemy y objetos planos.
- Hypothesis: generación property-based con shrinking, en Python.
- Instancio: generación de objetos type-aware para Java.
- Sembrar una base de datos: cuando los datos generados deben llenar una base de datos de dev o test.
Código complementario: los generadores ejecutables para los tres lenguajes están en el repo complementario.
Preguntas frecuentes
¿Por qué debería usar factories en lugar de fixtures estáticos?
Las factories generan datos bajo demanda y se adaptan a cambios de esquema automáticamente. Un users.json en git se vuelve obsoleto y deja de ejercitar las nuevas reglas de validación. Guarda los fixtures estáticos solo para tests golden-path donde importan los datos exactos, como snapshots.
¿Qué datos nunca deberían aparecer en tests?
Nunca datos personales reales, credenciales de producción ni datos de pago. Los datos sintéticos deben parecer reales sin exponer a nadie — incluso las copias de producción "enmascaradas" pueden filtrar a través de campos residuales.
¿Cómo mantengo los datos de test deterministas entre CI y ejecuciones locales?
Siembra el generador globalmente: Faker.seed(12345) en conftest.py, faker.seed(12345) en globalSetup de Jest, new Faker(new Random(12345)) en Java. Nunca dependas de la hora del sistema o /dev/urandom — los fallos intermitentes suelen ser un generador sin semilla escondido en un helper.
¿Cómo genero datos con relaciones entre entidades?
Pasa los objetos relacionados explícitamente: const user = createUser(); const order = createOrder({ customerId: user.id }). No hagas que createOrder() llame a createUser() internamente — oculta el usuario y rompe las aserciones sobre la relación. En Python, factory.SubFactory(UserFactory) enlaza factories limpiamente.
¿Qué casos edge deberían incluir los datos generados?
Cubre los límites que tus validadores cuidan: strings vacíos, longitudes máximas, Unicode y emoji, nulos, cero, negativos y fechas en los límites (epoch, futuro lejano). Genera ~80% de los casos con Faker y añade la lista de edge explícitamente, o deja que una herramienta property-based los explore por ti.
¿Cómo manejo constraints de unicidad con datos generados?
Faker no garantiza unicidad. Añade un contador (f"user{n}@example.com"), usa factory.Sequence en factory-boy, o prefiere uuid(), que es único por diseño. No confíes en la suerte: las colisiones se hacen más probables al crecer el lote (paradoja del cumpleaños).
¿Cómo genero datos para tests de integración de base de datos?
Usa factory-boy con tu ORM: class UserFactory(factory.django.DjangoModelFactory) con Meta: model = User, luego UserFactory.create() o create_batch(1000). Envuelve los tests en una transacción con rollback para que los datos no se acumulen entre casos.
¿Puede Faker generar datos localizados para tests de i18n?
Sí — Faker('es_ES'), Faker('ja_JP') producen nombres, direcciones y teléfonos con formato local correcto. Combina con casos Unicode explícitos (CJK, RTL, emoji) y chequeos de bytes vs caracteres: un carácter CJK puede ocupar 3 bytes en UTF-8.
¿Cómo limpio los datos de test generados después de los tests?
Haz rollback de una transacción por test, o trunca un esquema de test dedicado entre suites. Para archivos, usa directorios tempfile que el SO limpia solo. Nunca apuntes nada de esto a producción.
Recursos Relacionados
Configurar Fixtures de Test (pytest, Jest, JUnit)
Cómo crear, acotar y limpiar fixtures de test con pytest, hooks de Jest y el ciclo de vida de JUnit 5 — más conftest.py, factories de fixtures y patrones de teardown.
RecipeMedir Cobertura de Test
Cómo medir, reportar y hacer cumplir la cobertura de código con branch y condition coverage usando pytest-cov, nyc y JaCoCo para quality gates significativos.
PatternPatron Factory
Crea objetos sin especificar la clase exacta a instanciar. Un patrón de diseño creacional para la creación flexible de objetos.
RecipeProperty-Based Testing con Hypothesis
Cómo usar Hypothesis para property-based testing en Python, generando cientos de casos de test automáticamente desde strategies en lugar de escribirlos a mano.
RecipeImplementar Property-Based Testing
Cómo escribir tests property-based con Hypothesis, fast-check y jqwik que generan miles de entradas y encuentran casos límite.
RecipeVitest Snapshot Testing para React
Cómo usar Vitest snapshot testing para detectar cambios no intencionados en la UI de componentes React, incluyendo inline snapshots y flujos de actualización.