Mocks

Despues de meterme en el mundo de las pruebas unitarias, me llego el momento de introducirme en los mocks. La primera vez que lo oi, ni sabia lo que era y creo que es una de las herramientas mas interesantes para la realización de pruebas unitarias.

Espero que quede claro con este post lo que es una pequeña introducción a este mundo. Dar las gracias a Javi Larrea desde aqui.

Introducción

Llamamos “mock” a un objeto falso que simula el comportamiento de un objeto verdadero.

A priori podemos pensar que utilizar un objeto falso no tiene sentido así que la primera pregunta que nos surge es: “¿Cuándo necesitamos usar objetos falsos?”.

Bien, supongamos que estamos desarrollando un código que forma parte de una aplicación más amplia. Supongamos también que nuestro código necesita utilizar objetos que están siendo desarrollados por otra persona, o mejor, que se van a desarrollar en otro momento posterior. Cuando terminemos de codificar nuestro código deberemos de ejecutar las pruebas necesarias que permitan verificar que es correcto y que hace lo que tiene que hacer, pero ¿como vamos a poder probarlo si nuestro código necesita utilizar objetos que todavía no están disponibles?.

Es en este momento donde tiene sentido el uso de objetos “mock”. Utilizaremos estos objetos en los proyectos de pruebas de manera que podamos probar nuestro código simulando el comportamiento de objetos que todavía no están disponibles a través del uso de los mocks.

Un mock deberá implementar el mismo interfaz del objeto que queremos simular. En la clase que define la prueba deberemos definir qué métodos del objeto real queremos que simule el mock, indicando para cada uno de ellos cual es la respuesta esperada cuando reciba unos parámetros predeterminados. Esa respuesta debe ser la misma que esperamos que devuelva el objeto real cuando esté disponible.

Al ejecutar la prueba, cuando el código que queremos probar llame a un objeto que todavía no esté disponible y que hemos simulado con el mock, esa llamada será “interceptada” por el objeto “mock” que hayamos definido y este devolverá la respuesta que hayamos definido y que se deberá corresponder con la que esperamos que sea devuelta por el objeto real cuando esté construido.

La utilización de mocks nos permite independizar el desarrollo de unas partes de la solución de otras. Podemos desarrollar una parte y verificar que funciona correctamente con independencia del resto de partes con las que tenga relación. De esta manera el responsable de cada parte de código puede centrarse en probar que el código que está desarrollando funcione correctamente con independencia del estado de otras partes de la aplicación con las que tenga relación.

 

RhinoMock Framework

Existen varias herramientas que permiten trabajar mediante el uso de objetos mock. De entre ellas hemos tomado la decisión de utilizar el framework “Rhino Mocks” principalmente porque es más fácil de usar que el resto de herramientas de este tipo.

A continuación vamos a explicar que pasos debemos seguir para implementar y utilizar un objeto mock en nuestros proyectos .NET . Para ello vamos a basarnos en el siguiente ejemplo:

“Disponemos de una clase de negocio, PacienteCN, que realiza varias acciones y entre otras cosas llama a la capa de acceso a datos, PacienteAD, para recuperar las operaciones programadas para un paciente en una fecha determinada.

Esta capa de acceso a datos dispone de un método que recibe un número de historia y una fecha y devuelve una lista con las operaciones programadas para un paciente en esa fecha.

Tras terminar de desarrollar la clase de negocio queremos probar que esta funciona correctamente pero nos encontramos con que la capa de acceso a datos no está terminada todavía.

Para poder probar nuestra clase de negocio vamos a implementar un objeto mock que simule el funcionamiento de la capa de acceso a datos de manera que cuando lancemos el método que devuelve la lista de operaciones programadas para un paciente y una fecha sea el objeto mock quien intercepte esa petición y devuelva una respuesta predeterminada que es la que deberá devolver el objeto real cuando esté desarrollado y funcione correctamente.

De esta manera podemos probar como reacciona el código de la clase de negocio cuando recibe una respuesta de la capa de datos, sin necesidad de que esta esté implementada todavía.”

Instalación de librerías necesarias

Lo primero que tenemos que hacer es copiar en nuestro proyecto la librería “Rhino.Mocks.dll” que nos proveerá de los métodos necesarios para la utilización de los mocks. Deberemos colocarla en el siguiente directorio:

C:Mis Documentos Visual Studio 2005 Projects MiAplicacion References

 

Definición del Interfaz del objeto que queremos simular

El objeto mock que vamos a implementar deberá imitar el comportamiento del objeto real al que va a sustituir durante las pruebas. Para asegurarnos de ello el objeto mock y el objeto real deberán cumplir el mismo interfaz.

Partimos del hecho de que conocemos cual va a ser el comportamiento del objeto real aunque todavía no se encuentre disponible, es decir, tenemos una definición de dicho objeto. A partir de esa definición creamos un interfaz que utilizaremos más adelante para definir el objeto mock que simule nuestro objeto real.

En nuestro ejemplo vamos a simular la capa de acceso a datos que obtiene los datos de un paciente, y en concreto vamos a simular el método que devuelve la lista de intervenciones programadas para una historia y una fecha concretas. El interfaz quedará definido de la siguiente manera:

public interface IPacientesAD

{

   string ObtenerIntervenciones(int historia, DateTime fecha);

 }

Preparación de la clase cuyo código queremos probar

Debemos preparar la clase cuyo código queremos probar de manera que trabaje de la misma manera con un objeto real que con un objeto mock.

Para hacer esto se deberán realizar los siguientes pasos:

1. Crear un atributo privado que implemente el interfaz del objeto que vamos a querer simular

2. Crear dos constructores de dicha clase que instancien el objeto que queremos simular. El primero constructor no tendrá ningún parámetro y será utilizado en la ejecución real del código ya que será el que cree el objeto real. El segundo de ellos tendrá como parámetro un objeto y será utilizado en la ejecución de las pruebas de manera que el objeto que se pasa sea el mock que hallamos definido en la prueba y la clase trabaje en ese caso con el objeto mock y no con el real.

En nuestro ejemplo la clase que queremos probar es la clase de Negocio PacienteCN. El código de esta clase quedará de la siguiente manera:

 

 
 
public class PacienteCN
	{
		
// Atributo de la clase que implementa el interfaz del 
// objeto que queremos simular, en nuestro caso la clase de
// acceso a datos PacienteAD

private IPacienteAD _pacienteAD;


		// Constructor de la clase de negocio que implementa un 
// objeto de acceso a datos real.

		public PacienteCN()
		{
			_pacienteAD = new PacienteAD();
		}


		// Constructor de la clase de negocio que implementa un 
// objeto de acceso a datos que recibe como parámetro y
// que será utilizado desde la clase de pruebas donde se 
// le pasará un objeto mock.

		public PacienteCN(IPacienteAD pacienteAD)
		{
			_ pacienteAD = pacienteAD;
		}
		


// Metodo de la clase de negocio que llama al objeto de
// acceso a datos. El código es el mismo con independencia
// de que el objeto utilizado sea real o sea un mock.

public string ObtenerIntervenciones (int historia, DateTime fecha)
		{
		 return _pacienteAD.ObtenerIntervenciones(historia, fecha);
		}

	} 

En resumen, hemos preparado la clase para que dependiendo de cómo la instanciemos trabaje de la misma manera contra un objeto real que contra un objeto mock. Hemos independizado la clase de negocio de la clase de acceso a datos. Esta podrá obtener la información de una base de datos oracle, de una de sqlserver, de un AS400 o de cualquier otro proveedor, pero nuestra clase de negocio funcionará de la misma forma sea cual sea el proveedor utilizado en el acceso a los datos. Esto se consigue porque el objeto mock utilizado cumple el mismo interfaz que el objeto real de acceso a datos, es decir, externamente se comporta igual que él.

Preparación de la clase de pruebas que valida el funcionamiento de nuestro código

En la clase de pruebas que vamos a preparar para testear nuestro código es donde vamos a definir el objeto mock y su comportamiento.

Lo primero que tendremos que hacer es añadir en nuestro proyecto de pruebas la referencia a la librería Rhino.Mocks.dll.

A continuación deberemos incluir en nuestro fichero de pruebas, a través de la directiva using, la referencia para poder utilizar las funciones definidas en el espacio de nombres Rhino.Mocks:

 

using Rhino.Mocks;

El siguiente paso consiste en definir dentro de nuestra clase de pruebas una serie de variables que vamos necesitar utilizar:

 

public class PacientesCNTest
	{
		// Variable que necesita Rhino mocks para su ejecución.
// Se trata de un repositorio de objetos mock que vayamos
// utilizar
		private MockRepository _mocks;

		// Esta Variable contendrá el objeto mock que vamos a
// utilizar. Este objeto debe cumplir el interfaz de 
// la clase que debe simular, en nuestro ejemplo el
// interfaz de la clase de acceso a datos de pacientes.
		private IPacientesAD _pacientesADMock;


		// Esta variable contendrá el objeto de negocio que vamos
		// a crear y utilizar durante la ejecución de las pruebas.
		private PacientesCN _pacientesCN;

El siguiente paso consiste en definir dentro de la clase de pruebas un método que inicialice el uso de nuestros mocks. Este método será el encargado de crear el repositorio de mocks, los mocks que vayamos a utilizar, inicializar los mocks y crear el objeto que queramos probar pasándole por parámetro el objeto mock que queremos que utilice. Para definir este método vamos a utilizar el marcador [TestInitialize]:

 

[TestInitialize]
	public void PacienteCNTestInitialize()
	{
		// Creamos el repositorio de mocks

		_mocks = new MockRepository();


		// Creamos nuestro mock. En nuestro ejemplo debe cumplir el
		// Interfaz de la clase de acceso a datos de los pacientes.

	_pacientesADMock = _mocks.CreateMock(typeof(IPacientesAD))
as IPacientesAD;
		

		// Inicializamos el uso de los objetos mocks	

		InitializeMockObject();
		_mocks.ReplayAll();


		// Creamos el objeto cuyo código queremos probar, en 
// nuestro caso la clase de negocio de pacientes pasándole
		// como parámetro el objeto mock.

		_pacientesCN = new PacientesCN(_pacientesConADMock);

	}

A continuación debemos definir el método que inicializa nuestro mock, es decir, el método que define cual va a ser el comportamiento del mock. La inicialización consiste en definir cual debe ser la respuesta que debe dar el objeto mock cuando se lanza un método determinado con unos parámetros concretos. Dicha respuesta será la que esperamos que devuelva el objeto real cuando esté en funcionamiento.

Esto lo definimos en el método InitializeMockObject() al que hemos llamado al inicializar la prueba:

 

private void InitializeMockObject()
	{
	
// Cuando nuestro objeto mock _pacientesADMock reciba una 
// llamada a su método ObtenerIntervenciones() con los parámetros de
// entrada historia = 595962 y fecha = 31/01/2006 hacemos que
// devuelva la salida que esperamos. 
	Expect.On(_pacientesADMock).Call(_pacientesADMock.ObtenerIntervenciones 
(595962,new DateTime(2006,01,31))).Return( “ALEJOENDEIZAARRUZAQUIROFANO1OPERACIONAMIGDALAS”);

	}

Por último solo nos queda crear el método de prueba que ejecuta nuestro código, en nuestro caso la clase PacienteCN. Se trata de un método de prueba normal en el que lanzamos el método que queremos probar con unos parámetros concretos y comparamos si la salida es la esperada.

 

[TestMethod]
	public void ObtenerIntervencionesCNTest()
	{
		string actual;
		actual = _pacientesCN.ObtenerIntervenciones (595962, new DateTime(2006, 1, 31));

		Assert.AreEqual(actual, “ALEJOENDEIZAARRUZAQUIROFANO1OPERACIONAMIGDALAS”, 
"EL paciente esperado no es el correcto");
			
			
	}

Al ejecutar la prueba llamamos al método ObtenerIntervenciones de la clase de negocio que queremos probar. En este ámbito de pruebas la clase de negocio la hemos creado con un constructor al que le hemos pasado un objeto mock. Hemos definido que ese objeto mock simula el comportamiento para el método ObtenerIntervenciones cuando recibe unos parámetros determinados. Al ejecutar la prueba y llamar a ese método concreto de nuestro código será el objeto mock quien “intercepte” la llamada y responda en lugar del objeto real. Así podemos efectuar pruebas de nuestro código con independencia de la capa de acceso a datos. No nos importa que ésta llame a un AS400 o a una base de datos o a cualquier otro proveedor de datos ya que siempre deberá cumplir el interfaz que hemos creado a partir de su definición y que hemos hecho que cumpla también nuestro objeto mock.

Más información acerca de Rhino Mocks

Si deseas buscar más información referente al frameworks “Rhino Mocks” la puedes encontrar en la siguiente dirección: http://www.ayende.com/projects/rhino-mocks.aspx

2 comentarios sobre “Mocks”

Responder a anonymous Cancelar respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *