Pruebas unitarias: Manos a la obra ( II )

En el post anterior vimos una introducción al framework de Visual Studio para el desarrollo de pruebas unitarias. En este post completaré algunos aspectos que no se mencionaron en el post anterior.

En el ejemplo que vimos en el post anterior implementamos una prueba muy simple sobre el método Sumar.

[TestMethod()]
public void SumarTest()
{
    ClaseEjemplo target = new ClaseEjemplo();
    int a = 1;
    int b = 2;
    int expected = 3;
    int actual;
    actual = target.Sumar(a, b);
    Assert.AreEqual(expected, actual);
}

En la prueba podéis ver cómo usando la sentencia Assert.AreEqual se comprueba si el resultado del método Sumar corresponde con el resultado esperado.

La clase Assert es parte del Framework y que se incluye dentro de la referencia Microsoft.VisualStudio.QualityTools.UnitTestFramework. Esta clase nos proporciona algunos métodos para comprobar los resultados de los métodos que están sometidos a la prueba; IsFalse, IsNull, AreSame…..

clip_image002

Parece por tanto, que toda prueba debería terminar con una clausula de este tipo para comprobar si la prueba ha sido correcta o no. Pues bien, esto no es así.

Aunque seguramente usaremos alguno de los métodos de la clase Assert en la mayoría de las situaciones no es de uso obligatorio. Si no usamos esta clausula, la ejecución de la prueba dará un resultado correcta siempre y cuando la ejecución de la misma no genere una excepción.

Por ejemplo, vamos a modificar nuestro método suma para que sólo sume valores positivos y si alguno de los parámetros que recibe es menor que 0, que devuelva una excepción.

public int Sumar(int a, int b)
{
    if (a < 0 || b < 0)
        throw new ArgumentException();
    
    return a + b;
}

En este caso, nos podría interesar hacer una prueba dónde comprobemos que no se genera una excepción cuando los parámetros sean mayores que 0. En esa prueba no sería necesario incluir ninguna sentencia de comprobación. Si le pasamos valores positivos la prueba será correcta siempre y cuando no genera una excepción.

Del mismo modo, nos interesará hacer una prueba para comprobar que la validación se hace correctamente y que se devuelve la excepción ArgumentException en este caso.

En este caso, deberemos decorar el método de prueba con el atributo ExpectedException. Con este atributo estamos especificando que el resultado esperado de la ejecución de la prueba es la excepción ArgumentException. Si no se genera la excepción o la excepción no es del tipo esperado, la prueba fallará.

[TestMethod()]
[ExpectedException(typeof(System.ArgumentException))]
public void SumarTest()
{
    ClaseEjemplo target = new ClaseEjemplo();
    int a = -1;
    int b = -2;
    int actual;
    actual = target.Sumar(a, b);
}

Para terminar con esta introducción hablaremos sobre los métodos de inicialización. En las clases que contienen las pruebas unitarias existen cuatro métodos qué tienen una finalidad especial y que están decorados con un atributo que determina su objetivo.

El atributo ClassInitialize identifica un método que contiene código que debe ejecutarse antes de que se ejecute cualquiera de las pruebas de la clase y para asignar los recursos que utilizará la clase de pruebas.

El atributo TestInitialize identifica un método que contiene código que se ejecuta antes de cada prueba que contiene la clase y se usa para asignar y configurar los recursos  que necesitan todas las clase de la prueba.

El atributo ClassCleanup identifica un método que se utilizará después de la ejecución de todas las pruebas de la clase y para liberar los recursos  obtenidos por la prueba.

El atributo TestCleanup contiene código que se ejecutará después de ejecutar cada prueba y se puede usar la liberar los recursos  obtenidos durante la prueba.

//Use ClassInitialize to run code before running the first test in the class
[ClassInitialize()]
public static void MyClassInitialize(TestContext testContext)
{
}

//Use ClassCleanup to run code after all tests in a class have run
[ClassCleanup()]
public static void MyClassCleanup()
{
}

//Use TestInitialize to run code before running each test
[TestInitialize()]
public void MyTestInitialize()
{
}

//Use TestCleanup to run code after each test has run
[TestCleanup()]
public void MyTestCleanup()
{
}

Claro está, el uso de estos métodos no es para nada obligatorio y sólo deberemos emplearlos en las situaciones que lo necesitemos. Por defecto ,al generar la prueba, estos métodos están comentados.

Como ya vimos en los post anteriores una prueba unitaria debería funcionar de manera independiente al entorno y una vez ejecutada dejarlo tal y como estaba antes de ejecutar la prueba.

Estos cuatro métodos nos ayudarán a cumplir con estos dos objetivos ya que nos podrán ayudar a preparar el entorno y los recursos necesarios para la ejecución de las pruebas y a restaurar el entorno y liberar los recursos una vez ejecutadas.

Ibon Landa

bon Landa lleva más de 15 años dedicado al desarrollo de software. Durante este tiempo ha trabajado en diferentes empresas en las cuáles ha podido trabajar en diferentes entornos y tecnologías. Actualmente está focalizado principalmente en tareas de desarrollo, arquitectura, en las herramientas del ciclo de vida y en todo lo relacionado con la plataforma de Cloud Computing Microsoft Azure, área en el que ha sido reconocido como MVP. Participa de forma activa en la comunidad, escribiendo su blog, manteniendo un portal sobre Microsoft Azure y colaborando con Microsoft y grupos de usuarios en eventos de formación, talleres y giras de producto.

4 comentarios en “Pruebas unitarias: Manos a la obra ( II )”

  1. Por ejemplo si hago prueba unitaria de insert, usaría esos métodos para borrar los registros insertados, cierto?

    Y en cuanto a liberar los recursos, te refieres a conexiones a la base de datos? o algo más?

    Saludos,

  2. Hola Sergio,

    Sí, por ejemplo podrías borrar los registros que hayas insertado o deshacer los cambios realizados.

    En los recursos me refiero a base de datos, a un XML o DataSource que puedas usar a las pruebas, objetos Helper que uses para las pruebas….realmente cualquier cosa que puedas necesitas para tus pruebas.

    En los que se ejecutan antes crear los objetos necesarios, durante las N pruebas los usas y al final los liberas.

  3. Hola, tengo varios metodos que consultan, insertan, modifican y eliminan datos de la BD, pero la coneccion se extrae de otra clase. Cuando quiero hacer una prueba unitaria de estos metodos me sale el error diciendo que no tiene coneccion con la BD ¿como puedo hacer para que se ejecute la clase de conexion y luego se utilice esta conexion antes de realizar la prueba de los metodos?. Gracias por tu respuesta.

  4. Hola Javier,

    A los métodos de consultar/insertar/modificar por lo que dices entiendo que de alguna manera se le pasa un objeto conexión? Si es así, sería el código de la prueba unitaria el que instanciaría la clase de conexión y le pasaría el objeto a los métodos.

    Si los métodos a probar necesitan que se le indique una conexión, tendrá que ser el código de test el que la genere y se la propocione.

    De todas maneras, en la capa de acceso a datos yo soy partidario de que cada método cree su propia conexión ( realmente no se crea porque se usa un pool ) , para liberarla tan pronto como ya no se necesite. No soy partidiario de tener un objeto conexión y llevarlo entre métodos…pero bueno, esa sería otro tema 🙂

    Un saludo,

Deja un comentario

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