Pruebas Unitarias Parte 4: Code coverage con JaCoCo


En posts anteriores hemos hablado sobre como escribir nuestros test unitarios, el uso de mocks, spying y como escribir código “Testeable”, el día de hoy hablaremos sobre un concepto llamado Code coverage.

Code Coverage

El code coverage es una métrica utilizada en el desarrollo de software que determina el número de líneas de código que fueron ejecutadas durante nuestros test unitarios. Utilizando code coverage podemos determinar si nuestras aplicaciones se prueban de forma correcta y que porcentaje de escenarios no cuenta con pruebas automatizadas.

JaCoCo

JaCoCo es un plugin de maven que se utiliza para realizar reportes con base en la métrica de code coverage.

Configuración

Para configurar JaCoCo solo debemos incluir el siguiente plugin en nuestro código

<plugin>
	<groupId>org.jacoco</groupId>
	<artifactId>jacoco-maven-plugin</artifactId>
	<version>0.8.2</version>
	<executions>
		<execution>
			<goals>
				<goal>prepare-agent</goal>
			</goals>
		</execution>
		<execution>
			<id>report</id>
			<phase>prepare-package</phase>
			<goals>
				<goal>report</goal>
			</goals>
		</execution>
	</executions>
</plugin>

Utilizaremos JUnit para hacer nuestros tests de ejemplo así que incluiremos su dependencia a nuestro proyecto:

<dependency>
	<groupId>junit</groupId>
	<artifactId>junit</artifactId>
	<version>4.10</version>
	<scope>test</scope>
</dependency>

Con lo anterior tenemos listo nuestro ambiente para empezar a trabajar con JaCoCo y JUnit.

Paso 2 : Creando una clase de ejemplo 

Una vez que tenemos nuestro ambiente listo, crearemos una clase sobre la que ejecutaremos nuestras pruebas:

/**
 * @author raidentrance
 *
 */
public class Calculator {
	public int sumPositiveValues(int... numbers) {
		int sum = 0;
		for (int number : numbers) {
			if(number>0){
				sum += number;
			}
		}
		return sum;
	}
}

La clase Calculator tiene un método llamado sumPositiveValues(…) el cual dado un conjunto de números enteros sumará todos aquellos que sean positivos.

Paso 3 : Creando test unitarios

Una vez que tenemos nuestro método a probar el siguiente paso será crear su test unitario, veamos el ejemplo:


import static org.junit.Assert.assertTrue;

import org.junit.Test;

/**
 * @author raidentrance
 *
 */
public class CalculatorTest {
	private Calculator calculator = new Calculator();

	@Test
	public void testSumPositiveValues_withNegativeValues() {
		int result = calculator.sumPositiveValues(-10, -20, -30);
		assertTrue(result == 0);
	}
}

Como podemos ver el test unitario prueba el método sumPositiveValues() solo con valores negativos y al final valida que el resultado sea cero, si ejecutamos lo anterior veremos que el test unitario es exitoso.

Paso 4 : Ejecutando el goal de JaCoCo

Una vez que tenemos nuestro test listo queremos ver el code coverage de nuestro programa, para esto ejecutaremos el siguiente goal de maven:

mvn install
mvn jacoco:report

El goal anterior creará un directorio dentro de el directorio target llamado site dentro de este veremos otro folder llamado jacoco y dentro de el un archivo llamado index.html que al abrirlo se verá como se muestra a continuación:

El reporte anterior indica que el code coverage de nuestra aplicación es de 86%, todo nuestro código se encuentra dentro del paquete com.devs4j.jacoco , al darle click veremos la clase Calculator y su porcentaje de code coverage como se muestra en la siguiente imagen:

Al dar click en Calculator podremos ver el code coverage a nivel de método:

En este caso podemos ver que el método sumPositiveValues tiene un 84% de code coverage, al darle click podemos ver exactamente el código que no se ejecutó durante los tests:

En este caso podemos ver que como el test unitario solo probó el resultado utilizando números negativos la línea 15 nunca se ejecutó. Por esta razón no alcanzamos un code coverage de 100%.

Paso 5 : Agregando los tests faltantes

Como vimos una parte de nuestro código no se ejecutó durante los tests, eso significa que debemos agregar un test que utilice números positivos para asegurarnos que todo nuestro código se ejecutó durante la fase de tests unitarios, para esto modificaremos la clase CalculatorTest.java y agregaremos el siguiente test:

@Test
public void testSumPositiveValues_withPositiveValues() {
	int result = calculator.sumPositiveValues(10, 20, 30);
	assertTrue(result == 60);
}

Una vez hecho esto aseguraremos que con los dos tests unitarios que tenemos se ejecutará todo nuestro código.

Paso 6: Ejecutando de nuevo JaCoCo

Una vez incluido el nuevo test ejecutaremos nuevamente el plugin de JaCoCo para ver el nuevo code coverage:

Como podemos ver ahora el code coverage de nuestra aplicación es del 100%, esto no nos asegura que nuestro código es perfecto, pero si que todas las partes de el fueron probadas por tests unitarios.

Puedes encontrar el código completo en el siguiente enlace : https://github.com/raidentrance/jacoco-example.

Para estar al pendiente sobre nuestro contenido nuevo síguenos en nuestras redes sociales  https://www.facebook.com/devs4j/ y https://twitter.com/devs4j.

Autor: Alejandro Agapito Bautista

Twitter: @raidentrance

Contacto:raidentrance@gmail.com

Pruebas unitarias parte 3: Introducción a Spying con Mockito


En el post anterior hablamos sobre Pruebas unitarias parte 2: JUnit y Mockito primeros pasos, el siguiente paso será hablar sobre Spying.

¿Qué es un Spy y cómo es diferente a un Mock?

Un Mock reemplaza por completo la clase base y solo devuelve valores por defecto o valores definidos por nosotros, mientras que un Spy mantendrá los objetos originales y solo reemplazará algunos métodos, a esto se le conoce como partial mocking.

Veamos el siguiente ejemplo:

@Mock
private ArrayList mockArrayList;

@Test
public void mockTest() {
	mockArrayList.add("Object 1");
	mockArrayList.add("Object 2");
	mockArrayList.add("Object 3");
	assertEquals(0, mockArrayList.size());
}

En el ejemplo anterior se crea un mock de la clase ArrayList,  como se puede observar se ejecuta el método add 3 veces pero al realizar la validación assertEquals(0, mockArrayList.size()); esta devuelve verdadero y el test es exitoso.

La pregunta natural es ¿Por qué si se agregaron 3 objetos a la lista, al ejecutar el método mockArrayList.size() devuelve 0? La respuesta es simple, el objeto mock que creamos no contiene una implementación real de los métodos add() o size(), si no que solo su definición que será utilizada para pruebas.

En el siguiente ejemplo se mostrarán las ventajas de utilizar Spying:

@Spy
private ArrayList spyArrayList;

@Test
public void spyTest() {
	spyArrayList.add("Object 1");
	spyArrayList.add("Object 2");
	spyArrayList.add("Object 3");

	assertEquals(3, spyArrayList.size());

	when(spyArrayList.size()).thenReturn(20);
	assertEquals(20, spyArrayList.size());
}

Como se puede ver el código es muy similar al anterior, la diferencia es que estamos utilizando un Spy en lugar de un Mock, con esto crearemos un partial mock,este nos permitirá mantener el comportamiento original del objeto y solo sobre escribir algunos comportamientos, analicemos el código anterior:

  1. Se agrega el objeto Object 1 a la lista
  2. Se agrega el objeto Object 2 a la lista
  3. Se agrega el objeto Object 3 a la lista
  4. Se valida si el tamaño de la lista es 3, al estar utilizando un Spy en lugar de un Mock el resultado será verdadero así que el test sera exitoso
  5. Se sobre escribe el comportamiento del método size definiendo que la próxima vez que se ejecute el valor que devolverá será 20.
  6. Se valida si el tamaño de la lista es 20, como sobre escribimos el comportamiento del método size para que la próxima ejecución devuelva 20 el test será exitoso.

 

Como conclusión, se puede observar que es posible crear partial mocks utilizando Spying con mockito, el como usarlos en sus aplicaciones depende de ustedes.

Síguenos en nuestras redes sociales https://twitter.com/geeks_mx y https://www.facebook.com/geeksJavaMexico/ para recibir los siguientes posts que hablarán de temas más avanzados sobre Testing, JUnit y Mockito.

Autor: Alejandro Agapito Bautista

Twitter: @raidentrance

Contacto:raidentrance@gmail.com

Pruebas unitarias parte 2: JUnit y Mockito primeros pasos


Cuando hablamos de unit tests debemos hablar de “Mocking“, este es uno de los skills principales que debemos tener a la hora de hacer tests en nuestras aplicaciones.

Al momento de desarrollar aplicaciones las dividimos en capas, web, negocio y datos, entonces al escribir tests unitarios en una capa, no queremos preocuparnos por otra, así que la simulamos, a este proceso se le conoce como Mocking.

Test unitarios

Un test unitario debe probar un componente aislado de nuestra aplicación, los errores o efectos secundarios de otros componentes deben ser eliminados. La pregunta normal sería ¿Cómo aislar mi componente de sus dependencias para así probarlo? la respuesta es a través del uso de “test doubles“, estos se clasifican del siguiente modo:

  • Dummy objects : Este tipo de objetos son asignados al componente pero nunca se llaman y por lo general están vacíos.
  • Fake objects: Este tipo de objetos tienen implementaciones funcionales pero simplificadas, normalmente utilizan datos que no vienen de la base de datos principal, sino que se tienen en cache u otra fuente más simple.
  • Stub classes : Es una implementación parcial de una interfaz o clase con el propósito de utilizarla durante el test, normalmente solo cuentan con los métodos implementados que serán utilizados durante el test.
  • Mock objects: Es una implementación Dummy de una interfaz o clase en la cual se define la salida que tendrá la llamada de un método.

Estos “test doubles” aseguran que las pruebas se ejecuten solo sobre el componente que deseamos probar asegurando que no se tendrá ningún efecto secundario de ninguna otra clase.

Uso de Mocks

Es posible crear mocks manualmente, pero ya existen frameworks que pueden hacerlo por nosotros, estos permiten crear objetos mock en tiempo de ejecución y definir su comportamiento.

El ejemplo clásico de un objeto mock es un proveedor de datos, cuando se ejecuta la aplicación el componente se conectará a una base de datos y proveerá datos reales, pero cuando se ejecuta un test unitario lo que buscamos es aislarlo y para esto necesitamos un objeto mock que simulará la fuente de datos, esto asegurará que las condiciones de prueba sean siempre las mismas.

Un punto importante que se debe considerar es diseñar nuestros componentes para que sean probados fácilmente, si quieres saber más sobre esto te recomendamos el post Pruebas Unitarias Parte 1: ¿Cómo escribir código “Testeable”?.

Caso práctico con Mockito

Una vez que entendemos los conceptos básicos el siguiente paso es crear un ejemplo práctico, para hacerlo utilizaremos el framework de Mockito.

Configuración

Para incluir el soporte de Mockito en nuestra aplicación es necesario definir las siguientes dependencias:

Maven dependencies

Como se puede ver se incluye lo siguiente al proyecto:

  • Dependencia de Mockito : Brinda soporte para la creación de mocks
  • Dependencia de JUnit : Brinda soporte para la creación de tests unitarios
  • Maven compiler plugin : Define la versión de Java a utilizar

Ejemplo a realizar

En el ejemplo a realizar se crearán dos interfaces CalculatorService y DataService, con los siguientes propósitos:

  • DataService : Contendrá el método int[] getListOfNumbers(); que devolverá una lista de números de una fuente de datos.
  • CalculatorService : Contendrá el método double calculateAverage(); que calculará el promedio de la lista de números devuelta por el servicio DataService.

A demás se creará un test unitario que ayudará a probar el CalculatorService.

DataService.java

/**
 * @author raidentrance
 *
 */
public interface DataService {
	int[] getListOfNumbers();
}

CalculatorService.java

/**
 * @author raidentrance
 *
 */
public interface CalculatorService {
	double calculateAverage();
}

Una vez definidas las interfaces veamos la implementación de CalculatorService.
CalculatorServiceImpl.java


import com.raidentrance.services.CalculatorService;
import com.raidentrance.services.DataService;

/**
 * @author raidentrance
 *
 */
public class CalculatorServiceImpl implements CalculatorService {
	private DataService dataService;

	@Override
	public double calculateAverage() {
		int[] numbers = dataService.getListOfNumbers();
		double avg = 0;
		for (int i : numbers) {
			avg += i;
		}
		return (numbers.length > 0) ? avg / numbers.length : 0;
	}

	public void setDataService(DataService dataService) {
		this.dataService = dataService;
	}
}

En este caso no escribiremos implementación del DataService ya que haremos un mock del mismo para nuestras pruebas.

Probando nuestro componente

Una vez escritas las implementaciones de nuestros servicios el siguiente punto es probarlas, para esto crearemos un test unitario junto con mocks que permitan simular diferentes respuestas basadas en los tests, veamos el test de ejemplo:

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

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

import com.raidentrance.services.DataService;
import com.raidentrance.services.impl.CalculatorServiceImpl;

/**
 * @author raidentrance
 *
 */
@RunWith(MockitoJUnitRunner.class)
public class CalculatorServiceTest {
	@InjectMocks
	private CalculatorServiceImpl calculatorService;

	@Mock
	private DataService dataService;

	@Test
	public void testCalculateAvg_simpleInput() {
		when(dataService.getListOfNumbers()).thenReturn(new int[] { 1, 2, 3, 4, 5 });
		assertEquals(3.0, calculatorService.calculateAverage(), .01);
	}

	@Test
	public void testCalculateAvg_emptyInput() {
		when(dataService.getListOfNumbers()).thenReturn(new int[] {});
		assertEquals(0.0, calculatorService.calculateAverage(), .01);
	}

	@Test
	public void testCalculateAvg_singleInput() {
		when(dataService.getListOfNumbers()).thenReturn(new int[] { 1 });
		assertEquals(1.0, calculatorService.calculateAverage(), .01);
	}
}

Del código anterior podemos analizar lo siguiente:

  • @RunWith(MockitoJUnitRunner.class) : Se utiliza para definir que se utilizará el Runner de Mockito para ejecutar nuestras pruebas.
  • @Mock : Se utiliza para informar a Mockito que un objeto mock será inyectado en la referencia dataService, como se puede ver no es necesario escribir la clase implementación, Mockito lo hace por nosotros.

  • @InjectMocks : Se utiliza para informar a Mockito que los mocks serán inyectados en el servicio definido, es necesario que se cuente con métodos setters en los que se coloca.

  • when(dataService.getListOfNumbers()).thenReturn(new int[] { 1, 2, 3, 4, 5 }) : Define que cuando se ejecute el método getListOfNumbers se devolverá el resultado new int[] { 1, 2, 3, 4, 5 }.

Con lo anterior seremos capaces de probar nuestros componentes de forma aislada asegurando que las condiciones de nuestros tests siempre serán las mismas.

Síguenos en nuestras redes sociales https://twitter.com/geeks_mx y https://www.facebook.com/geeksJavaMexico/ para recibir los siguientes posts que hablarán de temas más avanzados sobre Testing, JUnit y Mockito.

Puedes encontrar el código completo en el siguiente enlace https://github.com/raidentrance/mockito-example.

Autor: Alejandro Agapito Bautista

Twitter: @raidentrance

Contacto:raidentrance@gmail.com

Pruebas Unitarias Parte 1: ¿Cómo escribir código “Testeable”?


Esta es la 1era parte de una serie de posts sobre tests unitarios. Hablaremos sobre que es una prueba unitaria, como escribir código que sea fácil de probar y como codificar estas pruebas usando JUnit y Mockito.

Una prueba unitaria es un tipo de prueba de software que tiene como objetivo verificar el funcionamiento correcto de un componente individual de una aplicación aislándolo del resto de su entorno. Para realmente probar un componente de manera individual, es necesario tener pleno control sobre su entorno (entradas y dependencias), ya que de no hacerlo, estaremos probando el componente más sus dependencias, lo que hará más difícil identificar la causa raíz de algún defecto ya que no sabremos si el problema se encuentra en el componente que estamos probando o en alguna de sus dependencias. Hacer esto puede parecer complicado debido a que la mayoría de los componentes que tenemos en nuestras aplicaciones dependen de uno o más componentes para su funcionamiento (por ejemplo, un servicio puede depender de un repositorio para obtener información de una base de datos). Sin embargo, si desarrollamos nuestros componentes con la idea de que deben ser “testeables” desde un inicio, esto será  muy sencillo.

Un componente puede considerarse “testeable” si:

  1. Sigue el Principio de Responsabilidad Única
  2. Está correctamente encapsulado y tiene una interfaz pública bien definida
  3. Se puede aislar fácilmente del resto del sistema
  4. Sus salidas son observables, es decir, se puede observar fácilmente los resultados de una ejecución
  5. Tiene un comportamiento determinístico (ejecutar un mismo método varias veces con las mismas entradas siempre dará el mismo resultado)

El siguiente código es un ejemplo de una clase NO “testeable”.

public class PayrollServiceImpl {

    private EmployeeRepositoryImpl employeeRepository;
    private NotificationServiceImpl notificationService;

    public PayrollServiceImpl() {
        this.employeeRepository = new EmployeeRepositoryImpl();
        this.notificationService = new NotificationServiceImpl();
    }

    public void increaseSalary(int employeeId, BigDecimal amount) {
        Employee employee = this.employeeRepository.findEmployee(employeeID);

        // only employees with more than 5 years of experience can get a raise
        if (employee.getYearsOfExperience() > 5) {
            employee.setSalary(employee.getSalary() + amount);
            this.employeeRepository.save(employee);

            // notify employee about the raise
            this.notificationService.notifySalaryIncreased(employee);
        }
    }

}

Esta clase tiene varios problemas. En primer lugar, no se puede aislar fácilmente del resto del sistema ya que su constructor crea las dependencias explícitamente. En segundo lugar, las dependencias son clases concretas (no interfaces), lo que aumenta el acomplamiento de nuestra clase y hace más difícil controlar el comportamiento de las dependencias. Finalmente, no hay forma de observar cuál fue el resultado de llamar al método increaseSalary.

Una más “testeable” del código se muestra a continuación:

public class PayrollServiceImpl {

	private EmployeeRepository employeeRepository;
	private NotificationService notificationService;

	private static final int NOT_ENOUGH_EXPERIENCE = 405;
	private static final int SALARY_INCREASED = 123;

	public PayrollServiceImpl(EmployeeRepository employeeRepository, NotificationService notificationService) {
		this.employeeRepository = employeeRepository;
		this.notificationService = notificationService;
	}

	public int increaseSalary(int employeeId, BigDecimal amount) {
		Employee employee = this.employeeRepository.findEmployee(employeeId);
		// only employees with more than 5 years of experience can get a raise
		if (employee.getYearsOfExperience() > 5) {
			employee.setSalary(employee.getSalary() + amount);
			this.employeeRepository.save(employee);

			// notify employee about the raise
			this.notificationService.notifySalaryIncreased(employee);

			return SALARY_INCREASED;
		} else {
			return NOT_ENOUGH_EXPERIENCE;
		}
	}

}

Los problemas de la versión anterior de la clase PayrollServiceImpl se corrigieron de la siguiente manera:

  1. El constructor de la clase ahora recibe sus dependencias como parámetros
  2. Las dependencias son ahora interfaces en lugar de implementaciones concretas de las clases. Lo que facilita la creación de “mocks”.
  3. El método increaseSalary ahora devuelve códigos de estado para reflejar cuál fue el resultado de la ejecución

Los cambios realizados en la clase PayrollServiceImpl tienen varios beneficios: ahora podemos controlar cuáles son las dependencias de esta clase en nuestras pruebas unitarias, lo que nos permitirá usar “mocks” para controlar el funcionamiento de estas dependencias durante la prueba. Por ejemplo, podríamos pasar un “mock” de la interfaz NotificationService al constructor de clase de PayrollServiceImpl al inicializar nuestra prueba unitaria para así verificar si el método notifySalaryIncreased fue llamado o no. De manera similar, podríamos hacer que el “mock” del EmployeeRepository arroje una excepción al ser invocado para simular el caso en el que se pasa un employeeId no existente como parámetro.

Como podemos observar, escribir código “testeable” requiere algo de disciplina y un esfuerzo extra a la hora del diseño y desarrollo de nuestros componentes. Sin embargo, como recompensa, tenemos un código más fácil de probar, limpio, fácil de mantener y débilmente acoplado. Escribiendo pruebas unitarias adecuadas para nuestros componentes podremos incrementar la confianza que tenemos en el correcto funcionamiento de nuestro sistema.

En el siguiente post explicaremos como escribir dichas pruebas unitarias usando JUnit y mockito.