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
| Estrategia | Velocidad | Aislamiento | Complejidad |
|---|---|---|---|
| Crear/Destruir por test | Lenta | Alto | Baja |
| Rollback de transaccion | Rapida | Alto | Media |
| Datos de referencia compartidos | Rapida | Medio | Media |
| Snapshots de base de datos | Rapida | Alto | Alta |
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.