Testing
Testing no consiste en alcanzar un número mágico de cobertura. Consiste en ganar confianza para cambiar código sin romper comportamiento importante.
Un buen test protege una decisión relevante del sistema.
Qué testear
- Reglas de negocio.
- Casos de uso.
- Bugs que ya ocurrieron.
- Transformaciones de datos con casos límite.
- Integraciones con riesgo real.
- Flujos críticos de usuario.
Qué no suele merecer la pena
- Getters, setters y código trivial.
- Detalles internos que cambian con un refactor.
- Implementación exacta de librerías externas.
- Tests que solo comprueban que un mock fue llamado sin verificar comportamiento útil.
Tipos de test
Unitarios
Prueban una unidad pequeña sin depender de red, base de datos, filesystem ni framework.
Útiles para dominio, funciones puras, validadores, value objects y casos de uso con dependencias fake.
Integración
Prueban que varias piezas funcionan juntas.
Útiles para repositorios, queries, adapters, APIs internas, serialización, autenticación y permisos.
End to End
Prueban un flujo desde la perspectiva del usuario o consumidor externo.
Útiles para caminos críticos: login, checkout, formularios importantes, pagos, publicación, sincronizaciones.
No deberían cubrir todas las combinaciones. Son más lentos y más frágiles.
F.I.R.S.T.
Un buen test debería ser:
- Fast: rápido para poder ejecutarlo a menudo.
- Independent: no depende de otros tests ni de su orden.
- Repeatable: da el mismo resultado en cualquier entorno equivalente.
- Self-validating: pasa o falla sin interpretación manual.
- Timely: se escribe cerca del código que protege.
Estructura Arrange Act Assert
it("returns active users only", () => {
const users = [
{ id: "1", active: true },
{ id: "2", active: false },
];
const result = getActiveUsers(users);
expect(result).toEqual([{ id: "1", active: true }]);
});Nombres de test
El nombre debería explicar el comportamiento esperado:
it("rejects checkout when the cart is empty", () => {})
it("applies the renewal discount to active subscribers", () => {})
it("keeps the original image when upload optimization fails", () => {})Evita nombres que solo describen implementación:
it("calls validate", () => {})
it("returns true", () => {})Mocks
Usa mocks para aislar límites externos:
- Email.
- Pagos.
- APIs externas.
- Reloj.
- Random.
- Base de datos, cuando el test no pretende probar persistencia.
Evita mockear el código que realmente quieres diseñar. Si todo son mocks, el test puede pasar aunque la aplicación real esté rota.
TDD
TDD es útil cuando el comportamiento está claro y quieres diseñar desde el uso:
- Escribe un test que falla.
- Escribe el mínimo código para pasarlo.
- Refactoriza manteniendo el test en verde.
No es obligatorio hacerlo siempre. Lo importante es que el test exista cuando el comportamiento empieza a ser importante.