lunes, 24 de noviembre de 2008

Insertando una ProgressBar en un DataGridView


Después de unos cuantos posts orientados hacia el terreno de la metodologías y la gestión de proyectos, con este artículo vamos a volver al campo del desarrollo puro y duro.

El objetivo de este post es mostrar cómo poder insertar un control de tipo ProgressBar en una columna del control DataGridView. Este problema, a priori complicado de solucionar ya que el DataGridView no permite la inserción directa de controles en sus columnas, es relativamente sencillo de abordar si se comprende el modelo de objetos que utiliza el control DataGridView de .Net.

No vamos a entrar en detalle sobre el citado modelo de objetos, únicamente explicaremos que para poder crear nuevas columnas (cómo la nuestra de ProgressBar) necesitamos crear una nueva clase que represente el tipo de columna, y una nueva que represente el tipo de celdas que pertenecen a dicha columna. Para ello basta con crear clases que deriven de DataGridViewColumn (o una de sus clases hijas) y DataGridViewCell (o una de sus clases hijas)

Para crear una columna que permita añadir objetos ProgressBar vamos a crear una clase que derive de DataGridViewImageColumn (después veremos porque concretamente de esta y no de la clase base o de alguna de las otras subclases)


public class DataGridViewProgressColumn : DataGridViewImageColumn
{
public DataGridViewProgressColumn()
{
CellTemplate = new DataGridViewProgressCell();
}
}

En esta clase vemos que en el constructor se asigna una propiedad llamada CellTemplate. Esta propiedad se encarga de definir el tipo de celda que corresponde a la columna, de manera que cuando se tengan que crear nuevas celdas el DataGridView sepa cómo crearla. Cómo podemos ver se esta asignado a la propiedad una nueva instancia del tipo DataGridViewProgressCell, que es una clase que también tenemos que crear. Ahora vamos a ver cómo construir la clase que representará a nuestro nuevo tipo de celda:

class DataGridViewProgressCell : DataGridViewImageCell
{
}

Lo primero es derivar del tipo de celda correspondiente (DataGridViewImageCell, que es el tipo de celda que corresponde a la columna de la que habíamos derivado nuestra ProgressColumn)

Ahora vamos a explicar como funcionará esta clase para poder entender todo el código que va a ir dentro de ella:

La celda mostrará una barra de progreso (en el ejemplo la estándar de Windows, pero ya veréis que será muy fácil modificarla al gusto de cada uno) , que irá actualizándose en base al valor que nosotros le pasemos a la celda. Es decir nosotros asignaremos valores enteros entre 0 y 100 y la barra de progreso se irá actualizando. Parece lógico pero ya veréis que si no de deja claro ahora puede ser complicado entender el siguiente código:

Añadimos los siguientes constructores a la clase:

static Image emptyImage;

static DataGridViewProgressCell()
{
emptyImage = new Bitmap(1, 1, System.Drawing.Imaging.PixelFormat.Format32bppArgb);

}

public DataGridViewProgressCell()

{

this.ValueType = typeof(int);
}


En el constructor estático (o de clase) se inicializa una variable de tipo Image mientras que en el contructor público se indica el tipo de datos de la columna, que será un entero. Este valor será el que se utilizará para recoger el porcentaje que se debe mostrar en la barra de progreso y ahora veremos que utilidad tiene la imagen del cosntructor estático.

El siguiente paso es sobreescribir el método GetFormattedValue. Este método sirve para transformar el valor de entrada de la celda (que en este caso es un entero) en el objeto que espera el DataGridView, que al estar derivando de DataGridViewImageCell es una imagen (Ahora se entiende el porqué de la imagen del constructor estático)


protected override object GetFormattedValue(object value, int rowIndex, ref DataGridViewCellStyle cellStyle, TypeConverter valueTypeConverter, TypeConverter formattedValueTypeConverter,
DataGridViewDataErrorContexts context)
{
return emptyImage;
}



Cómo veis, en lugar de formatear el valor de entrada y devolver la imagen correspondiente que parecía la opción más lógica según lo expuesto arriba en este método devolvemos directamente la imagen vacía, ya que en nuestro caso no se va a utilizar este método para representar el valor del ProgressBar, pero se debe devolver una imagen por consistencia con el tipo de datos de la celda madre (DataGridViewImageCell).

Y finalmente vamos ver cómo tenemos que a sobreescribir el método Paint de la celda. En este método será donde representemos realmente el valor del progreso. Con esto completaremos la clase:

protected override void Paint(System.Drawing.Graphics g, System.Drawing.Rectangle clipBounds, System.Drawing.Rectangle cellBounds, int rowIndex, DataGridViewElementStates cellState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts)
{
int progressVal;
if (value != null && value.GetType() == typeof(Int32))
progressVal = (int)value;
else
progressVal = 1;

float percentage = ((float)progressVal / 100.0f);

Brush backColorBrush = new SolidBrush(cellStyle.BackColor);

Brush foreColorBrush = new SolidBrush(cellStyle.ForeColor);

base.Paint(g, clipBounds, cellBounds, rowIndex, cellState, value, formattedValue, errorText, cellStyle, advancedBorderStyle, (paintParts & ~DataGridViewPaintParts.ContentForeground));

const int margin = 4;

ProgressBar pb = new ProgressBar();
pb.Height = cellBounds.Bottom - cellBounds.Top - (margin * 2);
pb.Width = cellBounds.Right - cellBounds.Left - (margin * 2);
pb.Value = progressVal;

Bitmap bmp = new Bitmap(pb.Width, pb.Height);

pb.DrawToBitmap(bmp, pb.ClientRectangle);

g.DrawImage(bmp, new Point(cellBounds.X + margin, cellBounds.Y + margin));
}


Lo que estamos haciendo aquí es que cada vez que se llama al evento Paint de la celda se actualiza el estado de la variable ProgressBar interna de la misma y se "pinta" el control dentro de la celda (mediante GDI). Ahora vemos cómo a partir de un entero de entrada tenemos el control ProgressBar en pantalla. El utilizar GDI para "pintar" el control aporta mucha flexibilidad ya que permite darle la apariencia que se quiera: Por ejemplo en lugar de utilizar un control ProgressBar interno y "ver su imagen" podríamos dibujar el resultado del progreso de la manera que mejor nos pareciera, con los colores que mas nos gustasen, con efectos, con texto, etc...

Ahora que ya tenemos las clases preparadas vamos a ver cómo utilizarlas en un DataGridView de manera que podamos visualizar el control en las diferentes filas que se añadan al mismo.

En el ejemplo vamos a suponer que creamos el DataGridView en tiempo de diseño (aunque se podría hacer en tiempo de ejecución). Lo primero es arrastrar el control DataGridView a nuestro formulario y visualizar la pantalla de propiedades del control.


Arrastramos el DataGridView al formulario


Pantalla de propiedades



En esta pantalla definimos las columnas que tendrá nuestro DataGridView. Pulsando el botón Add aparece una pantalla donde se indica entre otras cosas el tipo de la columna y aquí ya podemos ver que en la lista aparece un nuevo tipo, DataGridViewProgressBarColumn, que será el que seleccionaremos para la columna que tiene que mostrar la barra de progreso (Para que el nuevo tipo de columna aparezca hay que tener acceso al espacio de nombres donde hemos definido la clase desde el formulario).



Añadiendo la columna de progreso

Cuando hayamos añadido las columnas necesarias (en el ejemplo añadiremos únicamente dos) volvemos al formulario y creamos un objeto BindingSource arrastrándolo hacia el mismo. Con este objeto implementaremos el enlace entre los datos y el DataGridView.

En la pantalla deberíamos tener algo parecido a esto:


DataGridView con las dos columnas y el objeto BindingSource

El siguiente paso es crear un DataSet que será el que contenga los datos. Este DataSet podríamos extraerlo de una base de datos, de un servicio Web o de cualquier otro origen de datos, en nuestro ejemplo lo construiremos y lo llenaremos programáticamente.

private void InitializeDataSet()
{
//Creamos el DataSet y definimos las columnas.
customDataSet = new DataSet();
customDataSet.Tables.Add(new DataTable("TablaEjemplo"));
customDataSet.Tables["TablaEjemplo"].Columns.Add(new DataColumn("DSNombre", typeof(string)));
customDataSet.Tables["TablaEjemplo"].Columns.Add(new DataColumn("DSProgreso", typeof(int)));

DataRow row1 = customDataSet.Tables[0].NewRow();
row1["DSNombre"] = "Fila 1";
row1["DSProgreso"] = 25;
customDataSet.Tables[0].Rows.Add(row1);

DataRow row2 = customDataSet.Tables[0].NewRow();
row2["DSNombre"] = "Fila 2";
row2["DSProgreso"] = 35;
customDataSet.Tables[0].Rows.Add(row2);

//Ponemos a false el valor AutoGenerateColumns para utilizar las columnas definidas en vista de diseño
dataGridView1.AutoGenerateColumns = false;

//Enlazamos el Dataset con el DataGrid
bindingSource1.DataSource = customDataSet.Tables[0];
dataGridView1.DataSource = bindingSource1.DataSource;
}


Volvemos a la vista de diseño del Grid e indicamos la relación entre las columnas del DataGrid y las del DataSet (opción Columnas de las propiedades del control)


En la opción DataPropertyName de cada columna ponemos el nombre de la columna del DataSet que enlazaremos. Fijaros que son las columnas definidas en customDataSet

Si ahora ejecutamos el proyecto vemos que aparecen las dos filas creadas y que la barra de progreso de cada una corresponde a los valores dados a las columnas del DataSet. Si quisíeramos modificar ese valor sólo tendríamos que ir cambiando el valor de la celda DSProgreso del DataSet y automáticamente se vería reflejado en el Grid y en la barra de progreso.



Gracias al potente modelo de objetos que .Net define para el DataGridView podemos de manera similar al ejemplo mostrado definir columnas para hospedar casi cualquier tipo de control que necesitemos. En ocasiones habrá que derivar de la columna DataGridViewImageCell cómo en el ejemplo, pero podemos cualquiera de las otras clases base según la funcionalidad que necesitemos insertar en nuestra nueva columna.

Os esperamos con vuestras opiniones, sugerencias sobre el tema o críticas al artículo en los comentarios!

Un saludo!!



jueves, 13 de noviembre de 2008

SCRUM ( III )

En el artículo de hoy veremos las diferentes reuniones que se realizan a lo largo de cada uno de los sprints.


Reunión de inicio de sprint

Esta es la reunión de la que nos acordaremos más en el transcurso del sprint sinó la hacemos correctamente. Se realiza al principio de cada sprint, a poder ser un lunes por la mañana, cuando la gente está bien despierta y sin los agobios de toda la semana de trabajo. A ella acuden el dueño del producto, el scrum master y el equipo de desarrollo. La faena del dueño del producto ( como ya hemos visto en anteriores artículos ) es llegar a la reunión con el product backlog priorizado y detallado en sus histórias más prioritarias. En la reunión ( que acostuma a durar toda la mañana ) el dueño del producto explica al equipo cada una de las histórias que cree que van a entrar en el sprint y el entre todos se dedican a dividir la história en las diferentes tareas que la van a componer. Este diálogo es importantísimo ya que ayuda mucho a detallar exactamente lo que se va a implementar en el sprint y evita que al final del mismo haya malentendidos entre el dueño del producto y el equipo de desarrollo. De la misma forma, ayuda a detallar la história tal y cómo la entiende el dueño del producto, ya que el equipo puede creer que para implementar una tarea tiene que hacer un acceso a datos complicadísimo y en cambio el dueño del producto no lo cree necesario. Sin este diálogo, el equipo de desarrollo implementaria este acceso a datos, tardando un montón y haciendo código innecesario ya que el cliente no lo cree necesario.
En esta reunión, una vez detalladas las tareas, el equipo las debe estimar. Hay várias maneras de estimar una tarea, de las quales ya hablaremos algún dia. La estimación también ayuda mucho a refinar las tareas. En la estimación puede quedar reflejado que el equipo de desarrollo cree que para una tarea se debe hacer un algoritmo muy complicado y en cambio el dueño del producto quiere algo más simple. La estimación del equipo saldría con un valor muy alto, valor que sorprendería mucho al dueño del producto y se iniciaria otro diálogo para delimitar el alcance de la tarea.

Cómo podemos ver, aquí queda de manifiesto una de las cosas más importantes de las metodologías ágiles: la comunicación. Es muy importante la comunicación entre los miembros del equipo, desde el dueño del producto hasta los desarrolladores.

La reunión de planificación debe terminar decidiendo entre el equipo y el dueño de producto qué histórias entran en el sprint. Esto lo podemos hacer también de varias maneras ( que también veremos en otro artículo ) pero básicamente de lo que se trata es de estimar la velocidad del equipo en el sprint ( la cantidad de puntos de história que puede hacer el equipo ) y mirar qué histórias de usuario entran en el sprint. Si todavía no conocemos la velocidad del equipo la podemos intentar deducir. Primero asumiremos que un punto de história es el trabajo que puede hacer un desarrollador en un dia de trabajo ( no en un dia ideal, sinó en un dia "normal" ). Esta definición no es importante. Podeis utilizar la definición que queráis. Lo que es importante es que la definición la mantengáis constante a lo largo de la vida del proyecto. No puede ser que un dia utilicemos dias ideales y otro dia dias "normales".

Una vez aclarado esto, sumamos los puntos de história que puede hacer el equipo. Por ejemplo, si somos un equipo de cinco personas, tres de las quales estan a tiempo completo y dos a mitad de jornada, tendremos que en tres semanas el equipo puede asumir 60 puntos de história. A este valor, nosotros le aplicamos otro modificador, para intentar estimar los posibles impedimentos que podamos tener. Un valor normal puede ser un 70%. Por lo tanto, diremos que nuestro equipo es capaz de asumir 42 puntos de historia en un sprint. Teniendo ordenadas las histórias por prioridad, podremos ir viendo qué histórias nos entrarán en el sprint. Por ejemplo, si nuestras histórias hemos estimado que nos ocuparán 6, 10, 8, 12, 4, 8 y 12 puntos de história, decidiremos que haremos las primeras 5 histórias.

Hay dos cosas más importantes que deben decidirse en esta reunión:
  • El objetivo del sprint. Puede parecer superfluo pero és importante definir un objetivo del sprint para que el equipo sepa porqué está trabajando.
  • Definición de acabado. Cómo vimos en el artículo anterior, es vital tener una definición común de acabado entre el dueño del producto y el equipo de desarrollo.

Sprint diário

El sprint diário es la herramienta que nos proporciona scrum para que el equipo pueda compartir conocimiento y se comunique a diário. En esta reunión ( de unos 15 minutos en general cómo máximo ) cada miembro del equipo debe contestar tres preguntas:

  • Qué hice ayer.
  • Qué haré hoy.
  • Qué inpedimentos tengo para realizar mi trabajo.

Esta reunión es muy importante por la visibilidad que dá del proyecto y del trabajo de cada uno. Haciendo asistir a todo el equipo a la reunión evitamos que haya alguien que no haga nada durante un dia. El hecho de tener que decir delante de todo el mundo que no se ha avanzado en nada, hace que la gente trabaje cada día.

Esta reunión también nos sirve para tener el control del avance del proyecto. Gracias al sprint diario podemos actualizar nuestro tablero de scrum ( real o virtual ) y ver cómo avanzan las histórias, ver si tenemos demasiadas histórias en paralelo, demasiadas en testeo, etc. Lo otro que podemos actualizar gracias a la esta reunión es el gráfico de burndown, que nos dá una información muy visual del estado del proyecto y, sobretodo, de la velocidad del equipo, pudiendo ver de un solo vistazo si se llegará a final de sprint con todas las tareas realizadas o no.

Demostración

La penúltima reunión del sprint es la demostración. En la demostración el equipo de desarrollo debe mostrar a todo el mundo los avances realizados en este sprint, es decir, el incremento de valor que han dado al producto. Esto es muy importante porque el equipo se obliga a tener un producto que se pueda ensenyar, en un entorno de preproducción lo más fiable posible. También es importante porque el dueño del producto puede ver si lo que se ha implementado es lo que el pedía, si ha habido algún malentendido, si todo va a la velocidad requerida, etc. A esta reunión también puede asistir otra gente de la empresa, cuya opinión puede ser tenida en cuenta por el dueño del producto para planificar las próximas histórias a realizar. El equipo nunca debe sentirse influenciado por estas opiniones, ya que su interlocutor en qüestión de producto es el dueño del mismo.

Retrospectiva

Es la última reunión de cada sprint. En esta reunión se hace una revisión de cómo ha ido el sprint, qué ha ido bien y qué ha ido mal. Hay várias formas de hacer una retrospectiva. Una de las más habituales y rápidas es hacer una línea temporal en la pizarra e ir apuntando todo lo que se ha hecho en el sprint ( reuniones, impedimentos, logros, etc ). Una vez hecho esto, cada miembro del equipo señala aquellas cosas que le parecen más positivas y aquellas que le parecen más negativas. Las positivas se intentan repetir sprint a sprint. Las negativas se estudian porqué han ido mal y se intenta ver qué se puede hacer para solventar la situación y hacer que vaya mejor en poteriores sprints.


Mini reuniones de diseño

Estas reuniones no son obligatorias pero nosotros recomendamos hacerlas para no sobrecargar en demasía las reuniones de revisión diárias. Su cometido es comentar entre varios miembros del grupo de desarrolladores cosas relativas a decisiones de diseño de la aplicación. Por ejemplo si el miembro A del equipo empieza hoy la tarea "backup de datos de la aplicación" y no tiene claro cómo hacerla, en lugar de empezar una discusión de cómo hacerlo en la reunión de revisión, los miembros implicado se reunen a posteriori para hablar del tema.

Pués esto ha sido todo por hoy. Nos leemos en los comentarios!

martes, 11 de noviembre de 2008

SCRUM (II)

En este segundo artículo sobre Scrum veremos toda aquella "documentación" que generamos a lo largo de la gestión de un proyecto utilizando Scrum. Más allá de la propia documentación del proyecto cómo pueden ser manuales de usuario, archivos de ayuda, diagramas de diseño, etc, hablaremos de la documentación que se genera por el echo de utilizar Scrum. Dado que Scrum es un marco de desarrollo ágil y cómo tal prefiere el software que funciona por encima de la documentación exhaustiva, veremos que la documentación que generamos no es muy amplia aunque, eso sí, muy útil.

Product Backlog El product backlog es el documento donde se describen las histórias de usuario que se quiere que alguna vez esten presentes en nuestra aplicación. Es cómo la lista a los reyes magos. Este documento está mantenido por el dueño del producto, de echo mantener este documento es su principal tarea. Debe mantenerlo actualizado con las histórias que él crea conveninte y, sobretodo, lo debe mantener priorizado. El equipo tiene que saber siempre cuáles son las tareas más importantes para nuestro cliente ( es decir, que tareas tienen un mayor retorno de la inversión ) para así poder centrarse en ellas y dar el máximo valor posible en cada iteración.

Esta lista no tiene porque estar completa al principio del proyecto, de echo, dado que somos un equipo ágil y valoramos el cambio cómo una oportunidad, no nos importa que esta lista vaya variando a lo largo del proyecto. Tampoco hace falta que todos los elementos estén detallados de igual manera; los que tienen que estar más detallados són los que tengan mayor prioridad, los otros ya se irán detallando más adelante. De esta manera gastamos el tiempo del dueño del producto en las tareas más importantes a día de hoy, dejando las menos importantes para más adelante porque quien sabe si al final se van a realizar o se van a modificar.

También es importante acordar la definición de completado ( Definition Of Done - DOD). Sobre este tema, hay un montón de literatura. Cómo muestra os adjunto una imagen sacada del artículo How Do We Know When We Are Done? de lo que entiende el autor por acabado:

Como véis el autor define varios niveles de definición de acabado y es realmente detallista. Es importante tener una buena definición de acabado y que todo el mundo la tenga clara para evitar posibles desviaciones en nuestra estimación por no realizar todas las tareas necesarias.


Sprint Backlog
Es el documento resultante de la reunión de inicio de sprint. En él se detallan lo máximo posible aquellas tareas que se van a realizar en el sprint actual. Se describen con profundidad las histórias de usuario, se dividen en tareas y se estima su esfuerzo. Son estas tareas las que compondran el sprint backlog. Durante el desarrollo del sprint, cada miembro del equipo seleccionará una tarea de entre las más prioritarias cuando acabe de realizar la actual. Esta lista la tendremos que ir manteniendo dia a dia en la reunión de sprint diaria actualizando el estado de las tareas ( no empezada, empezada, acabada ) y actualizando también las horas restantes de desarrollo. Esto nos permitirá mantener los siguiente documentos que pasamos a explicar.

Tablero de scrum
Este tablero nos indica el estado actual de nuestras tareas. Vemos que se compone de una cuadrícula compuesta por filas ( las histórias del product backlog ) y columnas ( el estado de las tareas ). El equipo de desarrollo debe actualizar dicho tablero cada dia en la reunión diária y así todo el mundo puede tener una visión rápida y exacta de cómo se está desarrollando el sprint. También podemos incluir en el tablero las tareas que no estaban planeadas y que se han acabado realizando y las próximas histórias del product backlog, por si acabamos las planeadas inicialmente.
( Imágen extraída del libro Scrum y XP from the trenches de Henrik Kniberg )

Sprint Burndown Chart

En el tablero de sprint también pondremos el sprint burndown chart. Este gráfico nos indica de una manera muy visual cuál es la velocidad del equipo y nos permite vislumbrar si el equipo acabará todas las tareas en el tiempo marcado. Cada día, en la reunión de sprint, el equipo actualizará el tablero de sprint con los datos de avance de estas tareas. Estos datos de avance ( las horas que le quedan a cada tarea ) también se pondrán de manifiesto en el gráfico, haciendo una línea desde el punto de las horas que quedaban por hacer ayer y las que quedan por hacer hoy ( ya que hemos restado las que hemos echo ). Esto, si lo comparamos con la línea ideal que se dibuja desde el punto de máximas horas del dia de inicio de sprint, al punto de horas cero en el úlitimo dia de sprint nos dá información sobre si el equipo avanza a la velocidad adecuada o no. En próximos artículos ya hablaremos de cómo analizar un gráfico de avance.

Y por hoy esto es todo. Nos leemos en el próximo artículo!

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!!