{}const=>[]async()letfn</>var
Desarrollo

Pruebas unitarias: comprobamos el código automáticamente

Descubre cómo las pruebas unitarias ayudan a verificar automáticamente el código, evitar errores y ahorrar tiempo en la depuración. Ejemplos prácticos en JavaScript, principios de escritura de pruebas, patrón AAA y consejos para implementar pruebas en el desarrollo.

К

Kodik

Autor

7 min de lectura

Imagina que has desarrollado una función compleja para calcular descuentos en una tienda en línea. El código funciona perfectamente, pero un mes después, un compañero realiza pequeños cambios en otra parte de la aplicación y, de repente, tus cálculos comienzan a dar resultados incorrectos. ¿Cómo detectar este problema a tiempo? La respuesta es simple: pruebas unitarias.

¿Qué son las pruebas unitarias?

Las pruebas unitarias son comprobaciones automáticas de pequeñas secciones de código, generalmente funciones o métodos individuales. Funcionan como puntos de control que verifican constantemente que tu código se comporte exactamente como se esperaba.

La idea principal es simple: escribes un código que llama a tu función con datos de entrada conocidos y comprueba que el resultado coincide con el esperado. Si el resultado es correcto, la prueba se supera, si no, la prueba falla y notifica un error.

🔥 100.000+ estudiantes ya están con nosotros

¿Cansado de leer teoría?
¡Hora de programar!

Kodik — una app donde aprendes a programar con práctica. Mentor IA, lecciones interactivas, proyectos reales.

🤖 IA 24/7
🎓 Certificados
💰 Gratis
🚀 Empezar
Se unieron hoy

¿Por qué son necesarias las pruebas unitarias?

Los desarrolladores principiantes a menudo se preguntan: ¿por qué perder el tiempo escribiendo pruebas si puedes comprobar el código manualmente? Hay varias razones.

La primera razón es la confianza en la refactorización. Cuando tienes cobertura de pruebas, puedes mejorar el código con seguridad, sabiendo que si algo se rompe, las pruebas lo informarán de inmediato. Es como una red de seguridad para un acróbata.

La segunda razón es la documentación. Las pruebas bien escritas muestran cómo se debe usar una función, qué datos de entrada acepta y qué devuelve. Esta es una documentación viva que siempre está actualizada.

La tercera razón es el ahorro de tiempo a largo plazo. Sí, escribir pruebas lleva tiempo ahora, pero ahorrará horas de depuración en el futuro. Las pruebas automatizadas se ejecutan en segundos y comprueban todas las funciones, mientras que las pruebas manuales pueden tardar horas.

Un ejemplo sencillo

Veamos un ejemplo práctico en JavaScript. Supongamos que tenemos una función para validar direcciones de correo electrónico:

function isValidEmail(email) {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return emailRegex.test(email);
}

Ahora escribamos una prueba unitaria para ella usando el popular marco Jest:

describe('isValidEmail', () => {
  test('must return true for a valid email', () => {
    expect(isValidEmail('user@example.com')).toBe(true);
  });

  test('must return false for email without @', () => {
    expect(isValidEmail('userexample.com')).toBe(false);
  });

  test('must return false for email without domain', () => {
    expect(isValidEmail('user@')).toBe(false);
  });

  test('must return false for an empty string', () => {
    expect(isValidEmail('')).toBe(false);
  });
});

Cada prueba comprueba un escenario específico. Si alguien cambia la expresión regular y accidentalmente rompe la validación, las pruebas lo detectarán inmediatamente.

Principios para escribir buenas pruebas

Una buena prueba unitaria debe ser independiente. Esto significa que no debe depender de los resultados de otras pruebas o del orden en que se realicen. Cada prueba crea sus propios datos y comprueba solo una cosa específica.

Las pruebas deben ser rápidas. Si todas las pruebas tardan minutos en ejecutarse, los desarrolladores las ejecutarán con menos frecuencia. Las pruebas unitarias deben ejecutarse en milisegundos para que puedan ejecutarse después de cada cambio de código.

Las pruebas deben ser claras. Cuando la prueba falla, debes entender de inmediato qué es exactamente lo que se ha roto. Utiliza nombres de prueba descriptivos y mensajes de error claros.

Patrón AAA

Un enfoque popular para estructurar las pruebas es el patrón AAA: Arrange, Act, Assert (Preparación, Acción, Verificación).

test('must correctly calculate the 10% discount', () => {
  // Arrange: preparación de datos
  const price = 1000;
  const discount = 10;
  
  // Act — ejecución de la acción
  const result = calculateDiscount(price, discount);
  
  // Assert: comprobación del resultado
  expect(result).toBe(900);
});

Esta estructura hace que las pruebas sean legibles y comprensibles incluso para aquellos que ven el código por primera vez.

Pruebas de casos límite

Una de las tareas más importantes de las pruebas unitarias es verificar los casos límite. Estas son situaciones que están al borde de los valores permitidos: matrices vacías, valores cero, números máximos, caracteres especiales.

describe('calculateAge', () => {
  test('must return 0 for a newborn', () => {
    const today = new Date();
    expect(calculateAge(today)).toBe(0);
  });

  test('must process a leap year', () => {
    const birthDate = new Date('2000-02-29');
    expect(calculateAge(birthDate)).toBeGreaterThan(0);
  });

  test('should throw an error for a date in the future', () => {
    const futureDate = new Date('2030-01-01');
    expect(() => calculateAge(futureDate)).toThrow();
  });
});

Son los casos límite los que más a menudo se convierten en una fuente de errores en la producción.

Mockups y stubs

A menudo, la función depende de servicios externos: bases de datos, API, sistema de archivos. Para las pruebas unitarias, no queremos usar recursos externos reales, ya que es lento y poco fiable. En su lugar, se utilizan mocks y stubs.

// Función que estamos probando
async function getUserData(userId) {
  const response = await fetch(`/api/users/${userId}`);
  return response.json();
}

// Prueba con moco
test('must receive user data', async () => {
  // Creamos un mock para fetch
  global.fetch = jest.fn(() =>
    Promise.resolve({
      json: () => Promise.resolve({ id: 1, name: 'Alexey' })
    })
  );

  const userData = await getUserData(1);
  
  expect(userData.name).toBe('Alexey');
  expect(fetch).toHaveBeenCalledWith('/api/users/1');
});

Los mocks permiten aislar el código bajo prueba y verificar que la función interactúa correctamente con las dependencias externas.

Cobertura de código con pruebas

La cobertura de código muestra qué porcentaje de su código se ejecuta durante las pruebas. La mayoría de las herramientas de prueba pueden generar informes de cobertura.

Sin embargo, es importante entender que una cobertura del 100 % no garantiza que no haya errores. La cobertura indica que el código se ha ejecutado, pero no garantiza que se haya probado en todos los escenarios posibles. Esfuérzate por conseguir una cobertura razonable de las partes críticas del código.

Pruebas en diferentes idiomas

El concepto de pruebas unitarias es universal, pero las herramientas varían según el lenguaje de programación.

Los marcos de trabajo de pytest y unittest son populares en Python. Proporcionan una sintaxis sencilla para escribir pruebas y muchas herramientas de verificación integradas.

JavaScript y TypeScript utilizan Jest, Mocha, Jasmine. Jest es especialmente popular gracias a su soporte integrado para mocs y su API fácil de usar.

En las aplicaciones Vue.js, Vue Test Utils se utiliza a menudo junto con Jest, lo que permite probar los componentes de forma aislada, verificar su renderizado y la interacción con el usuario.

Java tradicionalmente usa JUnit, y PHP usa PHPUnit. Cada una de estas herramientas está adaptada a las características de su idioma, pero los principios básicos siguen siendo los mismos.

TDD: Desarrollo a través de pruebas

Algunos equipos practican TDD (Test-Driven Development), un enfoque en el que las pruebas se escriben antes de escribir el código principal. El proceso es el siguiente: primero se escribe una prueba que falla que describe el comportamiento deseado, luego se escribe el código mínimo para que la prueba pase y, finalmente, se refactoriza el código, manteniendo las pruebas en verde.

Este enfoque ayuda a pensar mejor la arquitectura y garantiza que todo el código esté cubierto por pruebas. Sin embargo, TDD requiere disciplina y no siempre es adecuado para proyectos experimentales.

Cuando no se necesitan pruebas unitarias

Honestamente, no todo el código necesita pruebas unitarias. Getters y setters simples, funciones de formato triviales, componentes de interfaz de usuario sin lógica: todo esto puede prescindir de pruebas o cubrirse con pruebas de integración.

Concéntrate en probar la lógica empresarial, algoritmos complejos, funciones con lógica condicional y código que es crítico para el funcionamiento de la aplicación. Aquí es donde las pruebas serán más útiles.

¿Quieres profundizar en las pruebas y otras prácticas de desarrollo profesional?

Anexo Kodik ofrece cursos interactivos en Python, JavaScript y muchas otras tecnologías. La formación se basa en tareas prácticas y ejemplos reales, desde lo más simple hasta lo más complejo.

Únete a nuestro Canal de Telegram, donde encontrarás una comunidad activa de desarrolladores dispuestos a ayudarte con cualquier pregunta, compartir experiencias y apoyarte en tu viaje de aprendizaje de la programación.

🎯Deja de postergar

¿Te gustó el artículo?
¡Hora de practicar!

En Kodik no solo lees — escribes código de inmediato. Teoría + práctica = habilidades reales.

Práctica instantánea
🧠IA explica código
🏆Certificado

Sin registro • Sin tarjeta