beginner Por Mathias Paulenko

Generar Datos de Test

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.

Temas: testing

Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.

Descripción General

Los datos de test hardcodeados (name = "John", email = "test@test.com") rápidamente se vuelven obsoletos, fallan en exponer casos edge y no representan las distribuciones de datos de producción. Los generadores producen datos realistas, variados y deterministas que hacen los tests más confiables mientras reducen el mantenimiento manual de fixtures.

Cuándo Usar

  • For alternatives, see JUnit5 Soft Assertions with AssertJ.

  • 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

Cuándo NO Usar

  • El test requiere un escenario muy específico y conocido — hardcodéalo explícitamente
  • El determinismo entre ejecuciones es más importante que la variedad de datos — siembra el generador pero mantén valores mínimos
  • El esquema de datos es extremadamente simple (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)

# Proveedor personalizado para datos específicos 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 determinístico para tests property-based
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';

// Seed 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 batch
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 determinísticos 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();

Lo que funciona

  • Siempre siembra tu generador aleatorio. Sin una seed, un test que falla en CI puede pasar localmente porque los datos eran diferentes. Configura Faker.seed() o faker.seed() en un archivo de setup global.
  • Sobrescribe campos específicos para tests de escenario. createUser({ role: 'admin' }) es más claro que esperar que el generador aleatorio produzca un admin.
  • Usa distribuciones realistas. Una edad aleatoria entre 0 y 120 producirá principalmente datos inválidos. Restringe rangos para que coincidan con tu dominio (18-90 para adultos).
  • Genera datos cerca del test. Un archivo de fixture global users.json diverge del esquema. Genera programáticamente para que agregar un nuevo campo actualice todos los tests automáticamente.
  • Incluye casos edge intencionalmente. Testea strings vacíos, longitudes máximas, Unicode y valores null explícitamente junto con datos happy-path generados.

Errores Comunes

  • Datos aleatorios sin seed. Los tests fallan intermitentemente porque un email aleatorio coincidió con una restricción de unicidad, o un string aleatorio contuvo un patrón de inyección SQL.
  • Rangos demasiado permisivos. faker.number.int() usa rangos grandes por defecto que pueden violar reglas de negocio (precios negativos, nombres de 200 caracteres).
  • Mezclar datos generados y hardcodeados inconsistentemente. Algunos tests usan Faker, otros literales — el test suite tiene cobertura inconsistente y los desarrolladores no saben cuál usar.
  • No regenerar archivos de fixture estáticos. Exportar un fixture JSON una vez y commitearlo a git significa que los datos nunca ejercitan nuevas reglas de validación agregadas después del export.
  • Generadores que dependen entre sí. createOrder() llamando createUser() internamente oculta el usuario del test, haciendo imposibles las aserciones sobre la relación.

Lectura Adicional

  • Documentación oficial: consulta la referencia actualizada del framework o herramienta utilizada.
  • Guías relacionadas: explora las guías de testing y factory-pattern para profundizar.
  • Patrones complementarios: revisa los patrones de diseño aplicables a tu stack tecnológico.
  • Postmortems públicos: estudia incidentes reales de equipos que enfrentaron problemas similares en producción.

Notas de Producción

  • Despliega gradualmente usando canary o blue-green para detectar regresiones temprano.
  • Configura alertas para errores, latencia p99 y tasa de fallos antes de habilitar en producción.
  • Documenta el rollback en el runbook; prueba el procedimiento en staging al menos una vez por trimestre.
  • Revisa logs estructurados con correlation IDs para trazar requests end-to-end en incidentes.

Puntos Clave

  • Aplica generar datos de test cuando necesites una solución práctica para tu caso de uso.
  • Monitorea el rendimiento después de implementar; mide latencia, errores y uso de recursos antes y después.
  • Revisa la sección de Troubleshooting ante errores comunes; la mayoría tienen causa raíz documentada con solución.
  • Mantén dependencias actualizadas y ejecuta tests en CI para prevenir regresiones en producción.

Troubleshooting

  • Flaky tests: isolate shared state, time, and randomness. Make tests independent and deterministic; quarantine persistently flaky tests.
  • High coverage but bugs in production: coverage does not guarantee correctness. Add mutation testing, property-based tests, or contract tests.
  • Slow test suite: parallelize, mock slow dependencies, and avoid end-to-end tests for logic that can be unit tested.
  • Tests pass locally but fail in CI: check environment differences, timezone, locale, and dependency versions. Pin tool versions.
  • Debugging a failing integration test: Reset state before each test.

Errores Comunes en Producción

  • Copiar el ejemplo sin adaptarlo a volúmenes y modos de fallo reales.
  • Saltar tests de carga e inyección de errores antes del primer despliegue productivo.
  • Codificar valores fijos que deberían ser configurables por entorno.
  • Olvidar agregar logging y monitoreo en cada paso.
  • Desplegar sin plan de rollback ni estrategia de backup probada.
  • Asumir que el ejemplo mínimo escalará sin agregar caché o procesamiento por lotes.
  • No documentar la versión y configuración usadas en producción.
  • Dejar la receta sin cambios cuando evolucionan las dependencias o la escala.

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 schema automáticamente. Los fixtures estáticos se vuelven obsoletos cuando se agregan o eliminan campos — un archivo users.json...
¿Cómo mantengo los datos de test deterministas entre CI y runs locales?
Siembra tu generador aleatorio con un valor fijo en un archivo de setup global. En Python, llama Faker.seed(12345) en conftest.py. En JavaScript, llama faker.seed(12345) en un globalSetup de Jest. En...
¿Qué datos nunca deberían aparecer en tests?
Nunca uses datos personales reales, credenciales de producción o información de pagos. Usa datos sintéticos que se parezcan a los reales sin exponer a nadie. Faker genera nombres, emails y...
¿Cómo genero datos con relaciones entre entidades?
Pasa objetos relacionados explícitamente: const user = createUser(); const order = createOrder({ customerId: user.id }). No hagas que createOrder() llame internamente a createUser() — esto oculta el...
¿Cómo genero datos de edge case sistemáticamente?
Combina Faker con listas explícitas de edge cases. Genera 80% de los datos de test con Faker para cobertura amplia, luego agrega 20% de edge cases dirigidos: strings vacíos, strings de longitud...
¿Cómo genero datos para tests de integración de base de datos?
Usa factory-boy con SQLAlchemy o Django ORM: class UserFactory(factory.django.DjangoModelFactory) con Meta: model = User. Llama UserFactory.create() para insertar en la base de datos. Usa un fixture...
¿Cómo comparto generadores de datos de test entre test suites?
Extrae factories en un módulo compartido: tests/factories/user_factory.py. Importa en archivos de test: from tests.factories import UserFactory. Para JavaScript, exporta desde test-utils/: export {...
¿Cómo genero payloads de API realistas para contract tests?
Usa Faker para generar valores de campos, luego wrappéalos en el schema esperado de la API. Para specs OpenAPI, usa @stoplight/prism-cli para generar mock data desde el spec. Para protobuf, usa buf...
¿Cómo genero datos de test basados en tiempo para tests de scheduling?
Usa los métodos de fecha de Faker con puntos de referencia fijos. Genera fechas relativas a una base conocida: faker.date.between({ from: '2026-01-01', to: '2026-12-31' }). Para tests de scheduling,...
¿Cómo genero datasets grandes para load testing?
Usa generación en batch con factory.build_batch(N) en Python o Array.from({ length: N }, () =&gt; createUser()) en JavaScript. Para 100K+ filas, streamea data a un archivo o base de datos en lugar de...