¿Por Qué Probar Bases de Datos Directamente?
Los tests de API verifican lo que la API retorna, pero no verifican lo que realmente se almacena en la base de datos. Una API podría retornar una respuesta exitosa mientras los datos escritos son incorrectos, incompletos o violan constraints.
El testing de base de datos detecta problemas que los tests de API no capturan:
- Datos escritos en la tabla o columna incorrecta
- Enforcement de constraints faltante o incorrecto
- Efectos secundarios de triggers no reflejados en respuestas de API
- Problemas de rendimiento de índices con volúmenes de datos realistas
- Violaciones de aislamiento de transacciones causando corrupción de datos
SQL Esencial para Testers
Verificando Datos Después de Llamadas API
Después de llamar un endpoint de API, consulta la base de datos para confirmar la operación:
-- Después de POST /api/users con name="Alice", email="alice@test.com"
SELECT id, name, email, created_at
FROM users
WHERE email = 'alice@test.com';
Testing de Constraints
Constraint NOT NULL:
INSERT INTO users (name, email) VALUES (NULL, 'test@test.com');
-- Esperado: ERROR: null value in column "name" violates not-null constraint
Constraint UNIQUE:
INSERT INTO users (name, email) VALUES ('Alice', 'alice@test.com');
INSERT INTO users (name, email) VALUES ('Bob', 'alice@test.com');
-- Esperado: ERROR: duplicate key value violates unique constraint
Constraint FOREIGN KEY:
INSERT INTO orders (user_id, total) VALUES (99999, 50.00);
-- Esperado: ERROR: insert or update violates foreign key constraint
Constraint CHECK:
INSERT INTO products (name, price) VALUES ('Widget', -10.00);
-- Esperado: ERROR: new row violates check constraint
Testing de Tipos de Datos y Precisión
-- Valores monetarios deben usar DECIMAL, no FLOAT
SELECT CAST(0.1 + 0.2 AS FLOAT); -- Podría retornar 0.30000000000000004
SELECT CAST(0.1 + 0.2 AS DECIMAL(10,2)); -- Retorna 0.30
Testing de Stored Procedures
Los stored procedures encapsulan lógica de negocio en la base de datos. Deben probarse como cualquier otro código.
CALL transfer_funds(
@from_account := 'ACC001',
@to_account := 'ACC002',
@amount := 100.00
);
-- Verificar: cuenta origen disminuyó en 100
SELECT balance FROM accounts WHERE account_id = 'ACC001';
-- Verificar: cuenta destino aumentó en 100
SELECT balance FROM accounts WHERE account_id = 'ACC002';
-- Verificar: log de transacción creado
SELECT * FROM transaction_log
WHERE from_account = 'ACC001' AND to_account = 'ACC002'
ORDER BY created_at DESC LIMIT 1;
Edge cases a probar:
- Transferir más del saldo disponible (debería fallar)
- Transferir a la misma cuenta (debería fallar o ser no-op)
- Transferir monto cero o negativo (debería fallar)
- Transferencias concurrentes que excedan el saldo
Testing de Transacciones y Aislamiento
Propiedades ACID
Test de atomicidad: Inicia una transacción con múltiples statements. Fuerza una falla a mitad de camino. Verifica que ningún statement tomó efecto.
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 99999;
ROLLBACK;
SELECT balance FROM accounts WHERE id = 1;
-- Debe ser el saldo original
Testing de Índices
Los índices aceleran queries pero tienen trade-offs. Prueba que:
- Queries en columnas indexadas usan el índice (verifica con EXPLAIN).
- Los índices no fallan con valores NULL.
- Los índices compuestos funcionan para los patrones de query esperados.
EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'alice@test.com';
-- Busca "Index Scan" o "Index Only Scan" (no "Seq Scan")
Gestión de Datos de Test
Estrategias de Setup
1. Rollback de transacción (más rápido):
BEGIN TRANSACTION → Insert datos → Ejecutar tests → Verificar → ROLLBACK
2. Truncate después del test:
TRUNCATE TABLE orders CASCADE;
TRUNCATE TABLE users CASCADE;
3. Base de datos dedicada para tests: Crea una base de datos fresca para cada ejecución.
Ejercicio: Laboratorio de Testing de Base de Datos SQL
Configuración
Usa PostgreSQL con Docker:
docker run -d --name postgres-test \
-e POSTGRES_PASSWORD=testpass \
-e POSTGRES_DB=testdb \
-p 5432:5432 \
postgres:16
Crea el schema de test:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
role VARCHAR(20) DEFAULT 'user' CHECK (role IN ('user', 'admin', 'moderator')),
balance DECIMAL(10, 2) DEFAULT 0.00 CHECK (balance >= 0),
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id) ON DELETE RESTRICT,
total DECIMAL(10, 2) NOT NULL CHECK (total > 0),
status VARCHAR(20) DEFAULT 'pending' CHECK (status IN ('pending', 'paid', 'shipped', 'cancelled')),
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE order_items (
id SERIAL PRIMARY KEY,
order_id INTEGER REFERENCES orders(id) ON DELETE CASCADE,
product_name VARCHAR(200) NOT NULL,
quantity INTEGER NOT NULL CHECK (quantity > 0),
unit_price DECIMAL(10, 2) NOT NULL CHECK (unit_price >= 0)
);
Tarea 1: Testing de Constraints
Escribe SQL que pruebe cada constraint. Documenta el error esperado:
- Insertar usuario con nombre NULL.
- Insertar dos usuarios con el mismo email.
- Insertar usuario con role ‘superadmin’.
- Insertar usuario con balance negativo.
- Insertar pedido para user_id inexistente.
- Insertar pedido con total = 0.
- Insertar order_item con quantity = 0.
- Eliminar usuario que tiene pedidos (RESTRICT debe prevenirlo).
- Eliminar pedido (CASCADE debe eliminar sus order_items).
Tarea 2: Verificación de Integridad de Datos
Inserta datos de test y verifica integridad con queries.
Tarea 3: Testing de Transacciones
Prueba la atomicidad de una operación “crear pedido”:
- Inicia transacción → inserta pedido → inserta items → deduce balance → commit.
- Repite con saldo insuficiente → verifica rollback completo.
Tarea 4: Testing de Rendimiento
- Inserta 100,000 usuarios con datos aleatorios.
- Consulta por email — registra tiempo con EXPLAIN ANALYZE.
- Elimina el índice y repite — compara tiempos.
Entregables
- Scripts SQL para todos los tests de constraints con resultados.
- Queries de verificación de integridad con resultados.
- Scripts de tests de transacciones.
- Comparación de rendimiento con/sin índice.