lunes, 11 de junio de 2007

¿Qué es TDD?

Jesús Reina Carvajal [jesusrc@yahoo.com]

TDD son las siglas en inglés de Test-Driven Development, o sea, desarrollo orientado a pruebas. En lo adelante escribiré tests. La idea es bastante sencilla: primeramente se definen los test cases y los métodos que los ejecutarán; luego se escribe el código ejecutivo.

En mi más reciente proyecto he trabajado sobre algunas de las bases de TDD en .NET utilizando herramientas gratis al servicio de toda la comunidad. Una de ellas -quizá la más utilizada hoy en día- es NUnit .Como escribí en mi artículo anterior, muchas de las soluciones que hoy vemos en .NET han sido importadas o copiadas de la comunidad Java. Hoy no estoy tratando ninguna excepción de esta regla y tenemos que la plataforma que dio origen a NUnit es conocida como JUnit. Pues bien, ¿qué es NUnit? Simplificando un poco podemos afirmar que NUnit es una plataforma para tests, independiente del lenguaje en que se programe, siempre que sea .NET.

Hablando hipotéticamente, para hacernos una idea de la utilidad que reporta el TDD pongamos que en cierto proyecto tenemos que realizar una clase para enviar y/o recoger ficheros a/de un determinado servidor FTP. Primeramente definiremos los tests de esa clase que llamaremos Ftp. La clase que ejecutará los tests la llamaremos FtpTester.

¿Cuáles son los métodos que debemos comprobar? Inicialmente tendremos que crear la clase (constructores) y leer la configuración, cargar (upload), descargar (download), leer la lista de ficheros del servidor, cambiar catálogo, etc. Todo en dependencia de la funcionalidad que vayamos a implementar.

La imagen a continuación nos muestra parte de la clase FtpTester que comprueba la clase Ftp.
Tenemos el método privado pGetServer() que cada vez que es ejecutado nos devuelve una nueva instancia de la clase Ftp, configurada por los valores de un fichero .config. Luego, esa instancia es la que es utilizada por cada uno de los métodos que comprueban la funcionalidad de la clase Ftp. Por ejemplo, el método ConstructorTest() crea una instancia de la clase Ftp y la guarda en la variable local ftp. Luego llama el método GetFileList() que, justo en este caso, es innecesario, ya que no es lo que queremos comprobar. Finalmente hacemos un llamado al método IsInstanceOfType() de la clase Assert para asegurarnos de que la variable ftp verdaderamente contiene una instancia de la clase Ftp. Entonces tenemos que ConstructorTest() ejecutará satisfactoriamente si y sólo si la clase Ftp puede ser instanciada y si la variable ftp es del tipo Ftp.

Una vez que NUnit ha cargado la biblioteca (assembly) a comprobar, el programa busca todos los tags de tipo [Test(***)] y los ejecutará cuando, a través del interfaz gráfico pulsemos la tecla Run. Existe otra posibilidad de ejecutar los tests usando la línea de comando (nunit-console.exe) a la cual retornaré en un artículo venidero.

Los que son observadores habrán notado que los métodos de test marcados por el tag [Test(***)] terminan con un llamado a la clase Assert. Esta clase es parte de la plataforma NUnit y es utilizada precisamente para darle a conocer a NUnit el resultado del test. En dependencia de ello, NUnit coloreará la barra de progreso. Aquí voy a incluir un par de imágenes de mi biblioteca de funciones para que vean el resultado del ejemplo arriba ilustrado.

Este es el resultado de los tests de la clase Ftp:

Aquí veremos cómo NUnit nos muestra los tests que han fallado. Esta es una clase que comprueba mucha de la funcionalidad de Reporting Services, un paquete extra para SQL Server 2000, pero parte de la instalación estándar de SQL Server 2005. Trataré, en la medida de mis posibilidades, de retornar a Reporting Services en un próximo artículo.

A pesar de su versatilidad, NUnit tiene un talón de Aquiles: los métodos que son ejecutados asincrónicamente. No dudo que a partir de una equis versión de NUnit este problema sea solucionado, pero de momento esto requiere de esfuerzos extras por parte del programador. Es algo que he logrado hacer pero admito que no es fácil ni recomendable, sobre todo cuando tenemos varias capas en el stack de ejecución y varios hilos ejecutando a la vez.

Como hemos podido ver, es relativamente fácil hacer tests para casi la totalidad de las clases que desarrollemos. Un paso más en la optimización del proceso es automatizar los tests y builds con generación de documentación actualizada y todo.

Ahora, ¿a qué altura poner la barra? Podremos ponerla bien alta y ejecutar los tests atómicamente. Esto es recomendable para organizaciones con suficientes recursos, y varios programadores que puedan distribuir los tests y el código ejecutivo entre sí. Para organizaciones más pequeñas habrá que llegar a compromisos, indudablemente.

¿Cómo saber si los tests son suficientes entonces? ¡La pregunta de los cien pesos! Con un colega arquitecto llegamos a la conclusión de que todo método público accesible desde el interfaz gráfico o desde el nivel más alto debe ser comprobado, lo cual a su vez comprobará todos los métodos a niveles inferiores que sean consumidos por éste.

La principal ventaja de TDD sobre el desarrollo a ciegas -como yo le llamo-, es que cada cambio en el código puede ser comprobado de inmediato antes de hacer un check-in, eliminando la posibilidad de romper la compilación del sistema.

Lo anterior supone un cierto nivel de disciplina por parte de todos los miembros del proyecto y de la organización, pues, como sabemos, la anarquía no funciona.

Para saber más...



Artículos relacionados


No hay comentarios: