Mocking: El comienzo

0 comentarios

Hace cuestión de un par de meses preparé un pequeño texto para dar una escueta charla alrededor del concepto de mock y técnicas básicas de mocking, la idea era que un equipo de programadores tuviera un primer contacto para la creación de tests unitarios donde el codigo a probar tiene una dependencia con clases externas.

Claramente hay montones de artículos, textos, papers, tutoriales, ... en internet que cuentan el mismo problema que estamos tratando en este artículo, sin embargo, puede ser que siempre le pueda servir de ayuda a aquellos programadores que van a enfrentarse con el mismo tipo de problema, y otro ejemplo más siempre ayuda a comprender mejor cómo aplicar la solución.

Cuando como programadores nos enfrentamos por primera vez a la creación de tests unitarios para el código que estamos programando, uno de los problemas complicados es el de comprender cómo crear tests unitarios para aquellas clases que tienen dependencias con clases externas, ya que una de las primeras ideas que se nos ocurren es, la de probar conjuntamente la o las clases dependientes, a la vez que la clase que inicialmente teníamos en mente. Esto contradice la definición de test unitario, donde se pretende crear tests para una única unidad, en este caso hablamos de una clase sin tener que probar el codigo de clases dependientes. Si la clase sobre la que estamos trabajando y creando los tests unitarios depende de otras clases, por definicion sabemos el comportamiento de las clases dependientes, sabemos de antemano, por la definicion o especificacion dedicha clase la salida para una determinada entrada en cada uno de sus metodos que la componen.


Tomando como ejemplo de lo comentado el esquema anterior, nuestra idea es crear tests unitarios para la clase Navigator, la cual hace uso internamente de NavigatorFlow y NavigatorListener. Dado que inicialmente no hemos creado interfaz para NavigatorFlow (INavigatorFlow), la clase Navigator hace uso directo de NavigatorFlow, lo que obliga que al ejecutar los tests que escribimos de Navigator se ejecute el codigo de NavigatorFlow, lo que ademas de agregar complejidad a los tests unitario, estamos probando dos unidades en lugar de una.

Para evitar esta complejidad y hacer nuestras vidas mas fáciles como programadores, es preciso tener en cuenta a la hora de diseñar la jerarquia de clases, que creemos un interfaz para la clase NavigatorFlow y esta interfaz es la usada dentro de Navigator, de esta manera los tests unitarios que vamos a escribir serán menos complejos a la vez que nuestro codigo sera mas fáil de mantener y sencillo de entender por otros programadores.

Gracias a la creación de la interfaz INavigatorFlow, podemos en la clase que contiene los tests unitarios, definir un objecto mock (NavigatorFlowMock) usando dicha interfaz e inyectando dicho objeto mock en nuestra clase Navigator antes de ejecutar los tests unitarios. El objecto mock creado implementará la misma interfaz que NavigatorFlow (INavigatorFlow), lo que nos ayudara a definir la respuesta a la clase Navigator.

namespace MockingFirstExampleTest
{
    ///

    /// Unit tests for Navigator class.
    ///

    [TestClass]
    public class NavigatorTest
    {
        ///

        /// This is the instance of the class under test.
        ///
        private Navigator navigator;

        ///

        /// Mock object of NavigatorFlow.
        ///
        private Mock flowMock;

        [TestMethod]
        public void TestNavigateSuccess()
        {
            var input = "MyInput";
            var expectedOutput = input + input;

            // define expectations of the mock object
            flowMock.Setup(flow => flow.DoNext(input)).Returns(input + input);

            // call to navigate method
            var output = navigator.Navigate(input);

            // check expectations
            Assert.IsTrue(output.Equals(expectedOutput));
        }

        /// 
        /// Set up the common stuff for the unit tests.
        ///
        [TestInitialize]
        public void SetUp()
        {
            // create the mocks objects
            flowMock = new Mock();

            // instantiates the class under test
            navigator = new Navigator(flowMock.Object);
        }
    }
}
 

El código anterior define una clase de tests unitarios para la clase Navigator, dicha clase contiene una instancia a Navigator y otra que es el objeto mock de la clase NavigatorFlow (flowMock). En el método SetUp, el cual se ejecuta antes de cada test unitario, lo que necesitamos es instanciar el objeto mock e inyectar éste mismo en el objeto de Navigator.

Asimismo, en los test unitarios (TestNavigateSuccess) se configura el comportamiento del mock, en este ejemplo se indica que el objeto mock va a recibir una llamada a DoNext con una entrada determinada, y le indicamos la salida (Returns) de dicha llamada. En este caso estamos haciendo uso de una librarías de mocking existente, hay montones de librerías que nos facilitan la vida para aplicar estas técnicas, además, se pueden definir varias salidas y distintos comportamientos en nuestros objetos mock, solo es necesario conocer la librería que vamos a usar. También es posible crear el objeto mock sin librerías pero nos obligará a escribir algo de cødigo en una clase aparte, sin embargo, hay circunstancias donde es mejor esta práctica que el uso de una librería.
Read On