Fundamentos de Testing
Comprendiendo por qué testeamos, tipos de testing y desarrollando una mentalidad de testing
Por Qué Importa el Testing
El testing de software es el proceso de verificar que tu aplicación funciona como se espera y continúa funcionando a medida que haces cambios. No se trata solo de encontrar bugs—se trata de construir confianza, permitir desarrollo rápido y servir como documentación viva para tu código base.
🎯 El Valor del Testing:
Los tests te permiten refactorizar con confianza, detectar bugs temprano, documentar comportamiento y desplegar más rápido. La inversión de tiempo inicial paga dividendos durante toda la vida de la aplicación.
Beneficios del Testing Automatizado
🛡️ Confianza en los Cambios
Refactoriza o agrega funcionalidades sin miedo a romper funcionalidad existente. Los tests actúan como una red de seguridad.
📚 Documentación Viva
Los tests describen cómo debe comportarse tu código. Son documentación siempre actualizada.
🐛 Detección Temprana de Bugs
Detecta problemas antes de que lleguen a producción. Los bugs encontrados temprano son más baratos y fáciles de arreglar.
🚀 Desarrollo Más Rápido
Los tests automatizados se ejecutan en segundos. El testing manual toma minutos u horas por cada cambio.
🔄 Mejor Diseño
El desarrollo guiado por tests (TDD) a menudo conduce a código más modular y mantenible.
👥 Colaboración en Equipo
Los nuevos miembros del equipo pueden cambiar código con confianza. Los tests previenen regresiones cuando múltiples personas trabajan en la misma base de código.
Tipos de Testing
🔬 Testing Unitario
Testear funciones individuales, métodos o componentes en aislamiento.
Características:
- • Ejecución rápida (milisegundos)
- • Testear una cosa a la vez
- • Simular dependencias externas
- • Fácil de depurar cuando fallan
🔗 Testing de Integración
Testear cómo múltiples unidades trabajan juntas.
Características:
- • Testear interacciones entre módulos
- • Puede involucrar base de datos, APIs, sistema de archivos
- • Más lentos que tests unitarios
- • Detectan problemas de integración
🌐 Testing End-to-End (E2E)
Testear flujos de usuario completos de principio a fin.
Características:
- • Testear desde la perspectiva del usuario
- • Se ejecutan en navegador real
- • Más lentos pero mayor confianza
- • Testean el stack completo de la aplicación
📸 Testing de Snapshot
Capturar y comparar la salida de componentes.
Características:
- • Detectar cambios inesperados en UI
- • Útil para librerías de componentes
- • Rápido de escribir
- • Revisar cambios cuidadosamente
Tu Primer Test Unitario
Escribamos un test unitario simple usando Jest:
// sum.js - Función a testear
export function sum(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
export function multiply(a, b) {
return a * b;
}
export function divide(a, b) {
if (b === 0) {
throw new Error("No se puede dividir por cero");
}
return a / b;
}
// sum.test.js - Archivo de test
import { sum, subtract, multiply, divide } from './sum';
describe('Operaciones matemáticas', () => {
// Test para función sum
test('suma 1 + 2 para igual a 3', () => {
expect(sum(1, 2)).toBe(3);
});
test('suma números negativos correctamente', () => {
expect(sum(-1, -2)).toBe(-3);
});
// Test para función subtract
test('resta 5 - 3 para igual a 2', () => {
expect(subtract(5, 3)).toBe(2);
});
// Test para función multiply
test('multiplica 3 * 4 para igual a 12', () => {
expect(multiply(3, 4)).toBe(12);
});
test('multiplicar por cero devuelve cero', () => {
expect(multiply(5, 0)).toBe(0);
});
// Test para función divide
test('divide 10 / 2 para igual a 5', () => {
expect(divide(10, 2)).toBe(5);
});
test('lanza error al dividir por cero', () => {
expect(() => divide(10, 0)).toThrow('No se puede dividir por cero');
});
});
// Ejecutar tests con: npm test
🎯 Anatomía del Test:
- describe(): Agrupa tests relacionados
- test() o it(): Caso de test individual
- expect(): Aserción sobre lo que debería suceder
- Matcher: toBe(), toEqual(), toThrow(), etc.
Testing de Componentes React
// Button.jsx - Componente a testear
export function Button({ onClick, disabled, children }) {
return (
);
}
// Button.test.jsx - Test del componente
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from './Button';
describe('Componente Button', () => {
test('renderiza con el texto correcto', () => {
render();
const button = screen.getByText('Click me');
expect(button).toBeInTheDocument();
});
test('llama onClick cuando se hace clic', () => {
const handleClick = jest.fn();
render();
const button = screen.getByText('Click me');
fireEvent.click(button);
expect(handleClick).toHaveBeenCalledTimes(1);
});
test('no llama onClick cuando está deshabilitado', () => {
const handleClick = jest.fn();
render(
);
const button = screen.getByText('Click me');
fireEvent.click(button);
expect(handleClick).not.toHaveBeenCalled();
});
test('tiene atributo disabled cuando prop disabled es true', () => {
render();
const button = screen.getByText('Click me');
expect(button).toBeDisabled();
});
});
Desarrollo Guiado por Tests (TDD)
El Ciclo TDD: Rojo-Verde-Refactorizar
Escribe un test que falle
Haz que pase
Mejora el código
💡 Beneficios de TDD:
- • Te obliga a pensar en los requisitos primero
- • Asegura que cada línea de código esté testeada
- • Conduce a mejor diseño de API
- • Previene sobre-ingeniería
Patrones Comunes de Testing
Arrange-Act-Assert (AAA)
test('usuario puede agregar item al carrito', () => {
// Arrange: Configurar datos de test
const cart = new ShoppingCart();
const item = { id: 1, name: 'Libro', price: 20 };
// Act: Realizar la acción
cart.addItem(item);
// Assert: Verificar el resultado
expect(cart.items).toHaveLength(1);
expect(cart.total).toBe(20);
});
Setup y Teardown
describe('Tests de base de datos', () => {
let db;
// Ejecutar antes de cada test
beforeEach(async () => {
db = await connectDatabase();
await db.clear();
});
// Ejecutar después de cada test
afterEach(async () => {
await db.disconnect();
});
test('puede guardar usuario', async () => {
const user = { name: 'Alice', email: 'alice@example.com' };
await db.users.save(user);
const savedUser = await db.users.findOne({ email: 'alice@example.com' });
expect(savedUser.name).toBe('Alice');
});
});
Tests Parametrizados
// Testear múltiples inputs eficientemente
test.each([
[1, 1, 2],
[1, 2, 3],
[2, 2, 4],
[-1, 1, 0],
[0, 0, 0],
])('sum(%i, %i) devuelve %i', (a, b, expected) => {
expect(sum(a, b)).toBe(expected);
});
⚠️ Errores Comunes en Testing
- • Testear detalles de implementación en lugar de comportamiento
- • Demasiados mocks que hacen los tests frágiles
- • No testear casos extremos (null, vacío, valores límite)
- • Tests que dependen unos de otros (deberían ser independientes)
- • Tests lentos que los desarrolladores omiten
- • Nombres de test poco claros que no describen qué se está testeando
💡 Puntos Clave
- ✓ Los tests son una inversión que paga dividendos con el tiempo
- ✓ Comienza con tests unitarios para lógica de negocio core
- ✓ Testea comportamiento, no implementación
- ✓ Escribe tests fáciles de entender y mantener
- ✓ Tests rápidos se ejecutan más a menudo
- ✓ TDD puede conducir a mejor diseño pero no es obligatorio
📚 Más Temas de Testing
Explora los 6 temas de testing para construir una comprensión completa del testing de software.
Ver Todos los Temas