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

Anuncios

1 comentario »

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

w

Conectando a %s