Primer vistazo al desarrollo de pruebas unitarias con Mockito

El desarrollo de pruebas unitarias es un tema sencillo y fácil de comprender, pero este se complica cuando el sistema al que se le deben desarrollar pruebas, debe interactuar o se integra con sistemas externo.  Entiéndase como sistemas externos la base de datos, un servicio web, etc.

Cuando desarrollamos una prueba unitaria el objetivo es probar una funcionalidad de forma aislada o como su nombre lo indica de forma unitaria, por lo general sera un procesamiento antes o después de tener una interacción con un sistema externo, por ejemplo antes de guardar datos o después de obtener información desde la base de datos.

Dicho esto no nos interesa comprobar que la base de datos guarda la informacion o si recupera dicha informacion ya que no estamos probando la interaccion entre los diferentes componentes de nuestro sistema, con este punto de partida exponemos el siguiente tema al cual le deberemos aplicar prueba unitarias.

Descripcion de la funcionalidad

Se necesita un servicio que exponga mediante un método el calculo de interés aplicado a un monto determinado, dicho método recibida un identificador(dpi) con el cual se podrá recuperar la información de este identificador y así obtener su monto y aplicar el interés que corresponda.

Para mantener la sencillez solamente tendremos las siguientes reglas:

  • Si el monto de la deuda es menor a 1,500 el interes a aplicar es 5%
  • En caso contrario el interés a aplicar es de 9%
  • Si el dpi no existe se lanza un excepción personalizada

Test

Las pruebas unitarias se realizar con JUnit y Mockito todas bajo un proyecto java con Gradle.  Este proyecto puede ser clonado desde el siguiente repositorio:

git clone https://gitlab.com/zacapalug/gradle-mockito.git

El proyecto consta de las siguientes clases:

Con la clase FakeDTO se recrea una clase entity, la cual contiene las propiedades de un objeto que debería guardarse o recuperarse desde una base de datos.  La clase FakeBean simula el comportamiento que tendría un Bean con acceso a la base de datos, en el cual tiene métodos para recuperar información de dicha base de datos, dicha clase también se simula su inyección dentro de la clase FakeService.  En FakeService tenemos la lógica requerida en la Descripcion de la funcionalidad y la inyección del Bean con acceso a la base de datos.

El metodo calcularImpuesto de la calse FakeService, cumple con la funcionalidad requerida:

public double calcularImpuesto(long dpi) {
    final FakeDTO person = fakeBean.findByDpi(dpi);

    double interesAplicar = INTERES_A;

    if (person.getDeuda() > DEUDA_MINIMA) {
        interesAplicar = INTERES_B;
    }

    return person.getDeuda() * interesAplicar;
}

Analizando vemos que podemos realizar las siguientes pruebas:

  • Una prueba cuando el monto es mayor a 1500
  • Una prueba cuando el monto es menor a 1500
  • Una prueba donde no se encuentra el ID enviado

Las cuales hemos desarrollado en la clase FakeServiceTest.java.  La cual iniciaremos a detallar a continuación.

package com.tio_gt;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.when;

import com.tio_gt.excepciones.IdNoEncontradoException;

import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

public class FakeServiceTest {

    @Mock
    private FakeBean fakeBean;

    @InjectMocks
    private FakeService fakeService;

    @Before
    public void init() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void calcularImpuestoA() throws Exception {
        when(fakeBean.findByDpi(dpiPersonaA)).thenReturn(personaA);

        final Double calculo = fakeService.calcularImpuesto(dpiPersonaA);

        assertEquals(resultadoA, calculo);
    }

    @Test
    public void calcularImpuestoB() throws Exception {
        when(fakeBean.findByDpi(dpiPersonaB)).thenReturn(personaB);

        final Double calculo = fakeService.calcularImpuesto(dpiPersonaB);

        assertEquals(resultadoB, calculo);
    }

    @Test(expected = IdNoEncontradoException.class)
    public void calcularImpuestoPersonaInexistente() throws Exception {
        when(fakeBean.findByDpi(dpiPersonaC)).thenThrow(new IdNoEncontradoException());

        fakeService.calcularImpuesto(dpiPersonaC);
    }

    //<editor-fold defaultstate="collapsed" desc="Datos estaticos">
    private final long dpiPersonaA = 11223344L;
    private final long dpiPersonaB = 11223346L;
    private final long dpiPersonaC = 11223348L;

    private final FakeDTO personaA = new FakeDTO(dpiPersonaA, "Luis", "Morales", "Zacapa", 10000.00);
    private final FakeDTO personaB = new FakeDTO(dpiPersonaB, "Luis", "Morales", "Zacapa", 18000.00);

    private final Double resultadoA = 500D;
    private final Double resultadoB = 1620D;
    //</editor-fold>

}

En esta clase podemos encontrar 4 secciones importantes.De la linea 14 a la linea 18 podemos encontrar la primera sección, en la cual definimos nuestros Mocks, en este caso puntual creamos Mocks(@Mock) para fakeBean y para fakeService, ademas le indicamos a mockito que inyecte los mocks que cumplan con las dependencias dentro de el(@InjectMocks).

La segunda sección la podemos identificar de la linea de la linea 20 a la linea 23, en este bloque le indicamos a Mockito que inicie, inyecte y ejecute las operaciones necesarias para ejecutar nuestros tests, esto puede hacerse de varias formas, hemos utilizado esta ya que es una de las formas mas comunes y es compatible con la ejecución de tests en paralelo.

La tercera sección la corresponden los Tests como tal, teniendo de la linea 25 a la linea 48, la definición de los métodos que contienen las comprobaciones a realizar. El primer y segundo método tiene gran similitud,  en la primera linea especificamos el resultado requerido para un método especifico con las condiciones especificas, en otras palabras se especifica que cuando se ejecute findByDpi con el parámetro dado por el valor de una variable X.  Si vemos ambas lineas(la primera del método calcularImpuestoA y calcularImpuestoB.

when(fakeBean.findByDpi(dpiPersonA)).thenReturn(personA);

when(fakeBean.findByDpi(dpiPersonB)).thenReturn(personB);

Ambas configuran para que hacer el llamado a la función findByDpi, ya sea enviando el valor de dpiPersonaA o dpiPersonaB se retorne personaA o personaB, según sea el caso, ya que dicha función se ejecuta dentro la función calcular impuesto.

Con esto ya hemos quito la necesidad de hacer una consulta a la base de datos o cualquier otro sistema, que debería ser el encargado de devolverme los datos de ese dpi.  Prácticamente podemos indicar que when y thenReturn funcionan literalmente.

Luego tenemos en ambas funciones la linea donde se ejecuta la función a testear, almacenamos el resultado de obtenido por dicha función y luego se ejecuta el assert especifico.

Luego tenemos el método calcularImpuestoPersonaInexistente, el cual varia en que, en lugar de esperar un resultado esperaremos una excepción personalizada.

when(fakeBean.findByDpi(dpiPersonaC)).thenThrow(new IdNoEncontradoException());

Como ultima sección tenemos el bloque encerrado en el folder Datos estaticos, el cual inicia en la linea 50 y finaliza en la linea 60, este bloque corresponde con la data estática que deseamos probar, esta data la cual definimos y por ende conocemos su estado, y el resultado de aplicar los proceso o funciones que deseamos comprobar.  En otras palabras, es la fuente de datos a la que se le aplicara el procesamiento y con la que se comprobara que los resultados obtenidos sean los esperados.

Dependencias Gradle

Antes de poder ejecutar nuestras pruebas, necesitamos agregar las dependencias correspondientes en el archivo build.gradle

dependencies {
    testCompile 'junit:junit:4.12'
    testCompile 'org.mockito:mockito-core:2.9.0'
}

Ejecutar pruebas

Podemos ejecutar las pruebas de dos formas, ya sea al realizar un build

gradle clean build

O ejecutándolas directamente con el comando

gradle test

Una vez terminada la ejecución de las pruebas se puede verificar el reportes, el cual es generado en build/reports/tests/test/, en ese directorio puedes abrir el fichero index.html y observar resultados como el siguiente

Ahora vamos a realizar un cambio en el sistema para simular un error un humano, vamos a simular que un desarrollador ha equivocado los valores de los impuestos, En la clase FakeService.java vamos a cambiar los valores de las lineas 22 y 27. Y volvemos a ejecutar los tests

gradle test

Y volvemos a ver el reporte y podemos comprobar los errores detectados en nuestra fase de pruebas unitarias.

Con esto tenemos un ejemplo básico en donde se implementa pruebas unitarias con JUnit y Mockito.  El proyecto aunque utiliza clases dummies simula un problema real.  Esperamos estar subiendo mas temas sobre testing, tanto Unit Testing como Integration Testing.

A %d blogueros les gusta esto: