martes, 4 de noviembre de 2008

Introducción a las pruebas unitarias (II)

- Frameworks de pruebas unitarias

En este segundo artículo sobre la introducción a las pruebas unitarias vamos ver un ejemplo de aplicación de las mismas.

Cómo norma general las pruebas unitarias utilizan algún tipo de framework que permite su codificación, su ejecución y la visualización de los resultados de los tests. Hay multitud de frameworks donde elegir, por ejemplo NUnit o MbUnit . Para el ejemplo de hoy vamos a elegir el framework de test que viene directamente integrado en Visual Studio (muchos de los existentes pueden integrarse también), que no es otro que MSTest. Este framework está implementado en la librería Microsoft.VisualStudio.QualityTools.UnitTestFramework, que será añadida automáticamente por Visual Studio al proyecto donde se ubiquen las clases de tests.

En el ejemplo vamos a crear los test necesarios para una clase propia que permite realizar operaciones aritméticas básicas (sumar, restar, multiplicar y dividir).

Nuestra clase tendrá el siguiente aspecto:

namespace MathLibrary

{

public class Math

{

public int Sum(int a, int b)

{

return a + b;

}

public int Rest(int a, int b)

{

return a - b;

}

public int Div(int a, int b)

{

return a / b;

}

public int Mul(int a, int b)

{

return a * b;

}

}

}

- Creando el primer test

Una vez creada nuestra sencilla clase vamos a crear las pruebas unitarias necesarias. Para ello hacemos click con el botón derecho sobre el código fuente de nuestra clase y seleccionamos la opción Create Unit Tests…


Seleccionamos la opción de Crear Tests Unitarios…


Aparecerá una pantalla donde podemos indicar varios aspectos relativos a la clase de Tests que vamos a crear:

  • Que funciones queremos testear (cómo normal general las funciones públicas)
  • En que proyecto se debe crear la clase (por defecto aparece la opción de crear un nuevo proyecto)
  • Configuraciones relativas a la nomenclatura de las clases y funciones del proyecto de tests.

Pantalla de creación del test unitario…


Una vez indicados los parámetros de creación de los tests se nos creará la clase (y el proyecto si es necesario) de test. También podremos ver cómo la librería del Framework comentada anteriormente es añadida al proyecto.

En este código hay varias cosas a destacar:

  • El constructor de la clase está decorado con el atributo TestClass. Esto le indica al Framework de Tests que es una clase de testeo.

[TestClass()]

public class MathTest

  • Un método correspondiente a cada uno de los métodos a testear decorado con el atributo TestMethod.

[TestMethod()]

public void SumTest()

[TestMethod()]

public void RestTest()

[TestMethod()]

public void MulTest()

[TestMethod()]

public void DivTest()

  • Una region con 4 métodos que por defecto están comentados

[ClassInitialize()]

public static void MyClassInitialize(TestContext testContext)

[ClassCleanup()]

public static void MyClassCleanup()

[TestInitialize()]

public void MyTestInitialize()

[TestCleanup()]

public void MyTestCleanup()


Cada uno de estos métodos sirven cómo métodos auxiliares para la creación y/o libreación de recursos de los tests unitarios. Cómo alguno habrá deducido estos métodos se ejecutan respectivamente en los siguientes momentos: una vez antes de iniciar la ejecución de los test (MyClassInitialize) , al finalizar la ejecución de todos los tests (MyClassCleanup), antes de iniciar la ejecución de cada uno de los tests (MyTestInitialize) y al finalizar la ejecución de cada uno de los tests (MyTestCleanup).

Estos métodos pueden utilizarse para abrir/cerrar conexiones de bbdd, crear/destruir objetos necesarios para cada ejecución de un test o en cualquier situación otra situación que se crea conveniente.

Hay que comentar que a pesar de que por defecto se crea un método de test por cada método a probar, se pueden crear adicionalmente todos los métodos de test que se crean convenientes para probar el mismo test, por ejemplo para probar diferentes valores de entrada, probar el lanzamiento controlado de excepciones…


¿Cómo implementar las pruebas?


Llegados a este punto vamos a ver cómo probar las funciones de nuestra librería.

Para ello vamos a codificar cada uno de los 4 métodos que nos ha creado visual Studio por defecto.Lo primero es borrar el código que nos ha creado Visual Studio dentro del método (o utilizarlo cómo guía si se cree conveniente), y pensar cómo probar nuestro método. En nuestro caso es bien fácil: Para probar el método de sumar tenemos que comprobar que pasados dos números cómo parámetros nos devuelve un número que corresponde a la suma de los dos. El código del test podría quedar algo así:


[TestMethod()]

public void SumTest()

{

Math math = new Math();

int suma = math.Sum(3, 4);

Assert.AreEqual(suma, 7);

}


Creamos una instancia de la clase Math y comprobamos que el resultado de llamar al método Sum coincide con la suma de los dos números. Para las comprobaciones de los resultados se utiliza la clase Assert, que tiene métodos para realizar comprobaciones de todo tipo. Si la comprobación de dentro del Assert no se cumple, el test lanza una excepción y falla. Si todos los Assert de dentro de un método se cumplen (se pueden poner más de uno), se considera que el método es correcto.


Lo mismo que hemos hecho para la suma lo hacemos para los otros 3 métodos. Ahora sólo queda ejecutar los tests. Para hacerlo utilizamos la barra de herramientas de tests, concretamente la opción de Run All Tests In Solution (también tenemos la posibilidad de ejecutar algunos tests o incluso de debugarlos)




El resultado de los tests se muestra en la siguiente pantalla:



Si alguno de los tests fallase se mostraría el icono correspondiente y el mensaje indicando que error ha sido (si ha sido algún Assert o un error de implemetación en el propio test).


En este punto parece que ya hemos asegurado que nuestra clase funciona correctamente y que los tests cubren todos los casos, pero si nos fijamos un poco podemos ver que el código de la división lanzará una excepción cuando nos pasen cómo segundo parámetro un 0 ¿Cómo se controla esto desde la clase de test?


Pues la idea es declarar explícitamente que se espera que el método lance una excepción en unos casos determinados. En nuestro caso se debería crear un nuevo método que nos testee la división por cero de la siguiente manera:

[TestMethod()]

[ExpectedException(typeof(DivideByZeroException), "División por cero")]

public void DivByZeroTest()

{

Math math = new Math();

int div = math.Div(2, 0);

}


Decorando el método con el atributo ExpectedException aseguramos que el test sea cierto si se lanza la excepción indicada.


La pantalla de resultados de test quedaría de la siguiente manera:



En este punto ya tenemos nuestra clase probada, y nos aseguramos que posteriores modificaciones sobre la misma que produzcan algún error será detectado rápidamente.

-Configuración de tests



Desde Visual Studio se pueden configurar ciertos aspectos de los tests unitarios. Para ello se debe hacer doble click sobre el fichero LocalTestRun.testrunconfig que el IDE generado al crear el test unitario.



Aparecerá la siguiente pantalla:


En la misma podemos configurar una serie de aspectos, cómo el nombre de los tests, los ensamblados a testear, los ficheros a desplegar al hacer los tests, scripts, timeouts, etc…

Podéis investigar por vuestra cuenta con las diferentes opciones de configuración a medida que las vayáis necesitando en la codificación de pruebas.

- Cobertura de código

Una de las opciones a configurar que me parece interesante reseñar es la cobertura de código. Con la cobertura de código podemos saber que porcentaje de nuestro código está cubierto por algún test unitario. OJO, esto no quiere decir que ese código funcione, únicamente indica que hay algún test que se encarga de probarlo!


Para activar la cobertura de código hay que seleccionar la opción correspondiente en la pantalla de configuración y una vez allí marcar el ensamblado que queremos instrumentar (en nuestro caso MathLibrary.dll.

Cuando volvamos a ejecutar nuestros tests podremos consultar la cobertura de código en la pantalla destinada a tal efecto:


Código cubierto al 100%


-Conclusión



Con este artículo cerramos esta pequeña introducción a las pruebas unitarias con Visual Studio. A pesar de la sencillez del ejemplo, debería ser suficiente para empezar a crear vuestras primeras pruebas unitarias.


Y recordad que una de las cosas más importantes de todo equipo de desarrollo es asegurar la calidad del código, y que las pruebas unitarias son uno de los mecanismos más eficaces para conseguirlo!

Un saludo!!



No hay comentarios: