Tests unitarios, Pex y Code Contracts

Generalmente, los tests unitarios se deberían hacer antes de picar la primera línea de código. TDD va de eso: escribes un test que describe y documenta una funcionalidad del código y entonces se escribe el código necesario para que el test pase.

Lo mismo es aplicable a la corrección de un bug. Empiezas creando un test que reproduce el bug y después trasteas el código hasta que el test pasa. Con esto no sólo corriges el bug, sino que también lo dejas documentado y te aseguras de que si volviera a aparecer el error el test que acabas de hacer se chivaría. Es un EPIC WIN😉

Empecemos entonces creando un método de test que describe un método que elimina de una cadena una terminación que también le pasamos como argumento:

        [TestMethod]
        public void MyTestMethod()
        {
            var lResult = MyClass.TrimAfter("programador", "dor");
            Assert.AreEqual("programa", lResult);
        }

Si clicamos con el botón derecho sobre TrimAfter nos da la opción de generar un stub del método. Lo hacemos, y lo rellenamos con una implementación que pasa el test que acabamos de escribir:

        public static string TrimAfter(string value, string suffix)
        {
            int index = value.LastIndexOf(suffix);
            return value.Substring(0, index);
        }

Hasta este punto, hemos hecho un poquito de gimnasia con TDD.

Pex

Pex (otro día hablaré de Moles, su mellizo) es una herramienta de Microsoft para .NET que permite hacer de forma automatizada justo el proceso inverso. Analiza el código ya escrito intentando detectar los casos frontera para generar tests, buscando la máxima cobertura de código (code coverage).
Una vez instalado Pex (se puede descargar desde la propia página), clicamos con el botón derecho sobre el método TrimAfter que hemos escrito antes y seleccionamos Run Pex en el menú contextual. Si es la primera vez que lo ejecutamos Pex, preguntará qué framework de test utilizar. Tras un momento, obtendremos los resultados:

Examinando la lista de resultados podemos imaginar cómo funciona Pex. Se generan valores extremos para los parámetros de entrada (cada uno una columna, value y suffix) y se van combinando a ver qué nos responde en cada caso. En realidad, es algo más complejo, puesto que analiza el flujo de programa, los ifs, realiza varias pasadas, etc.
Vemos que de 6 combinaciones posibles peta en 5 de ellas (no está mal eh? :P).

Seleccionando la fila podemos ver en el panel de la derecha los detalles de la excepción. Y si clicamos con el botón derecho en la entrada, tenemos la opción de generar el test unitario correspondiente a la fila seleccionada:

        [TestMethod]
        [PexGeneratedBy(typeof(ProgramTest))]
        [PexRaisedException(typeof(NullReferenceException))]
        public void TrimAfterThrowsNullReferenceException335()
        {
            string s;
            s = this.TrimAfter((string)null, (string)null);
        }

Code Contracts

Code Contracts, por su parte, nos permite indicar pre-condiciones y post-condiciones en los métodos. Por ejemplo, en el método anterior una pre-condición es que el valor de las cadenas de entrada no puede ser nulo. Comprobar eso nos obliga a comprobar el valor que recibimos:

        public static string TrimAfter(string value, string suffix)
        {
            if (value == (string)null)
                throw new ArgumentNullException("value");
            if (suffix == (string)null)
                throw new ArgumentNullException("suffix");
            int index = value.LastIndexOf(suffix);
            return value.Substring(0, index);
        }

¡Bien! Ahora el código es más robusto y supera más tests. Pero aún hay un problema: El código que llama a nuestro método no puede saber de la existencia de esta pre-condición, puesto que la signatura del método sólo especifica el tipo de los argumentos (dos strings).

Aquí es donde entra Code Contracts. El mismo método lo podemos escribir así:

        public static string TrimAfter(string value, string suffix)
        {
            Contract.Requires(value != null);
            Contract.Requires(suffix != null);
            int index = value.LastIndexOf(suffix);
            return value.Substring(0, index);
        }

Bonito ¿eh? Pero no suficiente. Lo realmente interesante sería que a partir de estas anotaciones se generaran comprobaciones en tiempo de ejecución.
Por defecto, las llamadas a Contract se eliminan al compilar (por lo que no afectan al código compilado), pero podemos activar una opción en el proyecto para que se generen las comprobaciones:

Una vez lo activamos, al volver a ejecutar Pex vemos cómo lo que se genera es una ContractException:

Nos siguen quedando tres excepciones sin controlar. Podemos hacer que Pex nos genere las pre-condiciones por nosotros, mediante un click en el menú contextual. Esta acción agregará una nueva pre-condición a nuestro código. Para acabar, también agregaremos una post-condición:

        public static string TrimAfter(string value, string suffix)
        {
            Contract.Requires(value != null);
            Contract.Requires(suffix != null);
            // pre-condición agregada por Pex
            Contract.Requires
                (value.LastIndexOf(suffix) >= 0 && value.Length >= value.LastIndexOf(suffix)
                );
            // post-condición agregada a mano
            Contract.Ensures(!Contract.Result<string>().EndsWith(suffix));
            int index = value.LastIndexOf(suffix);
            return value.Substring(0, index);
        }

Y así sucesivamente😉

Bueno, antes de acabar, vamos a atar un cabo suelto: Cuando hemos marcado la casilla con la opción de generar comprobaciones en runtime, indicamos a Pex que reescriba nuestro código para inyectar las comprobaciones. Vamos a ver qué ha hecho vía Reflector:

Para más información puedes darte una vuelta por las páginas de los dos proyectos [Pex] [Code Contracts], en las cuales encontrarás documentación abundante y de calidad.

¡Hasta la próxima!

Enlaces: [Pex] [Code Contracts] [Reflector]

Esta entrada fue publicada en Dev y etiquetada , , , , . Guarda el enlace permanente.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s