El Problema de los Datos de Test

A medida que las suites de test crecen, gestionar datos de test se convierte en uno de los mayores desafios de mantenimiento. Considera una suite con 500 tests, cada uno requiriendo un objeto User. Si el modelo User agrega un nuevo campo requerido, debes actualizar los 500 tests.

Los datos hardcodeados crean tres problemas: duplicacion, fragilidad y opacidad (tests abarrotados de datos irrelevantes para lo que verifican).

Fabricas de Datos de Test

Una fabrica de datos es un modulo centralizado que crea objetos de test con valores predeterminados sensatos. Cada test solo especifica los campos relevantes para su escenario.

Patron Factory

public class UserFactory {
    private static int counter = 0;

    public static User createDefault() {
        counter++;
        return User.builder()
            .name("Test User " + counter)
            .email("user" + counter + "@test.com")
            .role("user")
            .active(true)
            .build();
    }

    public static User createAdmin() {
        return createDefault().toBuilder()
            .role("admin")
            .permissions(List.of("read", "write", "delete", "manage_users"))
            .build();
    }
}

@Test
void adminCanDeleteUsers() {
    User admin = UserFactory.createAdmin();
    User target = UserFactory.createDefault();
    assertTrue(userService.canDelete(admin, target));
}

Patron Builder

public class UserBuilder {
    private String name = "Default User";
    private String email = "default@test.com";
    private String role = "user";
    private boolean active = true;

    public static UserBuilder aUser() { return new UserBuilder(); }
    public UserBuilder withName(String name) { this.name = name; return this; }
    public UserBuilder withRole(String role) { this.role = role; return this; }
    public UserBuilder inactive() { this.active = false; return this; }
    public User build() { return new User(name, email, role, active); }
}

// Se lee como una especificacion
User admin = UserBuilder.aUser().withName("Alice").withRole("admin").build();

Ejemplo en Python (Factory Boy)

import factory
from models import User

class UserFactory(factory.Factory):
    class Meta:
        model = User

    name = factory.Faker('name')
    email = factory.LazyAttribute(lambda obj: f"{obj.name.lower().replace(' ', '.')}@test.com")
    role = "user"
    active = True

class AdminFactory(UserFactory):
    role = "admin"
    permissions = ["read", "write", "delete", "manage_users"]

user = UserFactory()
admin = AdminFactory()

Test Fixtures

Los fixtures gestionan el ciclo de vida del test — configurando precondiciones y limpiando despues.

Fixtures en JUnit 5

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class OrderServiceTest {
    private DatabaseConnection db;

    @BeforeAll
    void setupOnce() { db = DatabaseConnection.create("test-db"); }

    @BeforeEach
    void setupEach() { db.beginTransaction(); }

    @AfterEach
    void teardownEach() { db.rollbackTransaction(); }

    @AfterAll
    void teardownOnce() { db.close(); }
}

Fixtures en Pytest

@pytest.fixture
def db():
    connection = create_test_database()
    yield connection
    connection.cleanup()

@pytest.fixture
def user(db):
    user = UserFactory.create()
    db.save(user)
    return user

def test_admin_can_delete_user(admin_user, user):
    assert user_service.can_delete(admin_user, user) is True

Fixtures en Playwright

exports.test = base.extend({
    authenticatedPage: async ({ page }, use) => {
        await page.goto('/login');
        await page.fill('#email', 'admin@test.com');
        await page.fill('#password', 'password');
        await page.click('#submit');
        await use(page);
    },
    testUser: async ({ request }, use) => {
        const response = await request.post('/api/users', {
            data: { name: 'Test User', email: 'test@example.com' }
        });
        const user = await response.json();
        await use(user);
        await request.delete(`/api/users/${user.id}`);
    }
});

Estrategias de Datos de Test

EstrategiaVelocidadAislamientoComplejidad
Crear/Destruir por testLentaAltoBaja
Rollback de transaccionRapidaAltoMedia
Datos de referencia compartidosRapidaMedioMedia
Snapshots de base de datosRapidaAltoAlta

Ejercicios

Ejercicio 1: Construir una Biblioteca de Factories

Crea factories para User, Product y Order, implementa patron Builder con API fluida, crea metodos para personas comunes.

Ejercicio 2: Jerarquia de Fixtures

Implementa fixtures para setup/teardown con rollback de transaccion, crea fixture de cliente API autenticado, construye composicion de fixtures.

Ejercicio 3: Comparacion de Estrategias

Implementa los mismos 3 tests con estrategias Create/Destroy y Transaction Rollback, mide tiempos, recomienda una estrategia.