Por qué los tests son el corazón del pipeline
Un pipeline de CI/CD sin tests es como un cinturón de seguridad que nunca se abrocha: está ahí, pero no protege de nada. La automatización solo tiene sentido si sabe detenerse cuando algo está mal. Los tests son exactamente ese freno: el mecanismo que evita que un bug llegue a producción mientras duermes.
Los tres niveles que deberías conocer
No todos los tests cumplen la misma función. Combinar varios tipos te da una red de seguridad completa sin duplicar esfuerzo:
Tests unitarios
Prueban una función o clase de forma aislada, sin red, sin base de datos, sin dependencias externas. Son rapidísimos (miles en segundos) y por eso corren en cada commit. Un buen unit test responde: "dado este input, ¿esta función produce el output esperado?".
Tests de integración
Verifican que varios componentes colaboren bien. Aquí sí tocas la base de datos, envías una petición HTTP real, lees un archivo. Son más lentos que los unitarios y requieren configuración, pero detectan el tipo de bugs que solo aparecen cuando las piezas se encuentran.
Tests end-to-end (E2E)
Simulan un usuario real navegando tu aplicación. Abren un navegador sin cabeza, hacen clic, rellenan formularios y comprueban resultados. Son los más lentos y frágiles, pero son los únicos que prueban el sistema completo tal como lo vive el cliente. Herramientas como Playwright o Cypress se hicieron populares precisamente por esto.
La regla de oro: abortar si fallan
El principio fundamental es simple: si los tests fallan, el deploy no ocurre. Parece obvio, pero muchos equipos aprenden a ignorar tests rotos "que llevan así semanas". Ese es el momento exacto en que el pipeline se convierte en teatro: ejecuta, pero nadie confía en el resultado. Mantén cero tolerancia. Un test rojo se arregla antes de fusionar cualquier otra cosa.
PHPUnit: el estándar en PHP
PHPUnit lleva más de dos décadas siendo la herramienta de referencia. Un test unitario mínimo se ve así:
<?php
use PHPUnit\Framework\TestCase;
class CalculadoraTest extends TestCase
{
public function testSumaDosNumeros(): void
{
$calc = new Calculadora();
$this->assertEquals(7, $calc->sumar(3, 4));
}
public function testDivisionPorCeroLanzaExcepcion(): void
{
$this->expectException(InvalidArgumentException::class);
(new Calculadora())->dividir(10, 0);
}
}
En tu pipeline, basta con añadir vendor/bin/phpunit como un step. Si algún assert falla, el comando sale con código distinto de cero y el runner detiene el resto del workflow.
Jest para el lado Node
Si tu proyecto tiene componentes JavaScript o TypeScript, Jest es la opción por defecto en la mayoría de equipos. Detecta archivos *.test.js automáticamente, incluye mocks, snapshots y reporte de coverage sin configuración adicional. Integrarlo en el pipeline es tan simple como npm test.
Coverage: medir no es aprobar
El coverage o cobertura mide qué porcentaje de tu código se ejecuta al correr los tests. Es una métrica útil, pero no te dejes engañar: un 90% de coverage no significa que tengas un 90% de bugs atrapados. Significa que el 90% de las líneas se ejecutaron. La lógica que no verificaste con asserts sigue sin probarse.
Úsalo como termómetro, no como objetivo. Un umbral razonable (70-80%) ayuda a evitar regresiones, pero perseguir el 100% suele conducir a tests artificiales que no aportan valor real.
El hábito que cambia todo
Si vienes de un entorno sin tests, el primer mes duele. Escribir tests cuesta tiempo, parece frenar el desarrollo. Pero a las semanas empiezas a notar algo: los bugs dejan de repetirse, refactorizas sin miedo, tus despliegues se vuelven aburridos (y eso es exactamente lo que quieres). Un pipeline con tests convierte el deploy en un no-evento, no en una ceremonia de riesgo.