TDD Paso a paso utilizando VSTS

Por Bruno Capuano

Contenido

 1. Introducción
 2. Creación de un componente de ejemplo
 3. Creación de un proyecto de pruebas unitarias
 4. Creación de una lista de pruebas
 5. Creación de un Team Project en Team Foundation Server
 6. Utilización del Control de versiones de código fuente
 7. Ejecución de pruebas unitarias en el Team Project
 8. Compilación de código fuente y ejecución automática de las pruebas unitarias en Team Foundation Server
 9. Conclusión
 10. Referencias

1. Introducción

Este artículo muestra cómo utilizar VSTS para la creación de un componente, un juego de pruebas unitarias, y la integración con TFS para el control de versiones y el compilado automático en un entorno TDD.

Desde hace un tiempo ya, eXtreme Programming ha comenzado a cambiar los hábitos clásicos de los equipos de desarrollo. Uno de los pilares de esta metodología se centra en el aumento de la productividad de los equipos de desarrollo. Este aumento se puede lograr (en gran parte) si los equipos trabajan utilizando una nueva técnica de trabajo denominada Test Development Driven (TDD).

Hace unos meses, configurar un entorno TDD era una tarea complicada; pero desde la llegada de Visual Studio 2005 y Team Foundation Server (TFS), esta tarea se ha simplificado muchísimo.

Luego de estar probando durante mucho tiempos las versiones Beta de estos productos, finalmente pude participar en un proyecto donde aplicamos una metodología ágil, integrados con TFS.

El objetivo de este artículo es mostrar paso a paso cómo se puede utilizar Visual Studio Team System, en un proceso de desarrollo ágil, que contemple la creación de un componente, sus pruebas y los sucesivos pasos de integración hasta el proceso de Continuous Integration.

 

2. Creación de un componente de ejemplo

Lo primero que hacemos es crear un nuevo proyecto Visual Basic Net; para eso abrimos Visual Studio y seleccionamos las siguientes opciones:

  1. Menú File | New | Project.

  2. Seleccionamos Visual Basic, y dentro la opción Windows; el tipo de proyecto es Class Library.

  3. Definimos el nombre del proyecto; a modo de ejemplo lo llamamos TDDEjemplo (Ver Figura 1):

    Bb972286.art292-img01-570x409(es-es,MSDN.10).gif
    Figura 1: Creación de un proyecto Visual Basic .Net. Volver al texto.

Dentro de nuestro proyecto renombramos la clase con el nombre Labs y agregamos el siguiente código de ejemplo:

Public Class Labs 
 
    ''' <summary> 
    ''' Obtiene un mensaje de informacion 
    ''' </summary> 
    ''' <returns>Obtiene un mensaje de informacion</returns> 
    Public Function ObtenerInformacion() As String 
        Return "Hola Mundo" 
    End Function 
 
    ''' <summary>
    ''' Retorna el nombre completo a partir de un Nombre y un Apellido 
    ''' </summary> 
    ''' <param name="Nombre">Nombre</param> 
    ''' <param name="Apellido">Apellido</param> 
    ''' <returns>Retorna el nombre completo a partir de un Nombre y un Apellido</returns> 
    Public Function CrearNombreCompleto(ByVal Nombre As String, ByVal Apellido As String) As String 
        Return Nombre & ", " & Apellido 
    End Function 
 
End Class 

Hemos agregado 2 funciones: ObtenerInformacion y CrearNombreCompleto, que utilizaremos para integrar nuestro proyecto de pruebas en un proceso TDD.

 

3. Creación de un proyecto de pruebas unitarias

El siguiente paso es crear un proyecto que nos permita probar la funcionalidad de TDDEjemplo. Para eso crearemos una serie de pruebas unitarias (Unit Tests), que servirán como base para evaluar el funcionamiento correcto de las funciones que creamos en el paso anterior. Posicionamos el cursor en la línea de definición de una de las funciones, desplegamos el menú contextual, y seleccionamos la opción Create Unit Tests (Ver Figura 2):

Bb972286.art292-img02-521x319-0(es-es,MSDN.10).gif
Figura 2: Creación de las pruebas unitarias. Volver al texto.

Nota: Otra de las opciones que poseemos es la opción Rename; esta opción nos permite renombrar el nombre de la función y reemplazar este nombre en todos los lugares donde se utiliza la función. Esta acción es un caso de Refactoring.

El siguiente formulario nos permite seleccionar las funciones sobre las cuales queremos crear las pruebas unitarias. En este caso seleccionamos ambas funciones (Ver la Figura 3):

Bb972286.art292-img03-459x295(es-es,MSDN.10).gif
Figura 3: Selección de funciones para las pruebas unitarias. Volver al texto.

Las funciones con las pruebas unitarias se crean en un nuevo proyecto, en la opción para definir el nombre ingresamos TDDEjemploTest. Esta opción nos agrega un nuevo proyecto a la solución y le agrega como referencia el proyecto TDDEjemplo; además, agrega dentro de la solución una serie de nuevos elementos (Ver la Figura 4):

Bb972286.art292-img04-203x231(es-es,MSDN.10).gif
Figura 4: Solución con el nuevo proyecto de pruebas. Volver al texto.

En el nuevo proyecto de pruebas se ha creado la clase LabsTest.vb, que posee las siguientes pruebas unitarias:

Clase Labs.vb Clase LabsTests.vb
ObtenerInformacion ObtenerInformacionTest
CrearNombreCompleto CrearNombreCompletoTest

Las nuevas funciones utilizan la librería Microsoft.VisualStudio.QualityTools.UnitTestFramework, que ha sido incluida dentro de la suite de Visual Studio 2005 para la ejecución de pruebas unitarias. El asistente crea las nuevas funciones implementando una llamada a la función que se quiere probar y generando el código necesario para el mismo.

Por ejemplo, la función CrearNombreCompletoTest incluye la definición de las variables Nombre y Apellido; adicionalmente se agrega como tarea inicializar los valores de estas variables.

    '''<summary>
    '''A test for CrearNombreCompleto(ByVal String, ByVal String) 
    '''</summary> 
    <TestMethod()> _ 
    Public Sub CrearNombreCompletoTest() 
        Dim target As Labs = New Labs 
        Dim Nombre As String = Nothing 'TODO: Initialize to an appropriate value 
        Dim Apellido As String = Nothing 'TODO: Initialize to an appropriate value 
        Dim expected As String = Nothing 
        Dim actual As String 
        actual = target.CrearNombreCompleto(Nombre, Apellido) 
        Assert.AreEqual(expected, actual, "TDDEjemplo.Labs.CrearNombreCompleto did not return the expected value.") 
        Assert.Inconclusive("Verify the correctness of this test method.")     End Sub 

Para poder probar esta función completamos los valores de estas variables y también el de la variable expected, que almacena el valor esperado para luego compararlo con el resultado de la ejecución de la prueba.

El código final quedaría tal como se muestra en el siguiente ejemplo:

    '''<summary>
    '''A test for CrearNombreCompleto(ByVal String, ByVal String) 
    '''</summary> 
    <TestMethod()> _ 
    Public Sub CrearNombreCompletoTest() 
        Dim target As Labs = New Labs 
 
        Dim Nombre As String = "Bruno" 
        Dim Apellido As String = "Capuano" 
        Dim expected As String = "Bruno, Capuano" 
        Dim actual As String 
 
        actual = target.CrearNombreCompleto(Nombre, Apellido) 
 
        Assert.AreEqual(expected, actual, "TDDEjemplo.Labs.CrearNombreCompleto did not return the expected
 value.") 
        
    End Sub 
 
    '''<summary> 
    '''A test for ObtenerInformacion() 
    '''</summary> 
    <TestMethod()> _ 
    Public Sub ObtenerInformacionTest() 
        Dim target As Labs = New Labs 
 
        Dim expected As String = "Hola Mundo" 
        Dim actual As String 
 
        actual = target.ObtenerInformacion 
 
        Assert.AreEqual(expected, actual, "TDDEjemplo.Labs.ObtenerInformacion did not return the expected
 value.") 
 
    End Sub 

Nota: el generador de pruebas unitarias agrega, además de la prueba de la función, la sentencia Assert.Inconclusive(...) para verificar que se complete el código de la prueba unitaria; esta línea debe ser eliminada luego de implementada la prueba unitaria, ya que la misma no retornará un resultado exitoso si se deja esta llamada.

Una vez que hemos completado la definición de las pruebas unitarias podemos ejecutar las mismas para evaluar si nuestro componente TDDEjemplo.Labs funciona correctamente. Para lanzar el proceso de ejecución de pruebas unitarias debemos seleccionar menú Test | Start Selected Test Project with Debugger.

Si no hemos cometido ningún error y las pruebas validan esta definición, el resultado debe ser exitoso y lo podremos visualizar en la ventana de Test Results (Ver la Figura 5):

Bb972286.art292-img05-512x257(es-es,MSDN.10).gif
Figura 5: Ejecución exitosa de las pruebas unitarias. Volver al texto.

Si en cambio hemos tenido algún error o hemos dejado la llamada Assert.Inconclusive(...), podremos ver alguno de los siguientes resultados (Ver la Figura 6):

Bb972286.art292-img06-570x270(es-es,MSDN.10).gif
Figura 6: Ejecución exitosa de las pruebas unitarias. Volver al texto.

 

4. Creación de una lista de pruebas

Lo más probable es que nuestros proyectos no estén compuestos por 2 funciones, y con el paso del tiempo nuestras pruebas unitarias crecerán de la mano del tamaño de nuestros proyectos. Para ayudarnos en la organización de las pruebas existe el Administrador de Pruebas (Test Manager) que permite crear contenedores (listas) de pruebas. Estas pruebas se asocian a una solución en archivos con extensión *.vsmdi (Visual Studio Test Metadata File).

En nuestro proyecto poseemos un archivo de pruebas (TDDEjemplo.vsmdi); para editarlo, lo seleccionamos en el Solution Explorer y lo abrimos. La lista de pruebas se encuentra vacía por defecto. Para crear una nueva lista, seleccionamos el elemento List of Tests, desplegamos el menú contextual, y seleccionamos la opción New Test List.

A continuación debemos asignar el nombre y descripción (opcional) de la nueva lista, ponemos Pruebas Completas como nombre. Para asignar las pruebas a la lista debemos:

  1. Seleccionar el nodo All Loaded Tests; se muestran todas las pruebas que contiene la solución.

  2. Seleccionar las pruebas con las que queremos trabajar.

  3. Arrastrar hasta el nodo Pruebas Completas.

Nuestra lista Prueba Completas debe quedar tal como se muestra en el siguiente ejemplo (Ver la Figura 7):

Bb972286.art292-img07-551x216(es-es,MSDN.10).gif
Figura 7: Asignación de pruebas a una lista. Volver al texto.

Para ejecutar los Tests de la lista, seleccionamos las pruebas que deseamos y presionamos el botón Run Checked tests de la toolbar en el Test Manager. Esta ejecución integra las pruebas unitarias que hemos asignado a la lista y además permite la opción de ejecutar estas pruebas en modo debug o en modo stand alone.

Nota: Para cada lista de pruebas, podemos definir diferentes opciones, como por ejemplo los campos que queremos mostrar, o también podemos exportar la definición de la lista. Estas opciones se encuentran en la toolbar del Test Manager.

 

5. Creación de un Team Project en Team Foundation Server

Haciendo un pequeño resumen, hasta este momento hemos:

  • Creado un componente.

  • Creado un proyecto que implementa las pruebas unitarias para el componente.

  • Creado una lista de pruebas para organizar las mismas.

Sin embargo, en determinado momento nuestro pequeño componente pasará a formar parte de un proyecto mayor y debemos integrar nuestro output dentro de un proceso más amplio de desarrollo. Aquí es donde entra en juego TFS.

Con Team Foundation Server (TFS), los equipos de desarrollo tienen una herramienta que les permite realizar tareas colaborativas basadas en el servidor, al tiempo que evalúan el desarrollo y la salud de sus proyectos.

Inicialmente, en nuestro equipo, una persona será la encargada de la creación y administración de los Team Projects en TFS. En este caso, veremos los pasos necesarios para la creación de un Workspace que implemente la metodología MSF for Agile Software Development – MSDN.

Para conectarnos a un server TFS seguimos los siguientes pasos:

  1. Menú Tools | Connect to Team Foundation Server.

  2. Presionamos el botón Servers.

  3. Presionamos el botón Add.

  4. Ingresamos el nombre o la dirección IP de nuestro servidor TFS, definimos el Puerto (por defecto 8080) y presionamos OK.

  5. En la lista de servidores podremos ver una nueva entrada asociada a nuestro servidor.

  6. Desde el combo de servers, seleccionamos el servidor que hemos agregado y presionamos OK.

  7. Si el panel Team Explorer no está visible, seleccionamos menú View | Team Explorer, para desplegar el panel Team Explorer.

  8. Seleccionamos el server en el Team Explorer y desplegamos el menú contextual, luego seleccionamos la opción New Team Project.

  9. Definimos el nombre del Team Project, en este caso lo llamaremos LabsBruno.

  10. Seleccionamos la plantilla con la que se creará la estructura de nuestro Team Project, en este caso utilizaremos la plantilla MSF for Agile Development – v4.0.

  11. Completamos los datos para el portal de SharePoint que se creará asociado a nuestro Team Project. Podemos ver en la parte inferior, la url con la que podremos acceder a nuestro portal.

  12. Definimos el tipo de control de versiones de código fuente. En este caso seleccionamos la opción Create an empty source control folder.

  13. Verificamos las opciones que hemos seleccionado y presionamos el botón Finish. En este momento comienza la creación de los elementos de nuestro Team Project.

    Nota: El proceso de creación suele tardar un par de minutos debido a que crea diferentes elementos en el server: un portal de Sharepoint, una serie de reportes en Reporting Services, el repositorio de código fuente, etc.

  14. A continuación podremos ver en el Team Explorer nuestro nuevo Team Project con los elementos dentro del mismo; y podremos navegar el portal asociado al mismo seleccionando el proyecto, desplegando el menú contextual y seleccionando la opción Show Project Portal.

Nuestro nuevo proyecto posee una serie de tareas predefinidas para la configuración inicial del mismo. Se puede acceder a las mismas a través del portal o desde el Team Explorer, en la sección Documents | Project Managment | Project CheckList.xls.

Nota: Existe una herramienta que nos permite configurar los permisos de acceso al Sharepoint, Reporting Services, y Team Foundation Server desde una misma interfaz, la podemos encontrar en www.codeplex.com, Team Foundation Server Administration Tool.

 

6. Utilización del Control de versiones de código fuente

Una de las grandes prestaciones de Team Foundation Server es que, mientras controla el ciclo de vida de un proyecto, asocia tareas y requerimientos a versiones especificas del código de nuestra aplicación (Source Control). Para agregar nuestra solución TDDEjemplo a TFS, seleccionamos el nodo de la solución en el Solution Explorer, desplegamos el menú contextual y seleccionamos la opción Add Solution to Source Control.

Nota: Si dentro del TFS existe más de un Team Project, debemos elegir el proyecto en el cual queremos agregar la solución.

A continuación podremos acceder al controlador de versiones de código fuente haciendo doble clic sobre el nodo Source Control en el Team Explorer y podremos ver nuestra solución en estado agregado.

Para hacer un Check In de la solución, seleccionamos Check In desde el menú desplegable de la solución en el Solution Explorer. En TFS existen grandes cambios con respecto a los conceptos de Check In y Check Out de archivos, con respecto a Visual Source Safe (VSS). Team System posee el concepto de Workspaces. El servidor está conciente en todo momento del trabajo realizado en el Workspace, sabe qué directorios locales se utilizan para ese Workspace, qué versiones de archivos existen y qué cambios pendientes se han informado al servidor. Para conseguir la última versión de un archivo, se debe llamar explícitamente a Get Latest Version (esto es una gran diferencia con VSS).

Cuando se hace un Check Out de un archivo, en realidad se le dice al server que se está por editar una versión especifica de ese archivo; en este momento se puede elegir 1 de 3 opciones:

  • Lock None: el archivo no se bloquea, alguien dentro del equipo de trabajo puede editar el mismo archivo y subir los cambios al server. Luego, al momento de subir los cambios locales, se deberá resolver el conflicto de versiones si es necesario.

  • Lock Check Out: el archivo se bloquea en el server; es decir, ninguna otra persona dentro del equipo tiene acceso a modificar este archivo (igual que en VSS).

  • Lock Check In: el archivo no se bloquea en el server, pero no se permiten modificaciones al mismo hasta que la persona que ha hecho el Lock Check In suba su versión.

En cambio cuando se hace in Check In de un archivo, los cambios son más completos; podemos obligar a que antes de subir un archivo se ejecute un análisis del código, se ejecuten pruebas unitarias, etc. Además, podemos asociar cada una de estas subidas a una tarea previamente asociada a nuestro plan de trabajo y, de esta manera, controlar los cambios que implican cada tarea.

Además del Check In y el Check Out, en TFS existe el Shelving. Esta opción permite “subir” archivos al TFS pero sin la necesidad de marcar los mismos en estado Check In; de esta manera podemos subir el trabajo diario al repositorio de archivos, pero el mismo no será parte de las compilaciones diarias (este tema lo veremos más adelante).

 

7. Ejecución de pruebas unitarias en el Team Project

MSBuild es el nuevo sistema de generación para Microsoft y Visual Studio; utiliza un mecanismo extensible para describir los pasos de una generación. Utilizando MSBuild es posible crear un sistema propio de generación mediante tareas personalizadas escritas en formato Xml.

Podemos utilizar un asistente dentro de VSTS para crear un proyecto MSBuild, en el que compilemos y ejecutemos las pruebas unitarias asociadas a nuestro componente. Para esto debemos:

  1. Seleccionar el nodo Team Builds desde el Team Explorer, desplegar el menú contextual y seleccionar la opción New Team Build Type.

  2. El primer paso del asistente nos solicita el nombre y la descripción; en este caso ponemos como nombre TDD Ejemplo Build and Test.

  3. El segundo paso solicita las soluciones que queremos incluir en el proyecto; en este caso seleccionamos la solución TDDEjemplo.

  4. El tercer paso solicita la configuración de las soluciones que se aplicarán al momento de compilar las mismas; en este caso dejamos los valores por defecto: Configuration: Release; y Platform: Any CPU.

  5. El cuarto paso solicita la ubicación del Server de compilación, el directorio de compilación y un recurso compartido donde se moverá el resultado del test. Para que las pruebas se ejecuten correctamente es necesario contar un con server de compilación (TFSBuild) dentro de la arquitectura de TFS, asimismo este server debe estar configurado para la compilación y ejecución de pruebas unitarias.

Nota: Este punto es importante ya que la correcta configuración de los directorios y los permisos sobre los mismos define el comportamiento del proyecto de MSBuild.

  1. El quinto paso define si además de compilar la solución se ejecutarán las pruebas unitarias; en este caso seleccionamos la opción Run Test y podemos elegir dentro de los archivos .vsmdi de la solución, los diferentes test lists que queremos incluir. Seleccionamos Prueba Completas.

  2. El último paso muestra un resumen de las opciones que hemos seleccionado, presionamos Finish y dentro del Team Explorer se creará un nuevo ítem llamado Pruebas Completas.

Podemos editar y ejecutar este Team Build haciendo doble clic sobre el mismo en el Team Explorer y seleccionando la opción Build de la toolbar superior; y seleccionando las opciones que corresponden al build.

Dependiendo de la complejidad y recursos que utilice la solución, el proceso de Build and Test suele ser lento; aunque por lo general los pasos internos del mismo consisten en:

  • La obtención de los archivos de la solución para la compilación.

  • La ejecución de las pruebas unitarias.

  • El deploy final de los assemblies.

  • El deploy del log de ejecución al path de salida.

Una vez finalizado podemos ver en el mismo los resultados de la ejecución; si existe un error podemos revisar el log y verificar cuál fue la excepción que lo provocó. Además podemos revisar en el histórico de ejecución de pruebas la correspondiente a nuestro Test Build.

Dentro del directorio de compilación que hemos seleccionado podemos ver las diferentes compilaciones que se corresponden a nuestro Team Build, y también podemos revisar los assemblies y los logs de compilación y ejecución de pruebas unitarias.

 

8. Compilación de código fuente y ejecución automática de las pruebas unitarias en Team Foundation Server

Una de las grandes ventajas de poseer un server dedicado a la compilación y ejecución de pruebas unitarias es que la capacidad de integración continua; la misma se puede implementar muy fácilmente utilizando VSTS.

Como vimos hasta ahora, podemos integrar diferentes equipos de trabajo bajo un mismo Team Project; y podemos consolidar el trabajo de estos equipos a través de pruebas unitarias. Supongamos que el Equipo A genera una serie de componentes y su respectivo set de Tests; y el Equipo B utiliza los componentes generados por el Equipo A para producir sus componentes y además genera su propio ciclo de test.

Un trabajo manual que un encargado del Equipo B debería hacer diariamente es validar la integridad de los componentes que entrega el Equipo A; y su integración con los componentes de su equipo. Esta tarea se puede realizar fácilmente dentro del servidor de compilación utilizando un proyecto MSBuild. Pero podemos optimizar un poco más el proceso automatizando la ejecución de este proyecto; para eso podemos crear un archivo Bat que internamente llame al proyecto MSBuild utilizando el ejecutable TFSBuild.exe, como en el siguiente ejemplo:

"c:\Program Files\Microsoft Visual Studio 8\Common7\IDE\TfsBuild.exe" start BuildServer LabsBruno "TDDEjemplo
 Build and Test"

Podemos ver que este ejecutable recibe los siguientes parámetros:

  • Start: Se utiliza para ejecutar un tipo de generación de Team Foundation Build configurado.

  • BuildServer: es la dirección url del servidor de control de código fuente Team Foundation donde se protegen las soluciones que se están generando.

  • “TDDEjemplo Build and test”: Es la generación que se desea utilizar para la generación.

Luego podemos configurar una tarea programada de Windows para que se ejecute este Bat diariamente a una especificada hora, como se muestra en la Figura 8:

Bb972286.art292-img08-570x169(es-es,MSDN.10).gif
Figura 8: Tarea programada. Volver al texto.

La Figura 9 muestra cómo se ha ejecutado diariamente a las 08:00 AM, durante una semana, la generación “TDDEjemplo Build and Test”:

Bb972286.art292-img09-542x307(es-es,MSDN.10).gif
Figura 9: Ejecución programada diariamente. Volver al texto.

Utilizando este esquema podemos automatizar la compilación y ejecución de pruebas unitarias y de esa manera asegurar una calidad de trabajo en un esquema diario.

 

9. Conclusión

A diferencia de lo que muchos piensan, Visual Studio Team System es mucho más que la evolución de una herramienta para el desarrollo de soluciones. Utilizando VSTS podemos integrar a todas las partes del ciclo de vida de una solución.

VSTS posee 3 productos diferentes: VSTS para Arquitectos, que contiene capacidades como modelado y diseño; VSTS para Desarrolladores, que incluye herramientas para la programación y el análisis de código; y VSTS para Testers, que incluye herramientas para la creación y ejecución de pruebas unitarias y de carga. Además, está el Team Foundation Server como parte de VSTS, dedicado a soportar todo el esquema de integración de estas partes.

 

10. Referencias

Visual Studio Team System Developer Center

Team Foundation Server

MSF for Agile Software Development - MSDN

MSF for CMMI Process Improvement - MSDN

Microsoft Solution Framework official site

Team Foundation Server

Using Source Control Explorer

MSBuild

Comandos de Team Foundation Build (TfsBuild.exe)

Bb972286.bruno_capuano(es-es,MSDN.10).gif

Bruno Capuano trabaja como Senior Solution Developer en una empresa de consultoría y desarrollo en tecnologías Microsoft, donde se especializa en arquitecturas y soluciones .net; posee más de 6 años de experiencia en tecnologías Microsoft y durante más de 5 años trabajó en una importante empresa de tecnología de Córdoba, Argentina, donde participó en importantes proyectos en Estados Unidos, Chile y México, y alcanzando el puesto de Chief Developer Officer. Actualmente reside en Madrid, España, y participa activamente en las comunidades, eventos y grupos de usuarios Microsoft.