Saltar al contenido principal
Página

Tema 2.4 - Desarrollando pruebas unitarias con Mockito

Estilo de pruebas unitarias (Clásico y Burlón)

Antes de iniciar con este apartado, es conveniente aclarar que los estilos de pruebas unitarias clásico y burlón son conocidos de diferentes formas, lo que en muchas ocasiones puede confundir al lector o investigador de estos temas. El estilo clásico es muy conocido en la literatura como escuela de Chicago o Detroit y el estilo burlón como escuela de Londres o Mockist.

Como vimos anteriormente, TDD es una práctica de programación que consiste en escribir primero las pruebas (generalmente unitarias), después escribir el código fuente que pase la prueba satisfactoriamente y, por último, refactorizar el código escrito; lo que permite una mayor seguridad, robustez y mantenibilidad en el código. En la actualidad existen dos escuelas de TDD con tendencias distintas a realizarla, la primera, es la escuela de Chicago (estilo clásico) enfocada en el estado y la segunda, escuela de Londres (estilo mockist) basada en comportamiento o interacción.  En el caso de JUnit, la librería vista anteriormente, está diseñada bajo el enfoque del estilo clásico, manejando el estado como medio de comprobación entre un valor esperado y uno generado.

El estilo clásico de TDD usa objetos reales si es posible y un doble cuando no se puede utilizar uno real; mientras que para el caso de mockist TDD, siempre se utilizará un doble para cualquier objeto.

Entre ambos existen unas diferencias.

Clásico TDD

Mockist TDD

Verifica el estado

Verifica el comportamiento

Su trabajo generalmente es con objetos reales

Se prefiere el trabajo con objetos falsos

Las pruebas tienden a ser más de grano grueso, acercándose a más pruebas de estilo de integración

Las pruebas tienden a ser muy finas; pueden faltar integraciones

Usa mocks para probar colaboraciones

Usa mocks todo el tiempo


Test Doubles

test double es simplemente un tipo de objeto que sustituye a uno real cuando se va a escribir pruebas, siendo mucho más cooperativo con lo que queremos.

Importante: Como ya sabemos, una prueba unitaria es en pocas palabras, la acción de verificar el correcto funcionamiento de un componente de nuestra aplicación, sin embargo, para que esto ocurra debemos aislar el componente de sus dependencias., y la manera de hacerlo, es a través de test double.

Tipos  de Doubles:


  • Dummy Object: (Objetos ficticios):  Son objetos que se pasan como argumento, pero en realidad nunca se usa. Son una alternativa para rellenar listas de parámetros.
  • Test Stub: Es un objeto que reemplaza un componente real del que depende el SUT (Sistema bajo prueba), para que la prueba pueda controlar las entradas indirectas de este. Los Stubs son configurados para que retorne valores que se ajusten a lo que la prueba unitaria quiere probar.
  •  Test spy: Es una versión más capaz de un Test Stub, permitiendo ser utilizado para verificar las salidas indirectas del SUT al darle a la prueba una forma de inspeccionarlos después de ejercitar el SUT.
  • Mock Object: es un Objeto que reemplaza un componente real del que depende el SUT para que la prueba pueda verificar sus salidas indirectas. Se usan para probar que se realizan correctamente llamadas a otros métodos.
  • Fake Object: es un objeto que reemplaza la funcionalidad del DOC real con una implementación alternativa de la misma funcionalidad. Utilizan datos que no vienen de la base de datos principal, sino que se tienen en cache u otra fuente más simple.  No son adecuados para ser desplegados en producción, por lo que nunca serán utilizados fuera del ámbito de un test. Se diferencian con el mock en que el test no tiene control sobre estos, y se diferencian de los stubs en que estos últimos son creados e inyectados en el sistema para test individuales para una necesidad básica, pero los fakes son más completos, añadidos en el sistema antes de ejecutar las pruebas, ya que posiblemente se utilicen en más de una, esto los hace un poco más independientes del test.

En próximos apartados, se ampliara más el concepto de dobles de prueba y se darán ejemplos de ellos para entender más su funcionamiento, pero primero, veremos un framework que nos ayudará a construir de manera sencilla nuestros test doubles, este framework es conocido como mockito.

 

Mockito

  • Hace referencia a la manera de probar el funcionamiento de una clase aisladamente, sin necesidad de requerir conexión a bases de datos o lectura del servidor de archivos, el objeto simulado retorna datos ficticios de acuerdo a una entrada ficticia que se le pasa, (en esta parte es donde surge mockito), como un aliado importante al momento de crear objetos simulados (Mock Objects) utilizando Java Reflection para su generación.
  • Mockito es un framework de mocking y de dobles de prueba de Java para realizar pruebas unitarias; su objetivo es proporcionar la capacidad de escribir con claridad una prueba unitaria legible utilizando su API simple. La diferencia frente a los demás frameworks relacionados, radica al dejar el patrón de esperar-ejecutar-verificar que es utilizado en ellos. En su caso, la forma en que lo hace es con la simulación de clases e interfaces (no final) que admite verificar y apilar basándose en comparadores flexibles. 
¿Cómo se debe usar? 
 Imagine que requiere hacer pruebas unitarias a una aplicación Java que permite administrar una biblioteca, donde entre otras funciones registra libros, usuarios y préstamos respectivos, dichas funcionalidades deben almacenar la información en su base de datos. En este caso, es conveniente usar mockito para simular el servicio de la base de datos y dar cumplimiento a lo que sería mocking en las pruebas unitarias.

El uso de mockito brinda varios beneficios, entre los que se destacan, la compatibilidad con excepciones, la compatibilidad con el valor retornado, además permite la comprobación del orden de las llamadas al método, la creación de simulaciones a través de anotaciones y asegura que ante cambios de nombres a los métodos de interfaz o de reordenamiento de parámetros, ( el código no se ve afectado )

Como métodos manejados por Mockito están:

Método

Descripción

Mock()

Este método se usa para crear objetos simulados e una clase o interfaz, permitiendo cinco métodos mock() con argumentos diferentes.

When()

Se usa cuando se pretende devolver valores específicos cuando se llaman ciertos métodos

Verify()

Su uso se da cuando se desea comprobar si se llama a alguno de los métodos especificados, Usado en la parte inferior del código

Spy()

Método conocido como espía que simula parcialmente un objeto, este, anula los métodos específicos del objeto real

Reset()

Permite restablecer los mocks y rara vez es usado en pruebas

verifyNoMoreInteractions()

Es un método opcional que no es necesario usar en cada prueba. Su función es comprobar que cualquiera de los mocks tiene interacciones no verificadas.

verifyZeroInteractions()

Verifica que no se ha producido ninguna interacción en los mocks especificados. Al igual que el anterior método detecta las invocaciones producidas antes del método prueba, por ejemplo, en el setup() o @Before

doThrow()

Se usa en el código auxiliar de un método void para producir una excepción

doCallRealMethod()

Se usa cuando se desea llamar la implementación real de un método

doAnswer()

Se utiliza cuando queremos stub en un método void con un tipo de respuesta genérico

doReturn()

Es usado en raras ocasiones en las que el método when() no se puede usar. Sin embargo, para stubbing se sugiere usar when () por ser más seguro para el tipo de argumento además de ser más legible

inOrder()

Usado para crear objetos que verifica las simulaciones en un orden específico

ignoreStubs()

Es útil con los métodos verifyNoMoreInteractions() o verification inOrder(). Se usa para ignorar los métodos de código auxiliar de ciertos simulacros para la verificación

times()

Útil para comprobar el número exacto de invocaciones de un método

never()

Comprueba que la interacción no se realizó

atLeastOnce()

Se usa para verificar que la invocación al método se haya realizado al menos una vez

atLeast()

Necesita como parámetro, un número que indique la cantidad de veces que se debe invocar el método

atMost()

Requiere de un parámetro que indique el número máximo de invocaciones al método se permiten.

calls()

Sólo se puede utilizar con el método de verificación inOrder(). Permite una verificación no expansiva en orden

only()

Comprueba que el método especificado era el único método invocado

validateMockitoUsage()

Valida explícitamente el estado del marco de trabajo. El ejecutor integrado (MockitoJUnitRunner) y la regla (MockitoRule) llama al método validateMockitoUsage() después de cada método de prueba


Al igual que JUnit, mockito también usa anotaciones, por lo cual es importante conocerlas.

  • @RunWith: Inicializa los simulacros anotados con @Mock. Disponible en el paquete org.mockito.junit.
  • @Mock: Es una de las anotaciones más usadas, sirve para crear e inyectar instancias simuladas sin tener que recurrir manualmente al llamado de Mockito.mock.
  • @Spy: Admite crear objetos parcialmente ficticios, para espiar una instancia existente sin hacerlo manualmente con Mockito.spy. Disponible en el paquete org.mockito.
  • @InjectMocks: Inyecta campos simulados en el objeto probado. Disponible en el paquete org.mockito.
  • @Captor: Se usa con el método verify() para obtener los valores pasados cuando se llama a un  método. Disponible en el paquete org.mockito.

Es de aclarar, que si se desea inyectar un simulacro o mock en un espía, se debe hacer manualmente a través de un constructor

 

Instalación

Como requisito principal para hacer uso del framework de mocking, debemos tener la versión 1.5 o superior del JDK (Java Developtmen Kit) en nuestra máquina. En nuestro caso haremos uso como ya lo hemos venido haciendo del IDE Eclipse y de JUnit4 como framework para pruebas unitarias de Java.

En el caso de usar maven debemos añadir la siguiente dependencia al pom.xml

    <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>3.2.4</version>
    </dependency>

En el caso de usar gradle añadimos la siguiente dependencia al gradle build file

repositories { jcenter() }
dependencies { testImplementation 'org.mockito:mockito-core: 2.7.22'}


Para habilitar las anotaciones existen varias formas.

  • La primera opción es anotar la prueba JUnit con un MockitoJUnitRunner.
  • import org.junit.runner.RunWith;
    import org.mockito.junit.MockitoJUnitRunner;
    
    @RunWith(MockitoJUnitRunner.class)
    public class AnnotationJUnitRunner{
    
    }
  • La segunda opción es a través de la programación invocando a MockitoAnnotations.initMocks().

  • import org.mockito.MockitoAnnotations;
    
    
    public class AnnotationsInit{
    	@Before
    	public void inicializaMocks() {
    	MockitoAnnotations.initMocks(this);
    	}
    }
    
  • La tercera y última opción es a través de MockitoJUnit.rule()

  • public class MockitoWithJUnitRuler{
    
         @Rule
         public MockitoRule iniRule = MockitoJUnit.rule();
    }
    


¿Qué es un doble de prueba?

Como ya se definió anteriormente, las pruebas unitarias se centran en verificar que cada pequeño trozo de código (clase, función, método, componente, etc.) al que llamaremos de ahora en adelante SUT (Subject under Test – Objeto Bajo Prueba) que constituye el sistema funciona como debe ser.

Hay ocasiones en las que es posible que el SUT dependa de otra clase conocida como DOC (Depended-on Component), y es en estos casos, en los que se hace necesaria la incorporación de un Test Double o sustituto, con el fin de aislar su dependencia de ellos; esto permite probar escenarios que podrían ser difíciles de probar con los DOCs reales o que no están disponibles porque están en fases de desarrollo, este comportamiento es característico de las pruebas unitarias solitarias.




Un test double es entonces, un tipo de objeto que sustituye a los DOC para la ejecución de la prueba unitaria que nos permite escribir la prueba de la forma en la que nosotros queremos. 

Para ilustrar mejor el concepto veamos el siguiente ejemplo:

Supongamos un sistema de gestión de pedidos, el cual funciona por la interacción de 4 clases:

El SUT es la clase Factura: Se quiere hacer una prueba unitaria de la clase Factura, que como se puede observar interactúa con las clases Cliente y LineaFactura, y además a través de LineaFactura interactúa con Producto, para realizar una prueba unitaria sólo a la pieza factura podemos utilizar dobles de prueba para sustituir las otras tres clases, consiguiendo de esta forma aislar la prueba de Factura de la prueba de Cliente, Producto y LineaFactura.

Esta es la misión de los dobles de prueba que suplantan a las piezas originales durante la ejecución de la prueba unitaria de Factura:



¿Por qué es importante hacer uso de dobles?

El uso de dobles evita que tengamos que añadir métodos exclusivos para realizar pruebas al objeto, algo que siempre debemos evitar, pues la prueba no debe verse afectada por temas ajenos al funcionamiento de la aplicación. Los test doubles permiten que se tenga mayor control en las especificaciones del comportamiento de un objeto, de la siguiente manera:

  • Enfocarnos en la unidad: El hacer uso de dobles permite mantener las pruebas enfocadas en un solo objeto a la vez.
  • Especificar la interacción con objetos que aún no existen: Si a la hora de realizar la prueba aún no ha sido implementado el objeto real del que depende el SUT, un doble puede permitir que continúe con la especificación simulando la existencia del objeto real.
  • Controlar el estado del colaborador: Los objetos pueden comportarse de manera distinta dependiendo de su estado. Al usar un doble, se puede asegurar que el colaborador responde de acuerdo a un estado particular que se puede determinar al gusto, sin dejar nada al azar.
  • Simular un estado difícil de reproducir: Los errores de conexión, pruebas relacionadas a la hora o fecha, son condiciones que pueden resultar difíciles de reproducir en una prueba. Con el uso de dobles, se puede simular una respuesta como si se hubiese presentado la condición necesaria.
  • Aumentar la velocidad de ejecución de las pruebas: Un colaborador puede ser costoso de instanciar, sea porque requiere de la presencia de otros colaboradores o condiciones específicas del sistema, o sea que realiza una operación que tiene grandes costos de procesamiento o tiempo. Usar un doble nos permite que la prueba de código se efectúe más rápidamente al simular la respuesta y sin tener tantas complicaciones.


Ventajas de los dobles de test


  • Determinismo: Un doble en realidad siempre se va a comportar como se quiere, a diferencia de los objetos reales que dependiendo de muchos factores van a comportarse de una forma u otra. Es decir si el objeto de prueba accede a base de datos (objeto real) se comportará de forma distinta dependiendo de los registros que hay en la base de datos.
  • Rapidez: No acceden a la base de datos o al sistema de ficheros y tampoco acceden a Internet (objetos reales), por lo que hacen que el test sea mucho más rápido, que es lo que se desea, ya que una de las principales cualidades de un buen test unitario es su rapidez, lo que permite que se pueda ejecutar a menudo.

 

Tipos de dobles:

Dummy:

Cuando hablamos del objeto Dummy, nos encontramos con que es un Test Double que en realidad no tiene implementación, pero que básicamente se utilizan para llenar argumentos de llamadas a métodos que son irrelevantes para la prueba.

Función:

Para trabajar con el objeto Dummy, se debe crear una instancia de algún objeto que pueda ser instanciado fácilmente y que no contenga dependencias, como paso siguiente pasamos la instancia como argumento del sujeto bajo prueba (SUT) y como en realidad no se utilizara dentro del sistema o sujeto bajo prueba no es necesario que exista alguna implementación sobre el objeto. Si por alguna razón se hace un llamado a alguno de los métodos del objeto simulado, la prueba debe arrojar un error ya que sería lo mismo que llamar un método inexistente.

  • Los objetos Dummy se pueden utilizar siempre que se necesite usar objetos como atributos de otros objetos o argumentos de métodos, y ni la prueba ni el sujeto bajo prueba se preocupan por estos.


Ejemplo:
Un ejemplo básico de dummy es la lista de un carrito de compras, la cual necesitamos saber si se llena correctamente, si bien los parámetros que se incluyen no nos importa; si necesitamos la instancia para saber que el llenado se este ejecutando, para ello crearemos una clase Producto sencilla.

package com.dummy.modelo;

public class Producto {
	String nombre;
	int cantidad;
	
	public Producto (String nombre, int cantidad) {
		this.nombre = nombre;
		this.cantidad =cantidad;
	}
}

Una vez creada la clase producto generamos la clase Test para ella, En el código, pueden observar que se crea una colección, donde se almacenarán los datos, en este caso, se crearan los productos (con datos que no interesan o afectan la prueba), los mismos que se añadirán en líneas posteriores, a la lista. Al final, tendremos una aserción para verificar que si hayan sido añadidos a la lista a través del tamaño de esta, en caso de que se haya presentado algún error, el resultado de la prueba no será exitoso, sin embargo, para este caso la ejecución no tendrá ningún fallo.

package com.dummy.test;

import static org.junit.jupiter.api.Assertions.*;

import java.util.ArrayList;
import java.util.List;

import org.junit.jupiter.api.Test;

import com.dummy.modelo.Producto;

class ProductoTest {

	@Test
	void testListaCarrito() {
		List<Producto> listaCarrito = new ArrayList<>();
		//creamos los productos
		Producto prodDummy1 = new Producto("Arroz", 0);
		Producto prodDummy2 = new Producto("Hola", 1);
		
		//añadimos a los productos a la lista
		listaCarrito.add(prodDummy1);
		listaCarrito.add(prodDummy2);
		
		//assert
		assertEquals(2, listaCarrito.size());
	}
}


Fake

Un objeto fake o también conocido como objeto falso, es un test double que simula la funcionalidad de un objeto real con una implementación alternativa que funciona, pero que por lo general toman algún atajo que las hace poco adecuadas para la producción, es decir falsea algún comportamiento que no puede ser explicado en los test.

¿Cómo funciona?

Al utilizar este tipo de test double, lo primero que se debe hacer es construir una implementación con la misma funcionalidad del objeto real del cual depende el objeto bajo prueba, como paso siguiente se debe indicar al SUT que va a ser uso de la implementación en lugar del objeto real, La implementación necesita proporcionar los servicios equivalentes al sujeto bajo prueba para que este permanezca inconsciente de que no está usando el objeto real.

¿Cuándo Usarlo?

Usamos un objeto Fake para simular o reemplazar la funcionalidad de un objeto real en una prueba que no se centra en la verificación de entradas y salidas indirectas del SUT, una de las razones más comunes para usar este tipo de test doubles es que por alguna razón el objeto real no se encuentra disponibles, es muy lento o no se puede utilizar en el ambiente de prueba debido a efectos secundarios que se pueden presentar.


Ejemplo:

Para nuestro ejemplo, crearemos un fake de una calculadora que nos permitirá calcular un promedio, una raíz cuadrada de acuerdo a datos proporcionados y finalmente, se podrá realizar el cálculo de ecuaciones de segundo grado a través de la aplicación de formula general o formula del estudiante, a continuación se presenta la formula general.

MATEMATICAS APLICACIONES: FORMULA GENERAL Formula del bachiller

Lo primero que debemos hacer, es crear las interfaces (abstractas) necesarias, en este caso ServicioDeDatos y ServicioCalculadora. La interface ServicioDeDatos, proporcionará una lista de números para calcular el promedio, un número único para calcular la raíz cuadrada y la lista de coeficientes para calcular los valores de la ecuación de segundo grado. 

//Interface que devuelve números de una fuente de datos
public interface ServicioDeDatos {
	int [] obtenerListaNumeros();
	int obtenerNumeroRaiz();
	double [] obtenerCoeficientes();
}


La interface ServicioCalculadora contendrá los métodos calcularPromedio(), calcularRaizCuadrada() y calcularFormulaGeneral() que serán invocadas de acuerdo a los datos proporcionados por la interface ServicioDeDatos

//Interface que contiene el llamado a los métodos para calcular diferentes funcionalidades de acuerdo a lo 
//mandado por la interface ServicioDeDatos
public interface ServicioCalculadora {
	double calcularPromedio();
	double calcularRaizCuadrada();
	double [] calcularFormulaGeneral();
}


Siguiendo ese orden, crearemos una clase llamada ServicioCalculadoraImplementacion, la cual implementará ServicioCalculadora que permitirá crear todos los métodos definidos en dicha interface

import interfaceSD.ServicioCalculadora;
import interfaceSD.ServicioDeDatos;

//Se hace la implementación de los servicios
public class ServicioCalculadoraImplementacion implements ServicioCalculadora {
	private ServicioDeDatos servicioDatos;
	
	public void setServicioDeDatos(ServicioDeDatos servicioDatos) {
		this.servicioDatos=servicioDatos;
		}
	
	
	public double calcularPromedio() {
		int [] numeros = servicioDatos.obtenerListaNumeros();
		double avg=0;
		for(int i : numeros) {
			avg+=i;
		}
		return (numeros.length>0) ? avg/numeros.length : 0;
	}
	
	public double calcularRaizCuadrada() {
		int numero = servicioDatos.obtenerNumeroRaiz();
		double raizCuadrada = Math.sqrt(numero);
		return raizCuadrada;
	}
	
	public double [] calcularFormulaGeneral() {
		double [] coeficientes = servicioDatos.obtenerCoeficientes();
		double coeficienteA= coeficientes[0];
		double coeficienteB= coeficientes[1];
		double coeficienteC= coeficientes[2];
		double x1, x2 = 0;
		
		double primerCalculo= Math.pow(coeficienteB, 2) - 4*coeficienteA*coeficienteC;
		if(primerCalculo<0) {
			double [] resultado = {0};
			return resultado;
		}else {
			x1= ((-(coeficienteB))+Math.sqrt(primerCalculo))/(2*coeficienteA);
			x2= ((-(coeficienteB))-Math.sqrt(primerCalculo))/(2*coeficienteA);
			
			double [] resultado = {x1, x2};
			return resultado;
		}
		
	}
	}


Con lo anterior tendremos un doble de prueba de tipo fake, el cual no necesitara ser configurado en relación a sus respuestas o expectativas.

Mock

Un objeto mock o simulado es un objeto que reemplaza un componente real el cual es una dependencia para el sujeto bajo prueba para que pueda verificar las salidas indirectas, el objeto Mock se encarga de verificar el comportamiento de los objetos durante la prueba, ya que es necesario mirar dentro del SUT, para determinar si el comportamiento esperado si se ha presentado.

¿Cómo funciona?

Lo primero que se debe hacer es definir el objeto Mock que es el encargado de sustituir la implementación del objeto real del que depende el sujeto bajo prueba. Luego durante la prueba se configura el objeto simulado con los valores con los que se le debe responder al sujeto bajo prueba y las llamadas al método completadas con los argumentos esperados del sujeto bajo prueba; cuando se llama el mock durante la ejecución del sujeto bajo prueba, este compara los argumentos reales recibidos con los esperados usando Aserciones de igualdad, en caso de que no coincidan los datos la prueba falla.

¿Cuándo usarlo?

Este tipo de test doubles es usado como punto de observación para verificar las salidas indirectas del SUT, también es usado cuando no se desea invocar el código de producción o cuando no resulta fácil verificar la ejecución de un código.

Ejemplo

Siguiendo el ejemplo de fake, crearemos una clase ServicioCalculadoraTest con JUnit4 para probar o testear nuestros métodos, recurriremos a la implementación de mock, su uso se justifica ya que esta clase tiene una dependencia con la interface ServicioDeDatos y para aislarla debemos falsearla esto sucede al llamar la anotación @mock, para ServicioDeDatos y realizar la inyección de dependencia; ServicioCalculadoraImplementacion el cual requiere el mock en su constructor, en su implementación si un método esperado no se llama, la prueba fallará, para el ejecicio, ese método sería obtenerListaNumeros(), 


verificado por  verify( servicioDatos ).obtenerListaNumeros ().

package test;

import static org.junit.Assert.*;
import static org.mockito.Mockito.verify;
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 clases.ServicioCalculadoraImplementacion;
import interfaceSD.ServicioDeDatos;

//Utilizará el Runner de Mockito para ejecutar nuestras pruebas
@RunWith(MockitoJUnitRunner.class)
public class ServicioCalculadoraTest {
	@InjectMocks
	private ServicioCalculadoraImplementacion servicioCalculadora;
	
	@Mock
	private ServicioDeDatos servicioDatos;
		
	@Test
	public void testCalcularPromedio() {
		when(servicioDatos.obtenerListaNumeros()).thenReturn(new int[] {1,2,3,4,5});
		
		assertEquals(3.0,servicioCalculadora.calcularPromedio(), 0.0);
		verify(servicioDatos).obtenerListaNumeros();
	}
	
}

Una vez se realiza lo anterior, podemos ejecutar nuestra clase Test y vemos que el mock se implementó correctamente.

Mock resultado


Stub

Este Test Double se utiliza para reemplazar aquel componente real que genera dependencia sobre el SUT para que la prueba pueda controlar las entradas indirectas de éste, es decir, se reemplaza a aquel objeto que responde con datos reales; por lo general el test Stub contiene una serie de datos predefinidos y se usan para responder a las llamadas que se hacen durante los test.

¿Cómo funciona?

Para hacer uso del test Stub, lo primero que se debe hacer es definir una implementación del objeto del que depende el sujeto bajo prueba o SUT, esta implementación debe ser configurada para que responda a las llamadas del sujeto bajo prueba, antes de preparar el SUT se debe definir el Stub para que se use en lugar de la implementación u objeto real, cuando el Stub es llamado por el SUT durante la ejecución de prueba, el test double devuelve los valores previamente definidos.     

¿Cuándo usarlo?

El test Stub se puede utilizar como un punto de control en donde se busca controlar el comportamiento del sujeto bajo prueba con varias entradas indirectas sin necesidad de verificar las salidas indirectas, otra forma en la que podemos usar el Stub es cuando este inyecta valores simulando la información de un software no disponible en el entorno de pruebas, que ha sido llamado por el sujeto de pruebas, lo que permite que la prueba avance y no se quedes estancada al no tener los datos de software.

Ejemplo

Siguiendo el mismo ejemplo, vamos a crear un Stub, que nos permite verificar el estado en lugar del comportamiento. Es de aclarar que diferente a un Mock, a un stub no le importa si se llama o no, su función es simplemente proporcionar valores, esto lo hace a través del método when de mockito y el assert. Para este stub  sería when (servicioDatos . obtenerNumeroRaiz()). thenReturn (4 ). 

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

import java.util.ArrayList;

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

import clases.ServicioCalculadoraImplementacion;
import interfaceSD.ServicioDeDatos;
//Utilizará el Runner de Mockito para ejecutar las pruebas
@RunWith(MockitoJUnitRunner.class)
public class ServicioCalculadoraTest {
	@InjectMocks
	private ServicioCalculadoraImplementacion servicioCalculadora;
	@Mock
	private ServicioDeDatos servicioDatos;
	
	@Test
	public void testCalcularRaizCuadrada() {
		when(servicioDatos.obtenerNumeroRaiz()).thenReturn(4);
		assertEquals(2.0,servicioCalculadora.calcularRaizCuadrada(), 0.0);
	}
	
	@Test
	//Calcula la formula para solucionar ecuaciones de segundo grado
	public void testCalcularFormulaGeneral() {
		//coeficiente a, b y c
		double [] esperado= {-0.033111854279919584, -10.06688814572008};
		
		when(servicioDatos.obtenerCoeficientes()).thenReturn(new double[] {3.0,30.3,1.0});
		assertArrayEquals(esperado, servicioCalculadora.calcularFormulaGeneral(), 0.0);
	}
}


Con lo anterior, aseguramos que no se tenga una implementación real de los métodos, simplemente se «preprogramará» que si ese método obtenerRaiz es llamado, se obtendrá un resultado de 4; al ejecutar la prueba tendremos para ambos métodos un resultado positivo como se observa a continuación.

Prueba del doble de prueba Stub


Spy

Permite espiar, se utilizan cuando desea asegurarse de que un sistema haya llamado a un método incluyendo todos los detalles, también puede registrar todo tipo de cosas, como contar el número de invocaciones o mantener un registro de los argumentos pasados cada vez. Los espías se usan exclusivamente para verificar todo tipo de comportamiento.

¿Cómo funciona?

Antes de preparar el sujeto bajo prueba, se debe definir el test Spy como sustituto del objeto o clase real usado por el SUT, como lo mencionamos anteriormente el Spy está diseñado para verificar todo tipo de comportamiento, es por esto que durante la fase de verificación de resultados, la prueba compara los valores reales pasados al test Spy por el sujeto bajo prueba con los valores esperados por la prueba.

¿Cuándo Usarlo?

El test Spy es usado como punto de observación o verificación para las salidas indirectas del sujeto bajo prueba, es posible que el test Spy en algunos casos necesite proporcionar valores al sujeto bajo prueba en respuesta a llamadas a métodos, tal y como lo hace el test Stub, pero una de sus principales funciones es capturar las salidas indirectas del sujeto bajo prueba y guardarlas para verificarlas en su momento.

Ejemplo

Continuando con el mismo ejercicio, para crear el doble de prueba spy, haremos uso de la anotación que nos ofrece mockito @Spy, para ServicioDeDatos. Añadiremos valores como se hizo con el Stub. Es de recordar que Spy, trabaja parcialmente con el objeto real.

package test;

import static org.junit.Assert.*;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;

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

import clases.ServicioCalculadoraImplementacion;
import interfaceSD.ServicioDeDatos;

//Utilizará el Runner de Mockito para ejecutar nuestras pruebas
@RunWith(MockitoJUnitRunner.class)
public class ServicioCalculadoraTest {
	@InjectMocks
	private ServicioCalculadoraImplementacion servicioCalculadora;
		
	@Spy
	private ServicioDeDatos spyServicioDatos;
	
	//Spy
	@Test
	//Calcula la formula para solucionar ecuaciones de segundo grado
	public void testCalcularFormulaGeneral() {
		//coeficiente a, b y c
		double [] esperado= {-0.033111854279919584, -10.06688814572008};
		
		//Para Stubear un spy, es recomendado utilizar el doReturn en vez de when
		doReturn(new double[] {3.0,30.3,1.0}).when(spyServicioDatos).obtenerCoeficientes();
		assertArrayEquals(esperado, servicioCalculadora.calcularFormulaGeneral(), 0.0);
		verify(spyServicioDatos).obtenerCoeficientes();
	}
}


El resultado será nuevamente exitoso

Resultado del spy


En un resumen más detallado de lo que acabamos de ver, tenemos que los objetos Dummy son realmente una alternativa a los patrones de valor, los test Stub se usan para verificar las entradas indirectas del SUT, el test Spy y el objeto Mock por lo general se usan para verificar salidas indirectas del SUT y realizar la correcta verificación y por último, los objetos Fake proporcionan una implementación alternativa, un poco más simple que el objeto real, todas estas variaciones que se tienen se clasifican según cómo o por qué usamos el Test Double.

 

Información a tener en cuenta

Es bueno tener en cuenta que ni los objetos Dummy ni Fake necesitan ser configurados, los objetos Dummy nunca deben ser utilizados por un receptor, por lo que no necesitan una implementación real, los objetos Fake por el contrario, necesitan una implementación real, pero se necesita que sea mucho más simple  que el objeto que reemplazan, por lo tanto ni la prueba ni el autómata de prueba necesitarán configurar respuestas o expectativas para estos dos objetos, simplemente se deja que el sujeto bajo prueba los use como si fueran el objeto real.


Última modificación: miércoles, 16 de marzo de 2022, 15:42