TechLead

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

🔴 Rojo

Escribe un test que falle

🟢 Verde

Haz que pase

🔵 Refactorizar

Mejora el código

↻ Repite el ciclo

💡 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