Skip to content

Resumen

  • Las clases testables inyectan las dependencias y los stubs (dobles de pruebas) controlan los valores que devuelven las funciones invocadas desde la dependencia.

Flujo de dependencias y ficheros

El código presentado sigue un enfoque de pruebas unitarias en Java, separando la lógica de negocio de las dependencias externas para facilitar la inyección de stubs durante los tests.

item

La dependencia fija es la instancia de IService y buscarDatos.elemPendientes(cli) es un método de la dependencia del cual necesitamos controlar su valor.

  • Se instancia la STUB, BuscadorSTUB, que dará el valor a la llamada a buscarDatos.elemPendientes() cuando se llame desde el test (sut.generarFactura(cli)).
BuscadorSTUB stub = new BuscadorSTUB(10); // Devolverá 10 cuando elemPendientes() sea llamado
  • Se instancia la Testable, GestorPedidosTestable.
GestorPedidosTestable sut = new GestorPedidosTestable();
  • Se inyecta el Stub en la SUT, mediante el setter setBuscador(stub) se sobre escribe la instancia de IService en la sut (IService buscarDatos = getBuscador();).
sut.setBuscador(stub); // Reemplaza la dependencia fija con el Stub
  • Se ejecuta el test, llamando al método de la sut.
Factura expectedResult = new Factura(...);
Factura realResult = assertDoesNotThrow(() -> sut.generarFactura(cli));

assertEquals(expectedResult, realResult);

1. Código de producción (SUT) - GestorPedidos.java

Ubicación: /src/main/java
Archivo: GestorPedidos.java
Propósito:
Este es el código original de la SUT (System Under Test). Tiene un método generarFactura() que usa una dependencia IService para obtener información sobre los elementos pendientes.

public class GestorPedidos {
    public IService getBuscador() {
        IService buscar = new Buscador();
        return buscar;
    }

    public Factura generarFactura(Cliente cli) throws FacturaException {
        Factura factura = new Factura();
        IService buscarDatos = getBuscador();

        int numElems = buscarDatos.elemPendientes(cli);
        if (numElems > 0) {
            // código para generar la factura
            factura = ...;
        } else {
            throw new FacturaException("No hay ...");
        }
        return factura;
    }
}

Problema:

  • getBuscador() crea directamente una instancia de Buscador(), lo que hace difícil reemplazar esta dependencia con un stub en las pruebas.
  • No es posible controlar el comportamiento de elemPendientes() en un entorno de pruebas.

Solución:
Refactorizamos GestorPedidos para permitir la inyección de dependencias, creando una subclase GestorPedidosTestable.


2. Implementación del Stub - BuscadorSTUB.java

Ubicación: /src/test/java
Archivo: BuscadorSTUB.java
Propósito:
Este es un stub (doble de prueba) que implementa IService. Nos permite controlar el valor devuelto por elemPendientes() en los tests.

public class BuscadorSTUB implements IService {
    int resultado;

    public BuscadorSTUB(int salida) {
        this.resultado = salida;
    }

    @Override
    public int elemPendientes(Cliente cli) {
        return resultado;
    }
}

Beneficio:

  • Nos permite controlar el número de elementos pendientes sin depender de la implementación real de Buscador.
  • Evita acceder a una base de datos o sistema externo en los tests.

3. Subclase de GestorPedidos para permitir inyección - GestorPedidosTestable.java

Ubicación: /src/test/java
Archivo: GestorPedidosTestable.java
Propósito:
Esta clase hereda de GestorPedidos y sobrescribe getBuscador() para permitir la inyección de un stub en lugar del buscador real.

public class GestorPedidosTestable extends GestorPedidos {
    IService busca;

    @Override
    public IService getBuscador() {
        return busca;
    }

    public void setBuscador(IService b) {
        this.busca = b;
    }
}

Beneficio:

  • Permite asignar un stub en tiempo de prueba con setBuscador(stub).
  • Separa la lógica de producción de la de prueba sin modificar GestorPedidos.

4. Prueba unitaria - GestorPedidosTest.java

Ubicación: /src/test/java
Archivo: GestorPedidosTest.java
Propósito:
Esta clase es el driver, el código que ejecuta la prueba unitaria.

public class GestorPedidosTest {
    @Test
    public void testGenerarFactura() {
        Cliente cli = new Cliente(...);
        BuscadorSTUB stub = new BuscadorSTUB(10);
        GestorPedidosTestable sut = new GestorPedidosTestable();

        sut.setBuscador(stub);
        Factura expectedResult = new Factura(...);
        Factura realResult = assertDoesNotThrow(() -> sut.generarFactura(cli));

        assertEquals(expectedResult, realResult);
    }
}

Beneficio:

  • Se crea un stub (BuscadorSTUB(10)) que devuelve siempre 10 elementos pendientes.
  • Se inyecta este stub en GestorPedidosTestable usando setBuscador(stub).
  • La prueba valida que generarFactura() funciona correctamente sin depender de Buscador.

Resumen del flujo de dependencias

Código de producción (src/main/java)

  • GestorPedidos.java: Implementa la funcionalidad principal (SUT).
  • Contiene una dependencia IService, que en producción es Buscador().

Código de pruebas (src/test/java)

  • BuscadorSTUB.java: Implementa un stub de IService para pruebas.
  • GestorPedidosTestable.java: Clase que permite inyectar el stub en lugar de Buscador().
  • GestorPedidosTest.java: Prueba unitaria que usa GestorPedidosTestable con un stub.

Conclusión

  • Se logra desacoplar la SUT de la implementación real de Buscador.
  • Se facilita la prueba de GestorPedidos con datos controlados.
  • Se aplican principios de inyección de dependencias y diseño orientado a pruebas.

Generalización

  1. Los STUBS

    • Son dobles de prueba que reemplazan una dependencia real en la SUT.
    • Se usan para controlar los valores de retorno de métodos en dependencias externas o fijas.
    • Evitan interacciones con sistemas reales como bases de datos, APIs externas, etc.
    • Permiten pruebas deterministas con valores predefinidos.
  2. Las clases Testable

    • Se crean cuando la SUT tiene dependencias fijas y no inyectables.
    • Su propósito es permitir la inyección de dependencias mediante métodos como setDependencia().
    • Suelen sobrescribir métodos que crean instancias de dependencias fijas, sustituyéndolas por versiones inyectables.
    • No modifican la SUT original, sino que la extienden para hacerla testeable sin cambiar su comportamiento en producción.

En conjunto, los stubs permiten controlar el comportamiento de dependencias y las clases testables hacen posible la inyección de esas dependencias en la SUT para realizar pruebas unitarias de manera controlada.