15. Beneficios en pruebas unitarias y mantenimiento del código

Aplicar los principios SOLID no solo mejora la arquitectura, también impacta de manera directa en la calidad de las pruebas y en el mantenimiento diario del código. En este capítulo revisamos los beneficios concretos, métricas y ejemplos en Java.

15.1 Beneficios para las pruebas unitarias

  • Aislamiento sencillo: gracias a interfaces (ISP + DIP) los tests pueden reemplazar dependencias reales por dobles controlados.
  • Menos flujos ocultos: SRP reduce el número de escenarios que una prueba debe cubrir por clase.
  • Polimorfismo seguro: LSP asegura que un test diseñado para una interfaz sirve para todas sus implementaciones.
  • Refactorización con confianza: los tests fallan únicamente cuando se rompe un contrato real, no por efectos colaterales indeseados.

15.2 Ejemplo en Java: probar un servicio con dependencias

Consideremos el servicio AlertaService que depende de un Notificador, similar al visto en capítulos anteriores.

class AlertaService {
    private final Notificador notificador;

    AlertaService(Notificador notificador) {
        this.notificador = notificador;
    }

    void enviarAlerta(Alerta alerta) {
        notificador.notificar(alerta);
    }
}

La dependencia invertida permite crear un doble de prueba sin esfuerzos.

class NotificadorStub implements Notificador {
    private boolean notificado;

    public void notificar(Alerta alerta) {
        notificado = true;
    }

    boolean fueNotificado() {
        return notificado;
    }
}

@Test
void testEnviaAlerta() {
    NotificadorStub stub = new NotificadorStub();
    AlertaService service = new AlertaService(stub);

    service.enviarAlerta(new Alerta("CPU alta"));

    assertTrue(stub.fueNotificado());
}

Sin DIP, el servicio habría instanciado directamente una implementación concreta, obligando a usar recursos reales o a recurrir a frameworks adicionales.

15.3 Beneficios para el mantenimiento

  • Localización de cambios: SRP y OCP permiten modificar un comportamiento en una sola clase.
  • Nuevo comportamiento sin regressiones: agregar variantes usando interfaces evita tocar código probado.
  • Documentación viva: las interfaces expresan el contrato; los tests describen el uso.
  • Onboarding acelerado: el código modular sirve como guía para nuevas personas en el equipo.

15.4 Métricas que reflejan los beneficios

Métrica Sin SOLID Con SOLID
Tiempo medio para agregar una funcionalidad 5 días 2 días
Defectos regresivos por iteración 12 4
Cobertura de pruebas unitarias 30 % 80 %
Tiempo medio de incorporación (onboarding) 6 semanas 3 semanas

15.5 Relación entre principios y mantenimiento

  • SRP: el impacto de un cambio se limita a una clase, favoreciendo revisiones rápidas.
  • OCP: agrega comportamientos sin tocar código probado, reduciendo regresiones.
  • LSP: garantiza que las extensiones respeten el contrato original, evitando sorpresas en producción.
  • ISP: evita interfaces infladas que obligan a modificar múltiples clases cuando se alteran requisitos.
  • DIP: permite introducir nuevas tecnologías o proveedores sin reescribir la lógica de negocio.

15.6 Estrategias para potenciar los beneficios

  • Combinar SOLID con TDD: diseñar con pruebas en mente refuerza la separación de responsabilidades.
  • Adoptar pipelines de CI/CD: ejecutar tests y análisis estático en cada commit asegura que el diseño se mantenga.
  • Monitorizar métricas: analizar tendencias de defectos, cobertura y tiempos de ciclo para medir el impacto.
  • Revisión de código enfocada: discutir cómo cada cambio respeta o viola los principios.

15.7 Test de regresión como documentación

Al refactorizar con SOLID, los tests dejan de ser un simple guardián y pasan a ser documentación ejecutable. Un ejemplo es un test parametrizado que describe cómo debe comportarse un repositorio.

@ParameterizedTest
@MethodSource("repositorios")
void testGuardarYRecuperar(RepositorioOrdenes repositorio) {
    Orden orden = OrdenFactory.crear();
    repositorio.guardar(orden);
    assertTrue(repositorio.existe(orden.id()));
}

static Stream<RepositorioOrdenes> repositorios() {
    return Stream.of(new RepositorioOrdenesJdbc(dataSource()),
                     new RepositorioOrdenesEnMemoria());
}

El test garantiza que todas las implementaciones respeten el contrato, reforzando LSP y permitiendo agregar variantes sin temer regresiones.

15.8 Checklist para medir mejoras

  • ¿Las pruebas cubren casos críticos de negocio?
  • ¿Es posible introducir una nueva implementación sin editar la clase principal?
  • ¿Los tiempos de corrección de defectos disminuyeron tras aplicar SOLID?
  • ¿Las métricas de complejidad y cobertura mejoraron?
  • ¿El equipo percibe que el código es más fácil de entender y modificar?

Los principios SOLID potencian la calidad del software cuando se combinan con una estrategia de pruebas robusta. En el próximo tema exploraremos buenas prácticas complementarias que consolidan estos beneficios en el largo plazo.