Pexcando errores en nuestro código…

Buenas… ¿Conoceis Pex? Es una herramienta que genera tests unitarios a partir de nuestro código. Su característica principal es que analiza el código e intenta generar tests unitarios que cubran todas las posibilidades de nuestro código.

Vamos a ver un pequeño ejemplo de como usarlo y como se integra con Microsoft Code Contracts. Antes que nada os recomiendo echar un vistazo al genial Post de Jorge Serrano Precondiciones y Microsoft Code Contracts v1.0.

Vamos a hacer un método muy simple (y conocido por aquellos que desarrollen en VB.NET):

 public static class StringExtensions
{
     public static string Right(this string value, int chars)
     {
         return value;
     }
}

El método Right debe devolver los últimos chars carácteres de la cadena value.

Simplemente con este código nos descargamos Pex y lo instalamos. Una vez hecho vereis que nos aparece una nueva opción el menú contextual del “Solution Explorer” llamada “Run Pex Explorations”. Con esta opción lo que hacemos es que Pex analice todo nuestro código buscando generar Tests unitarios para nuestros métodos públicos:

image

Si ejecutamos esta opción Pex analiza el código y nos genera tests unitarios para el método Right:

image

 

En este caso nos ha generado un solo test. Es importante saber como Pex genera los tests: no lo hace al azar, sinó que intenta cubrir el máximo de nuestro código. En nuestro caso como no nuestro código apenas hace nada, con un solo test, Pex obtiene una cobertura del 100% y se queda ahí. Evidentemente Pex nada sabe de cual debe ser el resultado de nuestro método, por lo que no puede generar tests unitarios funcionales.

Ahora antes de empezar a analizar código vamos a ver el uso de Code Contracts. Una vez instalada, añadís la referencia a “Microsoft Contracts Library” (Microsoft.Contracts.dll). En el framework 4.0 parece ser que se incluirá el código dentro de mscorlib, por lo que no será necesario usar referencia alguna.

Ya estamos listos para definir los contratos en nuestra clase. Para ello debemos especificar las precondiciones de nuestro método. Una precondición es algo que debe cumplirse sí o sí para asegurar que la llamada  anuestro método es “válida”.

Una precondición de nuestro método es que NO vamos a aceptar que la cadena de entrada sea nula. El método Contract.Requires sirve para especificar las precondiciones:

public static string Right(this string value, int chars)
{
    Contract.Requires(value !=null);
    return value;
}

Si lanzamos Pex ahora vemos que… no ocurre nada distinto. Esto es porque por defecto los contratos no se evalúan. Si queremos que se evalúen debemos irnos a la pestaña “Code Contracts” de las propiedades del proyecto y habilitar “Perform runtime contract checking”. Una vez hecho esto, volvemos a ejecutar Pex y obtenemos lo siguiente:

image

Vemos que ahora Pex nos ha generado dos tests: uno que pase el contrato (le manda una cadena con un ‘’ y otro que no (le manda null).

Otra pecondición que vamos a usar es que el segundo parámetro debe ser mayor que cero:

public static string Right(this string value, int chars)
{
    Contract.Requires(value !=null);
    Contract.Requires(chars > 0);
    return value;
}

 

Una vez establecidas las precondiciones nos interesa establecer las postcondiciones, es decir todo aquello que aseguramos que se cumple al finalizar el método. Para ello usamos Contract.Ensures. P.ej. para añadir una postcondición que asegure que nunca devolveremos null:

public static string Right(this string value, int chars)
{
    Contract.Requires(value !=null);
    Contract.Requires(chars > 0);
    Contract.Ensures(Contract.Result<string>() != null);
    return value;
}

Una cosa realmente importante: Contract.Ensures se evalúa SIEMPRE al final del método… aunque como en este caso lo hayamos colocada antes del return, se evaluará después del return. El secreto está en que Code Contracts viene con un MSIL rewriter que modifica el código MSIL desplazando las llamadas a Contract.Requires al principio del método y de Contract.Ensures al final. El método Contract.Result<T> sirve para acceder al valor retornado por el método.

Contract.Requires no deja obsoleto a Debug.Assert. Ambos son necesarios: Con Contract.Requires comprobaremos las precondiciones lógicas de nuestro método, mientras que Debug.Assert lo seguiremos usando para comprobaciones de depuración (comprobaciones internas).

Tenemos el contrato de nuestro método especificado, tenemos a Pex para que nos genere tests unitarios… podemos empezar a meter código!

 public static class StringExtensions
{
     public static string Right(this string value, int chars)
     {
         Contract.Requires(value !=null);
         Contract.Requires(chars > 0);
         Contract.Ensures(Contract.Result<string>() != null);
         if (chars >= value.Length) return value;
         else
         {
             int inicial = value.Length - chars;
             if (inicial < 0) inicial = 0;
             return value.Substring(inicial, chars);
         }
     }
}

Una vez tenemos el código ya listo (o eso creemos jejejeeee…) relanzamos Pex para que nos cree un nuevo conjunto de tests unitarios para nuestro método. Como ahora tenemos un código un poco más complejo, Pex nos creará varios tests unitarios:

image

Pex ha creado 4 tests unitarios con el objetivo de cubrir al máximo nuestro código. A partir de este punto si queremos podemos generar un proyecto de test con estos 4 tests unitarios. Los podemos seleccionar desde la ventana de Pex Exploration Results y le dais a “Save”:

image

Con ello Pex nos genera el proyecto de tests unitarios:

image

El proyecto tiene dos archivos especiales: El fichero StringExtensionsTest.cs y el fichero StringExtensionsTest.Right.g.cs.

El segundo (.g.cs) se regenera cada vez que ejecutamos Pex, por lo que NO debemos poner código en él. El primero por su parte se mantiene y contiene lo que Pex llama PUTs, o Parametrized Unit Tests. Un PUT es un test unitario pero que acepta parámetros. Pex los usa para poder “agrupar” clausulas Assert: En lugar de colocar un Assert para cada test unitario, coloca uno de solo en el PUT y los tests unitarios que genera Pex, son llamadas al PUT con los parámetros correspondientes.

P.ej. el código del PUT generado es:

[PexMethod]
public string Right(string value, int chars)
{
    // TODO: add assertions to method StringExtensionsTest.Right(String, Int32)
    string result = StringExtensions.Right(value, chars);
    return result;
}

Como se puede ver no hace nada salvo llamar al método real que estamos testeando pasándole los parámetros. Pero aquí nosotros podríamos poner Asserts adicionales, que se comprobarían para todos los unit tests de Pex. El código de un unit test de Pex es similar a:

[TestMethod]
[PexGeneratedBy(typeof(StringExtensionsTest))]
public void Right04()
{
    string s;
    s = this.Right("", 1);
    Assert.AreEqual<string>("", s);
}

La llamada a this.Right es la llamada al PUT que ha generado antes.

Os dejo un par de videos con más información sobre Code Contracts y Pex para que les echeis un vistazo, dado que es un tema que vale realmente la pena!

Saludos!

3 comentarios sobre “Pexcando errores en nuestro código…”

Responder a jmtorres Cancelar respuesta

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