Testeo unitario con Moles

Moles es una nueva virguería de la gente de Microsoft Research; en concreto, del equipo de desarrollo de PEX, una herramienta de generación de tests white-box, para quien no lo conozca.

¿Qué es Moles? Un nuevo framework que permite sustituir cualquier método de .NET por un delegado. ¿Y qué podemos hacer con esto? Pues sustituir cualquier llamada al framework por lo que queramos que se ejecute. Esto, evidentemente, tiene especial utilidad en el testeo unitario, donde necesitamos crear las condiciones adecuadas para ver cómo se comporta nuestro código. Un ejemplo sencillo, expuesto en este vídeo por uno de sus creadores, sería poder “trucar” la hora que nos devuelve DateTime.Now. Como sabemos, no se puede modificar pero… como Moles ¡sí! Si quieres saber cómo, échale un vistazo al vídeo.

Yo voy a proponer un ejemplo distinto: acceder a las appSettings del ConfigurationManager. En el pasado tuve que modificar la signatura de un método que leía, internalmente, una propiedad de appSettings, para poder testearlo. La razón es que no tenía manera de simular las distintas condiciones (ausencia de la clave buscada, valor malformado, etc) puesto que la clase ConfigurationManager no podía mockearse. La solución al final fue pasarle al método la NameValueCollection que contiene las appSettings, pudiendo así simular mis condiciones de testeo.

Pero no mola  tener que modificar un método que ya tienes codificado por culpa de las limitaciones del framework de testeo. Aquí es donde Moles va a echarnos una mano. Veamos paso a paso cómo.

Empecemos con el método que vamos a testear. Digamos que lee una variable de las appSettings y la retorna. Si no existe la clave o tiene un valor vacío, lanza una excepcion. El código podría ser algo así:

/// <summary>
/// Retrieves app setting by its key.
/// </summary>
/// <returns>Value in the settings.</returns>
public static string ExtractAppSetting()
{
    string key = "myKey";
    if (ConfigurationManager.AppSettings.AllKeys.Contains(key) == false ||
        string.IsNullOrEmpty(ConfigurationManager.AppSettings[key]) == true)
    {
        throw new ConfigurationErrorsException("Key not found");
    }

    return ConfigurationManager.AppSettings[key];
}

Fácil, ¿no? El test más típico para este código sería aquél en el que previamente hemos configurado una key en la sección AppSettings del fichero App.config y después intentamos recuperarla mediante el método testeado. Suponiendo la existencia de una setting con key “myKey” y valor “myValue”, el siguiente test nos daría un bonito verde.

/// <summary>
/// Test that checks if the method returns the 
/// appropriate value from AppSettings.
/// </summary>
[TestMethod]
public void ExtractAppSettingValidConfigurationOk()
{
    Assert.AreEqual<string>(
        "myvalue", 
        AppSettingsManager.ExtractAppSetting(), 
        "Invalid value in AppSettings");
}

Bien, ¿y cómo podemos probar ahora el comportamiento de nuestro método si no existiera la key? Quizás, a la desesperada, nos podríamos plantear intentar modificar por código el fichero app.config. Sin embargo, esto no sería una buena idea porque los tests, de tener varios, se lanzan en hilos en paralelo, por lo que podríamos tener resultados inesperados si modificamos este fichero: las clásicas condiciones de carrera.

La solución nos la aporta Moles. En primer lugar, añadimos una referencia al assembly que contiene la clase ConfigurationManager; es System.Configuration. Después, ya podemos añadir el fichero Moles a nuestro proyecto de testeo, como muestra la siguiente figura. El nombre que le daremos debería coincidir con el assembly que vamos a “molear” (¡toma palabro!).

AddingMolesFile

Una vez creado el fichero Moles, ya podemos empezar a usar su “magia” en nuestro código de testeo. Creamos otro test, pero esta vez probará que el método lanza una excepción si las AppSettings están vacías y, por tanto, no contienen la key que estamos buscando. ¿Cómo se haría esto? Sencillo, usando el mole del ConfigurationManager, que nos dará la posibilidad de modificar la colección AppSettings.

/// <summary>
/// Test that checks if the method throws an exception if 
/// the AppSettings is empty.
/// </summary>
[TestMethod]
[ExpectedException(typeof(ConfigurationErrorsException))]
public void ExtractAppSettingEmptySettingsExceptionExpected()
{
    MConfigurationManager.AppSettingsGet = () => new NameValueCollection();
    AppSettingsManager.ExtractAppSetting();
}

Aún nos quedaría un último paso, puesto que si lanzáramos el test ahora mismo nos daría una excepción en tiempo de ejecución indicándonos que tenemos que añadir el siguiente atributo para indicar que el test tiene que ejecutarse “hosteado” por Moles. El test quedaría así finalmente:

/// <summary>
/// Test that checks if the method throws an exception if 
/// the AppSettings is empty.
/// </summary>
[TestMethod]
[HostType("Moles")]
[ExpectedException(typeof(ConfigurationErrorsException))]
public void ExtractAppSettingEmptySettingsExceptionExpected()
{
    MConfigurationManager.AppSettingsGet = () => new NameValueCollection();
    AppSettingsManager.ExtractAppSetting();
}

Y éste sería el resultado:

ExceptionFromMoles

¿Cómo? ¿Una excepción en tiempo de ejecución? Pues sí, resulta que Moles no soporta la modificación del objeto System.Configuration.ConfigurationManager. En este hilo lo explica levemente uno de sus creadores. Así que en lugar de tirar la basura el post, he preferido indicar un ejemplo de su uso, aunque el final haya sido tan trágico como el de Seven.

Otro día prometo postear un resultado correcto. En cualquier caso, Moles sigue siendo una tecnología a seguir, aunque con sus limitaciones.

 

Bibliografía:

Descargas: