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 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.