Blog de Fernando Machado Piriz

Artículos sobre transformación digital, arquitectura empresarial y temas relacionados

Archive for mayo 2010

Test Driven Development con Visual Studio 2010 y C# 4.0

with 4 comments

Este es el primer artículo de la serie sobre herramientas y técnicas de testing  con Visual Studio 2010, Team Foundation Server 2010, Microsoft Test Manager 2010, y C# 4.0.

En Test Driven Development se escriben los unit tests antes que el código bajo prueba. Visual Studio permite escribir los casos de prueba primero y simplifica la generación a partir del uso de esqueletos del código a probar. En este vídeo verá cómo crear un unit tests para un método Add de una clase Calculadora, y cómo Visual Studio genera automáticamente la clase Calculator y el esqueleto de método de Add por nosotros.

También pueden descargar el video.

Publicaré más videos próximamente. ¡No se los pierdan!

Anuncios

Written by fmachadopiriz

13/05/2010 at 11:12

Testing con Visual Studio 2010

with 8 comments

Este es el primer artículo de una serie sobre herramientas y técnicas de de testing con Visual Studio 2010, Team Foundation Server 2010, Microsoft Test Manager 2010, yC# 4.0.

Son videos introductorios cortos (1-5 minutos) y simples. Los temas que estaré cubiendo en los sucesivos artículos son:

¡No se los pierdan!

Written by fmachadopiriz

13/05/2010 at 11:02

Publicado en Anuncios

Tagged with , ,

Pex y Contracts: Juntos son dinamita

with 4 comments

Pex es un generador automático de pruebas de unidad que se integra con Visual Studio. Contracts es una implementación de un concepto llamado Diseño por Contrato (DBC por sus iniciales en inglés). En este artículo muestro cómo usar Pex y Contracts juntos para mejorar la calidad del código.

Hace un tiempo estuve trabajando en una aplicación en la que necesitaba cargar dinámicamente los tipos disponibles en ciertos ensamblados que implementaran cierta interfaz. No quiero entrar en los detalles cómo se resuelve ese problema de la carga dinámica de tipos (que dicho sea de paso podría haber intentado resolver con el Managed Extensibility Framework), sino cómo he usado Pex y Contracts para resolverlo.

He implementado una clase Helper que puede encontrar esos tipos buscando todos los tipos del ensamblado que implementan la interfaz dada. La clase también puede asegurarse que es posible crear instancias de esos tipos, buscando entre los métodos de instancia un constructor. Luego puede invocar el constructor para crear nuevas instancias de esos tipos.

Aquí está el código de la clase Helper para este artículo:

public class Helper
{
    public static IEnumerable<Type> LoadTypesFrom(
        string assemblyName, string interfaceName)
    {
        List<Type> result = new List<Type>();
        Assembly assembly = Assembly.LoadFrom(assemblyName);
        Type[] typesInAssembly = assembly.GetExportedTypes();
        foreach (Type type in typesInAssembly)
        {
            if (type.GetInterface(interfaceName) != null)
            {
                result.Add(type);
            }
        }
        return result;
    }

    public static IEnumerable<FileInfo> GetDllsIn(string path)
    {
        List<FileInfo> result = new List<FileInfo>();
        DirectoryInfo directoryInfo = new DirectoryInfo(path);
        foreach (FileInfo item in directoryInfo.GetFiles("*.dll"))
        {
            result.Add(item);
        }
        return result;
    }

    public static object CreateInstance(Type type)
    {
        ConstructorInfo ctor = type.GetConstructor(
        BindingFlags.Instance | BindingFlags.Public, null,
        CallingConventions.HasThis, Type.EmptyTypes, null);
        return ctor.Invoke(Type.EmptyTypes);
    }
}

El método LoadTypesFrom recibe el nombre completo incluyendo el camino de un ensamblado y el nombre de una interfaz; retorna una lista con todos los tipos en ese ensamblado que implementan esa interfaz.

La lista se retorna como IEnumerable<Type> y no como IList<Type> o ICollection<Type> para evitar que los clientes de Helper puedan siquiera accidentalmente modificar la lista quitando o agregando otros tipos que eventualmente pudieran no implementar la interfaz deseada.

El método funciona cargando el ensamblado a partir del nombre completo con Assembly.LoadFrom. Luego se obtienen todos los tipos con Assembly.GetExportedTypes. Por último se itera entre esos tipos y se determina si implementan la interfaz o no usando Type.GetInterface. Los tipos que implementan la interfaz son agregados al resultado.

El método GetDllsIn recibe el nombre de una carpeta y retorna una lista con todos los archivos con extensión .DLL en esa carpeta. La lista también se retorna como un IEnumerable<FileInfo> por las mismas razones que ya les comenté para el método anterior.

El método simplemente usa DirectoryInfo.GetFiles para obtener la lista de archivos.

El método CreateInstance recibe un tipo como parámetro y retorna una instancia de ese tipo.

El método busca primero un constructor sin parámetros usando Type.GetConstructor y luego crea la instancia invocando ese constructor con ConstructorInfo.Invoke.

Voy a crear los tests de unidad en Visual Studio, habiendo instalado Pex y Contracts previamente.

Ahora comienza la magia. Hago clic con el botón secundario en LoadTypesFrom y hago clic RunPex para invocar Pex.

image

Pex usa pruebas unitarias parametrizadas, PUT por su sigla en inglés. Una PUT es simplemente un método que toma ciertos parámetros, invoca el código que está siendo probado, y efectúa ciertas afirmaciones. Para un PUT escrito en uno de los lenguajes de .NET, Pex produce automáticamente una pequeña suite de pruebas con una alta cobertura de código. Además, cuando una prueba generada falla, Pex generalmente puede sugerir una solución. Pex realiza un análisis sistemático, aprendiendo sobre el comportamiento del programa al monitorear su ejecución, y usa mecanismo para generar nuevos casos de prueba con comportamiento diferente.

Al ejecutar Pex por primera vez, Pex encuentra una excepción ArgumentNullException y me muestra lo que ha encontrado:

image

Empezamos muy bien. Al programar Helper.LoadTypesFrom no me di cuenta que si assemblyName es null el método no va a funcionar; debería haberlo controlado. Afortunadamente Pex probó ejecutar LoadTypesFrom(null) y encontró el problema.

Típicamente resolvemos esto agregando

if (assemblyName == null) throw new ArgumentNullException("assemblyName");

al comienzo del método. Es una buena práctica de programación comprobar que los argumentos no sean null si cuando valen null el método no funciona. Pero hay dos cosas que no quedan bien resueltas aún con esta buena práctica:

  1. No puedo declarar explícitamente el prerrequisito que el método requiere que el argumento no sea null. Puedo agregarlo en la documentación y puedo también agregar en la documentación que se obtendrá una excepción ArgumentNullException si el argumento vale null. Pero no puedo evitar que un programador (ni siquiera yo mismo dentro de algún tiempo) olvide que el argumento no puede ser null; eso asumiendo que el programador leyó la documentación.
  2. No puedo lograr que el programa deje de compilar o que al menos al hacerlo me dé una advertencia si programo LoadTypeFrom(null). No sólo tengo que poder declarar los prerrequisitos del método sino que también las herramientas tienen que poder interpretar y validar esos prerrequisitos.

Aquí es donde entra Contracts.

Las raíces de este concepto se encuentran muy atrás en los fundamentos de corrección de software de C.A.R. Hoare y otros, que buscaron mecanismos para escribir programas correctos, ¡y saber que eran correctos! Más recientemente Bertrand Meyer popularizó el concepto con Eiffel.

La clave detrás del diseño por contrato es que para determinar si un programa es correcto, tenemos que tener primero una especificación de lo que el programa debe de hacer; luego es fácil: si el programa hace lo que está especificado, entonces es correcto.

No quiero aburrirlos con los detalles áridos del diseño por contrato. Basta por ahora decir que aplicado a la programación orientada a objetos, la especificación de un programa está dada por:

  1. Precondiciones. Son predicados (afirmaciones que pueden ser ciertas o falsas) en el contexto de un método que deben ser ciertos para poder invocar ese método. La responsabilidad de que el predicado sea cierto es de quién invoca el método: o hace que el predicado se cumpla o no puede invocar el método. Por el contrario, es un beneficio para el programador del método: sabe que el predicado se cumple y puede confiar en eso.
  2. Poscondiciones. Son también predicados en el contexto de un método que son ciertos cuando el método retorna. La responsabilidad de que el predicado sea cierto, es ahora del programador del método: tiene que hacer sí o sí que el predicado se cumpla al terminar el método. Por el contrario, es un beneficio para quien invoca el método: sabe que cuando el método retorne el predicado se cumple y puede confiar en eso.
  3. Invariantes. Son predicados en el contexto de una clase que son ciertos durante toda la vida de cada instancia. Las invariantes son como precondiciones y poscondiciones agregadas al comienzo y al final de cada método; y al final de los constructores.

El proyecto de Contracts de Microsoft Research no es la primera implementación de Microsoft de diseño por contrato; antes estuvo el proyecto Spec# del que tal vez alguno haya escuchado hablar.

La implementación de Contracts es brillante una vez que uno conoce todas las restricciones que tuvo que superar su equipo de diseño (antes no):

  1. No podían tocar el CLR.
  2. No podían cambiar la sintaxis de los lenguajes.
  3. No podían cambiar los compiladores.
  4. Debía estar disponible en la mayor cantidad de lenguajes posible.

Las razones para estas restricciones están, por un lado, en la cantidad de lenguajes que soporta el .NET Framework, no olviden que no son sólo C# o Visual Basic; y por otro lado, tocar el CLR puede ser fuente de inestabilidad en el .NET Framework mismo.

La solución fue:

  1. Crear un conjunto de clases para implementar precondiciones, poscondiciones e invariantes. Las mismas clases pueden ser usadas desde cualquier lenguaje.
  2. Integrarse con Visual Studio para controlar las afirmaciones en tiempo de diseño. Las violaciones de las afirmaciones aparecen en la lista de errores al compilar.
  3. Inyectar código en el momento de compilar para implementar el comportamiento asociado a las afirmaciones. Por ejemplo, las poscondiciones se programan en cualquier lugar del método, pero en el código generado están siempre al final.

La principal clase para usar diseño por contrato es Contract. En Visual Studio 2008 y .NET Framework 3.5, para usarla deben agregar el ensamblado Microsoft.Contracts.dll que está en %ProgramFiles%\Microsoft\Contracts\PublicAssemblies\v3.5 a la lista de referencias del proyecto. En Visual Studio 2010 y .NET Framework 4.0 el ensamblado está disponible por defecto. En cualquier caso, deben incluir una referencia al espacio de nombre System.Diagnostics.Contracts en la cláusula using de los fuentes en los que haya referencias a la clase Contract. Por último, tienen que habilitar el chequeo de los contratos en tiempo de diseño y de ejecución, en las propiedades del proyecto:

image

Toda esta introducción es necesaria para entender que la verdadera forma de especificar que un argumento no puede ser null es:

public static IEnumerable<Type> LoadTypesFrom(
    string assemblyName, string interfaceName)
    {
        Contract.Requires(assemblyName != null);
        Contract.Requires(interfaceName != null);
        …

Las precondiciones son declaradas con Contract.Requires. El argumento de Requires es una condición que puede ser cierta o falsa. Si en algún lugar de código hubiera una invocación Helper.LoadTypesFrom(null, null), al compilar aparecería:

image

El mensaje contracts: requires is false corresponde a la línea

Helper.LoadTypesFrom(null, null);

mientras que el mensaje + location related to previous warning corresponde a la línea

Contract.Requires(assemblyName != null);

Ahora bien, ¿cómo afecta Contracts la generación de casos de prueba con Pex?

Al ejecutar nuevamente Pex en el método LoadTypesFrom aparecen más casos de prueba y más pistas para continuar complementado la especificación del método:

image

El último mensaje de error sugiere que podemos dos agregar nuevas precondiciones a LoadTypesFrom:

Contract.Requires(assemblyName.Length > 0);
Contract.Requires(interfaceName.Length > 0);

image

El último mensaje de error sugiere nuevamente una precondición; sin embargo, la condición es más compleja que las anteriores, pues debemos especificar de alguna forma que todos y cada uno de los caracteres de la cadena son válidos. Este tipo de predicado requiere de lo que se llama un cuantificador universal: Contracts.ForAll. El método ForAll está sobrecargado, la forma en que lo vamos a usar requiere de un IEnumerable y un Predicate:

Contract.Requires(Contract.ForAll<Char>(
    Path.GetInvalidPathChars(),
    (Char c) => !assemblyName.Contains(c.ToString())));

El segundo argumento es una expresión lambda que se evalúa para cada elemento del primer argumento.

Al ejecutar nuevamente Pex podemos ver cómo el caso de prueba efectivamente ejercita la precondición agregada; y nuevamente el último mensaje de error nos da pistas sobre una nueva precondición:

image

La nueva precondición es:

Contract.Requires(assemblyName.Trim() == assemblyName);
Contract.Requires(interfaceName.Trim() == interfaceName);

Pex genera todavía más casos de prueba; y al igual que antes aparece un nuevo mensaje de error:

image

Cuando agregamos la precondición [1]

Contract.Requires(File.Exists(assemblyName));

Pex genera once casos de prueba sin ninguna excepción.

Recuerden que las precondiciones son obligaciones para quién invoque el método LoadTypesFrom. Ahora bien, ¿cuáles son las obligaciones de LoadTypesFrom? O dicho de otra forma, ¿dónde está especificado qué es lo que hace LoadTypesFrom?

Es hora de agregar poscondiciones. Las poscondiciones se agregan con Contract.Ensures:

Contract.Ensures(Contract.Result<IEnumerable<Type>>() != null);
Contract.Ensures(Contract.ForAll<Type>(
    Contract.Result<IEnumerable<Type>>(),
    (Type item) => item.GetInterface(typeName) != null));

La primera poscondición especifica que el resultado nunca es null. Esto implica que cuando no se encuentran los tipos buscados en el ensamblado indicado, el método retorna una lista vacía y no null [2].

La segunda poscondición también incluye un cuantificador universal. En prosa, la poscondición especifica que todos los tipos retornados como resultado, implementan la interfaz pasada como argumento, ¡qué es justamente lo que dije que el método hacía cuando lo describí más arriba!

Esa es justamente la belleza del diseño por contrato: es posible describir lo que un programa hace (un método en este caso) de forma inequívoca, no ambigua, y comprobable automáticamente. Además, como siempre acompaña al código, es más fácil de encontrar y mantener actualizada, no se pierde, etc.

Pero volvamos a Pex. Para guardar los casos de prueba generados, los seleccionamos, hacemos clic con el botón secundario del mouse, y luego elegimos Save…:

image

Pex nos muestra lo que va a hacer a continuación, paso a paso:

image

Luego que Pex realiza los cambios anunciados, la solución tiene ahora un proyecto de prueba, que podemos ejecutar igual que cualquier otro:

image

Antes de ejecutar los casos de prueba generados por Pex, vamos hablar un poco de cobertura.

Uno de los objetivos de usar Pex es generar la menor cantidad casos de prueba que aseguren la mayor cobertura posible. La cobertura de los casos de prueba, o simplemente cobertura, es la cantidad de líneas de código alcanzadas durante la ejecución de la prueba. Si la cobertura es baja, faltan casos de prueba y podría haber errores en las líneas de código no alcanzadas durante la prueba. No podemos afirmar que haya errores, pero sí que si hay errores en las líneas no alcanzadas no podremos encontrarlos.

Por el contrario, una mayor cobertura no es garantía de mejores casos de prueba o ausencia de errores, pero si me dan a elegir a mi prefiero la mayor cobertura posible.

Para medir la cobertura es necesario cambiar primero la configuración de ejecución las pruebas. Hacemos clic en Test, luego en Edit Test Run Configurations y luego en la configuración deseada, que en forma predeterminada es Local Test Run:

image

Allí seleccionamos Code Coverage de la lista y marcamos el ensamblado del que queremos medir la cobertura en la lista Select artifacts to instrument.

Para ejecutar los casos de prueba hacemos clic en Test, luego en Run, y luego All Tests in Solution. Al final obtenemos el resultado:

image

No sabemos todavía si el método LoadTypeFrom funciona o no. Para averiguarlo podemos generar un caso de prueba que busque una clase que implemente una interfaz que sabemos que está en cierto ensamblado. Agregamos en el mismo lugar donde está la clase de prueba generada por Pex, una interfaz y una clase que implemente esa interfaz, como estas:

public interface Foo { }

public class Boo : Foo { }
}

Luego agregamos un nuevo método así:

[TestMethod]
public void TestLoadTypesFrom()
{
    IEnumerable<Type> result = Helper.LoadTypesFrom(
    Assembly.GetExecutingAssembly().Location, "Foo");
    bool booExists = false;
    foreach (Type type in result)
    {
        if (type.Equals(typeof(Boo)))
        {
            booExists = true;
        }
    }
    PexAssert.IsTrue(booExists);
}

Como la interfaz Foo y la clase Boo están implementadas junto con los casos de prueba, están en el mismo ensamblado, el ensamblado que se está ejecutando cuando se invoca el caso de prueba. Ese ensamblado se obtiene con Assembly.GetExecutingAssembly. Por eso podemos estar seguros que el método LoadTypesFrom debería retornar Boo en la lista. El bucle foreach busca el tipo Boo en el resultado y usa PexAssert.IsTrue para mostrar el resultado junto con el resultado de los demás casos de pruebas generados por Pex.

image

Conclusiones

Pex es un proyecto de Microsoft Research, que se integra directamente en Visual Studio. Realiza un análisis sistemático del código a probar, buscando valores de frontera, excepciones, etc. y así encuentra valores de entrada/salida interesantes para los métodos seleccionados, que pueden ser usados para generar una pequeña suite de pruebas con alta cobertura de código.

Contracts es también un proyecto de Microsoft Research para poder aplicar conceptos de diseño por contrato en los lenguajes del .NET Framework.

Cada uno por su lado es muy interesante, pero usados juntos, podemos crear mejores especificaciones con Contracts y mejores casos de prueba con Pex. Por eso decimos, Pex y Contacts: ¡juntos son dinamita!

Referencias

La página principal de Pex en Microsoft Research es http://research.microsoft.com/en-us/projects/pex/

En MSDN DevLabs es http://msdn.microsoft.com/en-us/devlabs/cc950525.aspx

La página principal de Contracts en Microsoft Research es http://research.microsoft.com/en-us/projects/contracts/

En MSDN DevLabs es http://msdn.microsoft.com/en-us/devlabs/dd491992.aspx


[1] Alguien podría decir que no es correcto agregar esta precondición pues no indica una condición sobre el programa sino sobre un archivo que es externo al programa. Lo que estamos especificando es que el ensamblado debe existir para poder extraer tipos de él.

[2] Es discutible si el resultado debería ser null en lugar de una lista vacía. Alguien podría decir que se está instanciando un objeto que luego no se usa, lo que impacta negativamente en el desempeño. Yo prefiero el enfoque de retornar la lista vacía, pues siempre puedo poner el resultado en un bucle foreach sin miedo a que pueda fallar porque la colección a recorrer vale null, en este caso donde el impacto en el desempeño es mínimo.

Written by fmachadopiriz

07/05/2010 at 16:25

Testing con Visual Studio 2010 y Team Foundation Server 2010 en el Testing Day de TCS

with 2 comments

clip_image002El próximo jueves 13 de mayo de 2010, de 14:15 a 15:00, junto con Gabriel Klestorny de Microsoft, estaremos presentando las características de testing de Visual Studio 2010 y Team Foundation Server 2010, durante el Testing Day 2010 organizado por Tata Consultancy Services.

El evento tendrá luga en el KDC, Av. Italia 6201 y Bolonia (LATU).

Pueden registrarse enviando un correo a alexandra.vitola@tcs.com.

Algunas de las cosas que estaremos mostrando con Gabriel durante la presentación incluyen: Pex, Contracts, Test Impact Analysis, Intellitrace, Test Manager, Coded UI Test y Performance Testing.

Aquí les dejo la agenda completa del evento:

 Print

¡Los esperamos!

Written by fmachadopiriz

07/05/2010 at 15:26

Publicado en Anuncios

Tagged with ,

Lo que me llevé del CloudCamp en Montevideo

leave a comment »

Ayer fue el CloudCamp en Montevideo. Tres cosas prometían ser interesantes en el evento. Primero, la estructura, una unconference, con unpanels, lightning talks, open spaces, etc. Luego, los invitados como Eugenio Pace y Scott Densmore de Patterns & Practices Group de Microsoft, y Dave Nielsen de CloudCamp.org. Por último, el tema, cloud computing.

Y lo que prometía se cumplió. El evento fue desestructurado y con mucha participación de la audiencia, así que salió como “todo el mundo quería”, lo cual no es menor. Eugenio y Scott, así como Gastón con una de las lightning talks y Andrés Aguiar en la organización, no desentonaron. Dave resultó ser muy buen actor y su charla resultó muy divertida. Respecto al tema de cloud computing, aunque se habló de varios proveedores, alternativas, etc., dominó la plataforma Azure.

Algunas cosas que me llevé del CloudCamp (además de un ejemplar de Application Architecture Guide y otro de Claims-Base Identity and Access Control que entregaron a los asistentes):

Azure no está hecho sólo para escalar. También puedo hacer aplicaciones ASP.NET comunes y corrientes, que no usan worker roles, no usan queues, ni tables, ni blobs. Lo único que tengo que tener en cuenta es que en Azure no tengo afinidad en la sesión, es decir, las solicitudes del navegador al otro lado de la nube durante una sesión de usuario pueden caer en cualquier instancia, pero tengo que reconocer de alguna forma que son del mismo usuario. Ese problema ya existía antes, sólo que en algunos escenarios on premise podía vivir con él; ahora eso no es posible, y debo usar algún mecanismo de manejo de sesión (que dicho sea de paso, están disponibles hace rato, como un token en la URL, campos ocultos en el formulario, etc.).

Más allá de ese detalle, si puedo convertir mi aplicación ASP.NET a un web role, no estoy obligado a usar ninguna de las cosas nuevas de Azure. Ahora bien, si quiero que mi aplicación escale, entonces sí tengo que usar (dependiendo de cuál sea la razón por la cual mi aplicación ASP.net no escala) algunas de las características nuevas, como worker roles más queues, tables, etc.

Aunque Azure no esta hecho solo para escalar, la verdad es que sí permite escalar si hace falta. Tanto el modelo desacoplado de web role y worker role, comunicándose mediante queues, como las tables y blobs capaces de contener cantidades groseramente enormes de datos, como la configuración dinámica de instancias a demanda, como el balance de carga automático, etc., tienen como propósito permitirnos a los desarrolladores crear fácilmente aplicaciones fácilmente escalables. Los dos fácilmente en la frase anterior son a propósito: es fácil crear aplicaciones que escalen, pues se usan los lenguajes, tecnologías, etc. que venimos usando para aplicaciones ASP. NET, y también es fácil hacer que escalen, pues sólo tengo que cambiar la configuración y no tengo costos prohibitivos iniciales.

Cuando comenzamos a escuchar hablar de tables y blobs, fundamentalmente tables, vimos como una limitación el hecho que no haya consultas SQL, ni índices, ni ADO.NET, etc. Y la verdad, es que las tables y blobs no están hechas para resolver los problemas de almacenamiento de datos que se nos presentan a diario. Para esos problemas tengo SQL Azure. Las tables y blobs resuelven problemas nuevos, a los que la mayoría de los desarrolladores de aplicaciones de negocio no nos enfrentamos casi nunca (a menos que el negocio sea Facebook o You Tube): almacenar grandes volúmenes de datos (como por ejemplo imágenes o videos). En un blob que almacena videos, no haría una consulta que involucre el contenido del video (como por ejemplo SELECT * FROM VIDEOS WHERE “FERNANDO IS DOING A DEMO”; si necesito recuperar todos los videos de Fernando, usaré también SQL Azure, y haré una consulta en una base de datos para recuperar todos los punteros a los videos, y con los punteros voy directo al video en el blob.

Alguien podrá decir que todas estas cosas son obvias, pero en mi humilde opinión no son suficientemente enfatizadas.

En resumen, un formato muy atractivo para conferencias, la oportunidad de poder charlar mano a mano con expertos como Eugenio Pace, Scott Densmore y Dave Nielsen y la oportunidad escuchar y hablar sobre un tema nuevo y atractivo como cloud computing, eso fue para mi el CloudCamp.

Written by fmachadopiriz

05/05/2010 at 13:19

Publicado en Anuncios

Tagged with ,