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.