Pruebas unitarias: La red de seguridad, también conocida como Cómo dormir por la noche
La cobertura de código del 100% es una métrica vanidosa. Cómo escribir pruebas unitarias significativas con Jest que detecten errores reales sin impedir la refactorización.
Por qué Maison Code habla de esto
En Maison Code Paris, actuamos como la conciencia arquitectónica de nuestros clientes. A menudo heredamos stacks “modernos” construidos sin una comprensión fundamental de la escala.
Discutimos este tema porque representa un punto de inflexión crítico en la madurez de la ingeniería. Implementarlo correctamente diferencia un MVP frágil de una plataforma resistente de nivel empresarial.
La crisis de confianza
Necesita refactorizar una función principal.
Tal vez sea la función calculateTax() en la caja.
Es un desastre. Tiene declaraciones “if” anidadas. Utiliza una sintaxis antigua. Quieres limpiarlo.
Pero estás aterrorizado.
Se pregunta: “Si toco esto e infrinjo una norma fiscal específica para los clientes mayoristas alemanes, ¿lo sabré?”
Si la respuesta es “No”, estás paralizado.
Entonces lo dejas. El “Código heredado” permanece. Se pudre. Se convierte en la “carpeta aterradora” que nadie toca.
Las pruebas unitarias son la cura para esta parálisis.
Congelan el comportamiento del código en el tiempo.
Garantizan: “Esta función, dada la entrada 2, DEBE devolver 4”.
Si ese contrato se cumple, puede reescribir la implementación interna como desee.
Las pruebas facilitan la Refactorización agresiva. Sin pruebas, la refactorización es simplemente “adivinar”.
Por qué Maison Code analiza las pruebas unitarias
En Maison Code, a menudo realizamos “Misiones de rescate” en plataformas heredadas. Encontramos bases de código que los desarrolladores tienen miedo de implementar. “No toques el módulo UserSync, se rompe si lo miras mal”. Este miedo paraliza el negocio. Las nuevas funciones tardan meses porque el 80% del tiempo se dedica a pruebas de regresión manual. Creemos que El código comprobable es código limpio. Implementamos sólidas suites de pruebas unitarias utilizando Jest/Vitest para devolverle al equipo su confianza. Cuando las pruebas están en verde, realiza la implementación. Así de simple. Convertimos el “Viernes de Despliegue” de una pesadilla a un no evento.
La herramienta: Jest / Vitest
- Broma: El titular. Mantenido por Meta. Estándar en Create React App/Next.js. Potente pero pesado.
- Vitest: El retador. Desarrollado por Vite. Es funcionalmente idéntico a Jest (API compatible) pero 10 veces más rápido porque utiliza módulos ES de forma nativa. Usaremos la sintaxis de Jest aquí, ya que se aplica a ambos. Los conceptos son universales.
La estrategia: ¿Qué probar?
Aquí es donde fallan los equipos. La trampa de la “cobertura del 100%”. Los gerentes anhelan una “cobertura de código del 100 %”. Lo pusieron en los OKR. Esto conduce a pruebas inútiles.
Mala prueba (prueba del marco):
botón constante = ({ etiqueta }) => <botón>{etiqueta}</botón>;
prueba('El botón renderiza', () => {
render(<Etiqueta del botón="Ir" />);
expect(screen.getByText('Go')).toBeInTheDocument();
});
Esta prueba tiene Valor bajo. Está probando React. Sabemos que React funciona. Tiene Alto Mantenimiento. Si cambiamos el nombre de “etiqueta” a “texto”, la prueba se interrumpe, aunque la aplicación funcione. Esto es un “falso negativo”.
Buena estrategia de prueba: Concéntrese en Lógica empresarial y Casos extremos.
1. Funciones puras (mayor retorno de la inversión)
Una función pura depende sólo de argumentos y devuelve un valor. Sin efectos secundarios. Sin llamadas API. Es un placer probarlos. Se ejecutan en milisegundos.
// lógica.ts
/**
* Calcula el descuento según el nivel de cliente.
* Reglas:
* - VIP obtiene 20% de descuento.
* - Precio negativo arroja error.
* - El empleado obtiene un 50% de descuento.
*/
función de exportación calcularDescuento(precio: número, tipo: 'VIP' | 'Regular' | 'Empleado'): número {
si (precio < 0) arroja un nuevo error ('El precio negativo es imposible');
if (tipo === 'Empleado') precio de retorno * 0,5;
if (tipo === 'VIP') precio de retorno * 0,8;
precio de devolución;
}
// lógica.prueba.ts
describir('calcularDescuento', () => {
test('da 20% de descuento a VIP', () => {
// Organizar
precio constante = 100;
tipo constante = 'VIP';
// actuar
resultado constante = calcularDescuento(precio, tipo);
// afirmar
esperar(resultado).toBe(80);
});
test('da 50% de descuento al Empleado', () => {
expect(calculateDiscount(100, 'Empleado')).toBe(50);
});
test('da 0% de descuento a Regular', () => {
expect(calculateDiscount(100, 'Regular')).toBe(100); // 100 * 1 = 100
});
test('arroja un precio negativo', () => {
// Nota: encapsulamos el código de llamada en una función para que Jest pueda detectar el error
expect(() => calcularDiscount(-10, 'Regular')).toThrow('Precio negativo');
});
});
Esto es robusto. Documenta las reglas comerciales mejor que los comentarios.
2. Burlarse (El Mal Necesario)
La mayor parte del código interactúa con el mundo (base de datos, API, almacenamiento local). No se puede ejecutar una llamada API real en una prueba unitaria. Es lento, inestable y requiere credenciales. Te burlas de la dependencia.
// perfil de usuario.ts
importar { fetchUserFromStripe } desde './api';
exportar función asíncrona getUserStatus (id: cadena) {
usuario const = espera fetchUserFromStripe(id); // Dependencia externa
si (usuario.delincuente) devuelve 'Bloqueado';
devolver 'Activo';
}
// perfil de usuario.test.ts
importar { fetchUserFromStripe } desde './api';
broma.mock('./api'); // Simulacro automático del módulo. Reemplaza funciones reales con jest.fn()
test('devoluciones bloqueado si el usuario es moroso', async () => {
// Configurar la respuesta simulada
(fetchUserFromStripe como jest.Mock).mockResolvedValue({ id: '1', delincuente: verdadero });
estado constante = esperar getUserStatus('1');
esperar(estado).toBe('Bloqueado');
// Verifica que la API se haya llamado correctamente para garantizar que pasamos el ID
expect(fetchUserFromStripe).toHaveBeenCalledWith('1');
});
Advertencia sobre las burlas: Las burlas mienten.
Si el fetchUserFromStripe real cambia su formato de retorno (por ejemplo, delinquent se convierte en isDelinquent), tu prueba seguirá pasando (porque te burlaste del formato antiguo), pero tu aplicación fallará.
Utilice TypeScript para mantener los simulacros sincronizados con los tipos reales.
Prueba de instantáneas: ¿característica o error?
Jest presentó “Instantáneas”.
esperar(componente).toMatchSnapshot().
Serializa el componente renderizado en un archivo de texto (__snapshots__/comp.test.js.snap).
Si cambia H1 a H2, la prueba falla.
El problema: Los desarrolladores tratan esto como una molestia.
Realizan la prueba. Falla. Escriben jest -u (Actualizar) sin mirar.
Las pruebas instantáneas son frágiles.
Mejores prácticas: utilice instantáneas para componentes muy pequeños y estables (iconos, tokens de diseño) donde cualquier cambio sea sospechoso. No los utilices para páginas complejas.
Prueba de código asincrónico (la trampa asincrónica/en espera)
Probar promesas es complicado. Error común: olvidar “await” o “params”.
// MALO: La prueba finaliza antes de que se resuelva la Promesa
prueba('prueba asíncrona incorrecta', () => {
fetchData().luego(datos => {
expect(data).toBe('mantequilla de maní');
});
});
Si fetchData falla, es posible que la prueba aún pase (falso positivo) o se agote el tiempo de espera.
Bien:
prueba('buena prueba asíncrona', async() => {
datos constantes = esperar fetchData();
expect(data).toBe('mantequilla de maní');
});
Para temporizadores (por ejemplo, setTimeout), utilice Temporizadores falsos:
jest.useFakeTimers().
jest.advanceTimersByTime(1000).
Esto le permite probar un retraso de 10 segundos en 1 milisegundo.
El debate sobre TDD (desarrollo basado en pruebas)
“Escribe la prueba primero”. Pros: Te obliga a diseñar la API antes de la implementación. Da como resultado un código muy limpio y desacoplado. Contras: Velocidad inicial más lenta. Es difícil cuando estás “explorando” (creando prototipos) y no conoces la estructura final. Veredicto: utilice TDD para lógica algorítmica compleja (por ejemplo, análisis de un CSV, cálculo de impuestos, manipulación de cadenas). No lo use para componentes de interfaz de usuario simples en los que esté iterando en píxeles.
Integración versus unidad: la forma del trofeo
Kent C. Dodds formalizó el “Trofeo de Pruebas”.
- Análisis estático (ESLint, TypeScript): detecta errores tipográficos. (Más rápido/barato).
- Pruebas unitarias: prueba funciones puras. (Rápido).
- Pruebas de integración: Pruebe la conexión entre componentes. (El punto óptimo).
- Pruebas E2E: Pruebe el navegador completo. (Lento/Caro).
Recomendación: Escriba principalmente Pruebas de integración.
Pruebe LoginForm + SubmitButton + ApiMock.
No pruebe SubmitButton de forma aislada.
Pruebe que “Al hacer clic en Enviar se llama a la API de inicio de sesión”.
Preguntas frecuentes
P: ¿Cómo pruebo las funciones privadas? R: No lo hagas Pruebe la API pública. La función privada es un detalle de implementación. Si prueba funciones privadas, se encierra en esa implementación. Pierdes la capacidad de refactorizar.
P: ¿Integración CI/CD? R: Las pruebas deben ejecutarse en cada solicitud de extracción. Si “npm test” falla, el botón Fusionar debe estar deshabilitado. Esto mantiene la política de “Maestro Verde”.
Conclusión
Las pruebas son una inversión. Pagas por adelantado (Tiempo). Obtienes dividendos para siempre (estabilidad, velocidad de refactorización, documentación). Una base de código sin pruebas es una “base de código heredada” desde el primer día. Una base de código con pruebas es un “activo”. Se aprecia en valor.
¿Implementación paralizada?
Si su equipo está paralizado por el miedo a romper el código heredado, Maison Code es la solución. Realizamos “Incursiones de refactorización” donde auditamos su código base, agregamos cobertura de prueba y limpiamos la deuda tecnológica que lo está frenando. Capacitamos a su equipo sobre cómo escribir pruebas que no apestan.