Pruebas unitarias: Mocks

Los mocks son objetos falsos que simulan el comportamiento de un objeto real. Los objetos falsos son los que se usarán durante la ejecución de la prueba unitaria, lo que posibilitará que no necesitemos el objeto real y que no dependamos de él para poder probar correctamente y de manera completa el módulo.

El concepto de mock es sencillo; si tanto el objeto real como el objeto falso implementan la misma interfaz y el módulo que estamos probando trabaja contra interfaces en lugar de contra objetos, podremos hacer uso indistintamente de objeto falso o del objeto real sin que esto afecte al módulo, siempre y cuando ambos objetos tengan el mismo comportamiento.

El uso de Mocks debería ser recurso constante dentro de nuestras pruebas. Podría serlo, pero la generación de mocks puede resultar un poco pesada, lo que provoca que no siempre se empleen.

Al menos deberíamos usar mocks en los casos de dependencias externas con otros equipos de desarrollo o con módulos con lo que tengamos que integrarnos.

Aunque existen varios framework, como RhinoMock o NMock, que nos va a permitir generar estos objetos falsos, no debemos olvidar que cualquier objeto falso que creemos puede considerarse un mocks, aunque no se haga uso de un framework.

En el ejemplo que se describe a continuación supondremos que el módulo que estamos probando debe hacer uso de un Helper de envío de correos electrónicos que no es desarrollado por nuestro equipo de desarrollo. En este caso, es conveniente generar un mock para simular esta dependencia y poder probar nuestros desarrollos sin disponer del módulo de envío de correos.

Para simular esta dependencia implementaremos un mock haciendo uso de RhinoMock. La clase de envío de correos implementa la interfaz IMail.

public interface IMail
{
bool SendMail(string subject, string body);
bool SendMail(string subject, string body, string to, string from);
}

La clase que implementa la lógica de negocio, dónde se hace uso del módulo de envío de correos, debe implementar dos constructores; uno sin parámetros, que será el que se utilice en el mundo real y un constructor que recibe como parámetro un objeto de tipo IMail que será el que se emplee en las pruebas unitarias.

 

public class CustomerBL : ICustomerBL
{
private IMail mailHelper;

        public ERPServiceClient service;

        public CustomerBL()
        {
            service = new ERPServiceClient();
            this.mailHelper = new Mail();
        }

        public CustomerBL(IMail mail)
        {
            service = new ERPServiceClient();
            this.mailHelper = mail;
}

A continuación podéis ver el código fuente de la prueba unitaria que emplea un mock.

 

[TestClass()]
public class CustomerBLTest
{
        private MockRepository _mocks;

        private IMail mailMock;        

        private CustomerBL customerBL;

        private TestContext testContextInstance;

        [TestInitialize()]
        public void MyTestInitialize()
        {
            // Se crea un repositorio de mocks
            _mocks = new MockRepository();

            // Creamos un mock que implemente IMail.
            mailMock = _mocks.CreateMock<IMail>();

            // Inicializamos el uso de los objetos mocks    
            InitializeMockObject();
            _mocks.ReplayAll();

            // Creamos una instancia de objeto pasándole el objeto Mock
            customerBL = new CustomerBL(mailMock);

        }

        private void InitializeMockObject()
        {
            Expect.On(mailMock).Call(mailMock.SendMail("subject", "body", 
                null, "admin@admin.com")).Throw(new ArgumentException()); 
            Expect.On(mailMock).Call(mailMock.SendMail("subject", "body", 
                "mail@mail.com", "admin@admin.com")).Return(true);
        }
        
    

        [TestMethod()]
        public void AddUserTest()
        {
            CustomerEntity customer = new CustomerEntity();
            customer.Name = "MyName";
            customer.Address = "YYY";
            customer.Mail = "mail@mail.com";
            string actual;
            actual = customerBL.AddUser(customer);
            Assert.IsNotNull(actual);

            bool actualExists = customerBL.ExistCustomer(actual);
            Assert.AreEqual(true, actualExists);
        }

        [TestMethod()]
        [ExpectedException(typeof(ArgumentException))]
        public void AddUserTestException()
        {
            CustomerEntity customer = new CustomerEntity();
            customer.Name = "MyName";
            customer.Address = "YYY";
            customer.Mail = null;
            string actual;
            actual = customerBL.AddUser(customer);
        }

El método “MyTestInitialize” se utiliza para inicializar los objetos mocks que se emplearán en las pruebas que se incluyen dentro de la clase.

En este método crearemos el repositorio de mocks, inicializaremos los mocks que se utilizarán y crearemos el objeto sobre el que realizaremos las pruebas ( CustomerBL ), pasándolo como parámetro el objeto mock que queremos se utilice. Pasándole como parámetro el objeto mock conseguiremos que el objeto que se está probando use el objeto falso en lugar de objeto real.

En el método InitializeMockObject es dónde se describe el comportamiento que tendrá el objeto falso ante determinados parámetros de entrada.

En el código se describen dos comportamientos para el método “SendMail”; en uno de ellos se devuelve “true” mientras que en el otro se genera una excepción como resultado. Lógicamente, el comportamiento debe ser el mismo que tendría que el objeto real ante los mismos parámetros de entrada.

El último paso sería generar los métodos de prueba que cubren la funcionalidad de nuestra aplicación, haciendo uso de los objetos que hemos creado anteriormente.

Ya para terminar, en algún comentario, en un post anterior, se hacían referencia a los stubs y las diferencias entre stubs y mocks. Para los que estéis interesados en este tema os dejo un enlace dónde se habla de este tema, aunque lo realmente importante es que cogais la idea, que a veces, para hacer nuestras pruebas unitarias, necesitaremos objetos falsos que simulen ser los reales y así poder hacer pruebas de manera independiente al resto y de una manera más rápida.

Ibon Landa

bon Landa lleva más de 15 años dedicado al desarrollo de software. Durante este tiempo ha trabajado en diferentes empresas en las cuáles ha podido trabajar en diferentes entornos y tecnologías. Actualmente está focalizado principalmente en tareas de desarrollo, arquitectura, en las herramientas del ciclo de vida y en todo lo relacionado con la plataforma de Cloud Computing Microsoft Azure, área en el que ha sido reconocido como MVP. Participa de forma activa en la comunidad, escribiendo su blog, manteniendo un portal sobre Microsoft Azure y colaborando con Microsoft y grupos de usuarios en eventos de formación, talleres y giras de producto.

5 comentarios en “Pruebas unitarias: Mocks”

  1. Muy bueno Ibon, aunque personalmente opino que tener que escribir los interfaces y modificar el código del programa hace que utilizar Mocks a veces se convierta en un autentico coñazo…

    Saludos.

  2. Totalmente de acuerdo Juan.

    Por eso comentaba que al menos recomendaría usarlo en los casos de dependencias externas con otros equipos de desarrollo o con módulos con lo que tengamos que integrarnos.

    Suelen ser casos más «puntuales» y que sin los mocks difícilmente podríamos probarlo bien.

    Un saludo!

  3. No sé si conoceos TypeMock (http://www.typemock.com/index.php) es un framework para crear mokcs de manera muy sencilla por lo menos a mi me ha dado buen resultado haciendo pruebas para SharePoint, incluso sugerimos Gustavo y yo varias cosas al equipo de desarrollo.

    La verdad es que simplifica mucho el trabajo y permite hacer algunas virgerias que otros frameworks no dejan.

    Carlos.

Responder a csegura Cancelar respuesta

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