jueves, 26 de septiembre de 2013

TDD


 

 

 

 

TDD

Desarrollo guiado por pruebas de software


 

 


 

 

 
 
 
 

 

 

INTEGRANTES:

ALVARO PINILLA

EDUARDO LOPEZ

ALEXIS MIRANDA

 

CURSO:

GESTION DE CALIDAD DE SOFTWARE

LABORATORIO

 

PROFESOR:

ERWIN FISCHER

 

SEPTIEMBRE 2013

 

 


Contenido










 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

1.     Introducción.


 

En el siguiente informe se investigó sobre el desarrollo guiado por pruebas de software, TDD (Test-driven development). Se explicaran los fundamentos principales de esta técnica, así como la bibliografía que hay sobre este tema, los principales libros y autores. Por otra parte se mostrará, con un ejemplo, como es la aplicación de esta práctica de programación.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

2.     ¿Qué es TDD?


 

El Desarrollo Dirigido por Tests (Test Driven Development), conocido como TDD, es una técnica de diseño e implementación de software incluida dentro de la metodología XP. TDD es una técnica para diseñar software que se centra en tres pilares fundamentales:

 

  • La implementación de las funciones justas que el cliente necesita y no más.
  • La minimización del número de defectos que llegan al software en fase de producción.
  • La producción de software modular, altamente reutilizable y preparado para el cambio.

 

TDD es realmente una herramienta de diseño que convierte al programador en un desarrollador. TDD es la respuesta a las grandes preguntas de:

 

¿Cómo lo hago?, ¿Por dónde empiezo?, ¿Cómo sé qué es lo que hay que implementar y lo que no?, ¿Cómo escribir un código que se pueda modificar sin romper funcionalidad existente?

 

No se trata de escribir pruebas  como locos, sino de diseñar adecuadamente según los requisitos.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

3.     El algoritmo TDD.


 

La esencia de TDD es sencilla pero ponerla en práctica correctamente es cuestión de entrenamiento, como tantas otras cosas.

El algoritmo TDD sólo tiene tres pasos:

 

·         Escribir la especificación del requisito (el ejemplo, el test).

·         Implementar el código según dicho ejemplo.

·         Refactorizar para eliminar duplicidad y hacer mejoras

 

De esta forma el ciclo de desarrollo conducido por pruebas queda definido de la siguiente forma:

 

Elegir un requisito: Se elige de una lista el requerimiento que se cree que nos dará mayor conocimiento del problema y que a la vez sea fácilmente implementable.

Escribir una prueba: Se comienza escribiendo una prueba para el requisito. Para ello el programador debe entender claramente las especificaciones y los requisitos de la funcionalidad que está por implementar. Este paso fuerza al programador a tomar la perspectiva de un cliente considerando el código a través de sus interfaces.

Verificar que la prueba falla: Si la prueba no falla es porque el requerimiento ya estaba implementado o porque la prueba es errónea.

Escribir la implementación: Escribir el código más sencillo que haga que la prueba funcione. Se usa la metáfora "Déjelo simple" ("Keep It Simple, Stupid" (KISS)).

Ejecutar las pruebas automatizadas: Verificar si todo el conjunto de pruebas funciona correctamente.

Eliminación de duplicación: El paso final es la refactorización, que se utilizará principalmente para eliminar código duplicado. Se hacen de a una vez un pequeño cambio y luego se corren las pruebas hasta que funcionen.

Actualización de la lista de requisitos: Se actualiza la lista de requisitos tachando el requisito implementado. Asimismo se agregan requisitos que se hayan visto como necesarios durante este ciclo y se agregan requerimientos de diseño (P. ej que una funcionalidad esté desacoplada de otra).

 

Tener un único repositorio universal de pruebas facilita complementar TDD con otra práctica recomendada por los procesos ágiles de desarrollo, la "Integración Frecuente". Integrar frecuentemente nuestro trabajo con el del resto del equipo de desarrollo permite ejecutar toda batería de pruebas y así descubrir si nuestra última versión es compatible con el resto del sistema. Es recomendable y menos costoso corregir pequeños problemas cada pocas horas que enfrentarse a problemas enormes cerca de la fecha de entrega fijada.

 

 

 

 

 

 

4.     Principales autores y libros sobre TDD.


 

Las primeras páginas del libro de Kent Beck (uno de los padres de la metodología XP) dan unos argumentos muy claros y directos sobre por qué es beneficioso convertirla en nuestra herramienta de diseño principal. Estas son algunas de las razones que da Kent junto con otras destacadas figuras de la industria:

 

·         La calidad del software aumenta.

·         Conseguimos código altamente reutilizable.

·         El trabajo en equipo se hace más fácil, une a las personas.

·         Nos permite confiar en nuestros compañeros aunque tengan menos experiencia.

·         Multiplica la comunicación entre los miembros del equipo.

·         Las personas encargadas de la garantía de calidad adquieren un rol más inteligente e interesante.

·         Escribir el ejemplo (test) antes que el código nos obliga a escribir el mínimo de funcionalidad necesaria, evitando sobre diseñar.

·         Cuando revisamos un proyecto desarrollado mediante TDD, nos damos cuenta de que los tests son la mejor documentación técnica que podemos consultar a la  hora de entender qué misión cumple cada pieza del puzzle.

 

Frecuentemente, nos encontramos con gente muy desconfiada que mira con lupa el código de su equipo antes de que nadie pueda hacer “commit” al sistema de control de versiones. Esto se convierte en un cuello de botella porque hay varias personas esperando por el jefe (el arquitecto) para que dé el visto bueno y a este se le acumula el trabajo. Cuando el jefe sabe que su equipo hace TDD correctamente puede confiar en ellos y en lo que diga el sistema de integración contínua y las estadísticas del repositorio de código.

Para el programador junior se convierte en su guía que le indica que paso tiene que dar ahora. Y así, un paso tras otro, le guía en la implementación de la tarea que le ha sido asignada.

 

El libroDiseño Ágil con TDD” de los autores Carlos Blé Jurado, Juan Gutiérrez Plaza,  Fran Reyes Perdomo y Gregorio Mena, Se puede descargar en www.dirigidoportests.com/el-libro, donde también hay enlaces para quien quiera comprar la versión impresa o leerlo online. Es uno de los libros más completos que pudimos encontrar sobre lo que es TDD, incluye temas como que entendemos como agilísimo, los tipos de test y su importancia, y varios ejercicios prácticos.

 

 

 

 

 

 

 

 

 

 

5.     Ejemplo práctico.


 

Explicando el problema.

Bien, el problema que se nos plantea es la creación de un programa que calcule el importe de una colección de libros aplicando un determinado descuento en función de los distintos tipos de libros que compremos. Partiendo de que cada libro tiene un coste de 8€ el cliente nos indica que, si se adquieren dos libros distintos, se aplique un 5% de descuento a ambos libros, un 10% si se compran 3, un 20% para 4 distintos y un 25% para la serie completa de 5 libros.

Otra condición del cliente es que el programa obtenga el precio más económico si hay varias combinaciones posibles de packs.

 

Como entrada recibiremos un array con los códigos de los libros a comprar (de 0 a 4) y como salida obtendremos un valor double.

¡Muy bien! Ya sabemos que tenemos que hacer y que quiere el cliente por lo que vamos a escribir las primeras pruebas.

Pruebas Sencillas

Lo primero que debemos hacer es escribir unas pruebas sencillas que implementen los casos más básicos. La primera y más sencilla es que cuando nuestra cesta de compra esté vacia el precio total es 0. En NUnit sería así:

[Test]

public void EmptyBasket()

{

     Assert.AreEqual(0, _bookPriceCalculator.CalculateBasket(new int[] { }));

}

Sin entrar en explicar la sintaxis de NUnit simplemente indicar que esta prueba comprueba que si se le pasa un array vacio el sistema devuelve 0.

¿Cómo resolvemos esta prueba con la menor cantidad de código posible?

Muy fácil:

public double CalculateBasket(int[] books)

{

     return 0;

}

 

Esa es la solución más sencilla que hace que la prueba sea correcta. Obviamente en cuanto añadamos una segunda prueba tendremos que rehacer nuestro código pero de esto se trata TDD.

¡Ok! Vamos ha añadir una nueva prueba para indicar una nueva condición impuesta por el cliente: Cada libro vale 8€

[Test]

public void TwoSameBook()

{

     Assert.AreEqual(8 * 2, _bookPriceCalculator.CalculateBasket(new int[] { 0, 0 }));

}

En esta prueba indicamos que si se compran dos veces el libro "0" el precio total es de 16€.

 

 

 

¿Cómo modificamos nuestro método para que pase las dos pruebas?

public double CalculateBasket(int[] books)

{

     return books.Length * 8;

}

¡Vale! No es muy elegante pero solucionaría cualquier prueba que no implique un descuento por lo que podemos cerrar el capítulo de pruebas sencillas.

 

Añadiendo Funcionalidad: Calculando Descuentos

Nuestro cliente nos había puesto como premisa que, si se compraban dos o más libros distintos había que aplicar un descuento determinado por lo que vamos a crear una serie de pruebas de ejemplo que implementen esta funcionalidad:

[Test]

public void TwoDifferentBooks()

{

            Assert.AreEqual(8 * 2 * 0.95, _bookPriceCalculator.CalculateBasket(new int[] { 0, 1 }));

}

 

[Test]

public void SeveralDiscountsTwo_One()

{

            Assert.AreEqual(8 + (8 * 2 * 0.95), _bookPriceCalculator.CalculateBasket(new int[] { 0, 0, 1 }));

}

Como veis en las pruebas tan solo declaramos una entrada de datos y la solución que nuestro cliente espera. Es trabajo del desarrollador el encontrar el modo en que el código del programa satisfaga todas las nuevas pruebas sin "romper" ninguna de las anteriores ya implementadas.

La solución que se me ha ocurrido a mi es hacer una clase "BookPack" que simbolice un "paquete de libros" donde todos los libros deben de ser distintos por lo que cada compra se traduce realmente en varios "BookPacks" cada uno con su precio y descuento.

Sin tener en cuenta la última condición (que indicaba que se ha de escoger la combinación más económica), la solución más sencilla es esta:

List<BookPack> BookPacks = new List<BookPack>();

 

foreach (int bookCode in books)

{

     BookPack currentBookPack = null;

    

     foreach (var bookPack in BookPacks)

     {

          if (!bookPack.HasThisBook(bookCode))

          {

              currentBookPack = bookPack;         

          }

     }

 

     if (currentBookPack == null)

            {

                BookPacks.Add(new BookPack(bookCode));

            }

            else

            {

                currentBookPack .Books.Add(bookCode);

            }

}

return BookPacks.Sum(bookPack => BookPack.GetBookPackPrice(bookPack.Books.Count));

Donde "GetBookPackPrice" devuelve el precio del paquete en función de cuantos libros tiene:

public static double GetBookPackPrice(int quantity)

        {

            return 8 * quantity * GetDiscountPerDifferentBooks(quantity);

        }

//GetDiscountPerDifferentBooks devuelve 1, 0.95, 0.9, 0.8 o 0.75 en función de si se compran 1, 2, 3, 4 o 5 libros distintos respectivamente.

Con esto ya tenemos implementada tanto la funcionalidad básica (ningún libro o varios libros iguales) como el descuento básico por "paquetes de libros". Sólo nos faltaría añadir la última condición del cliente: que se elija la combinación más económica a la hora de hacer los paquetes.

Pruebas Finales: Obteniendo el paquete más económico

No es lo mismo hacer dos paquetes de 4 libros (51.2€ en total) que 1 de 5 y otro de 3 (51.6€). Con el código anterior obtendríamos la opción más desfavorable ya que solo se crea un nuevo paquete cuando el libro actual ya se encuentra en el paquete actual y no se tiene en cuenta el importe final.

Para añadir esta nueva funcionalidad vamos a escribir la última prueba:

[Test]

        public void SeveralDiscountsFourFour()

        {

            Assert.AreEqual(2 * (8 * 4 * 0.8), _bookPriceCalculator.CalculateBasket(new int[] { 0, 0, 1, 1, 2, 2, 3, 4 }));

        }

La implementación para este caso consiste en cambiar el concepto "CurrentBookPack" que tan solo va rellenando paquetes por "CheapestBookPack" el cual compara entre los distintos paquetes y se queda con el más económico.

 

Como el código resultante ya es más se puede ver directamente en el repositorio de GitHub donde está guardado.


 

 

 

6.     Conclusiones.


 

Creo que este es un gran ejemplo para explicar las virtudes de TDD ya que en un mismo proyecto hemos tenido que hacer 3 modificaciones que han alterado por completo el funcionamiento de la aplicación

 

Al principio solo teníamos que calcular importes sencillos (8 * unidades), después se añadió unos descuentos por volumen para terminar con un sistema más inteligente que se queda con la combinación más económica.

 

Ahora extrapola este ejemplo a un proyecto real con cientos de clases y miles de líneas y piensa que pasaría si tuvieras que hacer este tipo de cambios... asusta, ¿verdad?

Y es que ¿cuántas veces hemos "arreglado algo y roto otro cosa"? ¿Cuantas veces los requerimientos del software cambian y debemos adaptarnos rápidamente a las nuevas necesidades? ¿Cuantas veces programamos más de lo debido?

 

TDD ayuda a solucionar estos problemas facilitándote el modo en el que organizas tus tareas. Cuando programas primero una prueba estás abstrayéndote de algoritmos y estrategias para centrarte en entender el problema y la solución exacta que quiere el cliente.

Si logras crear pruebas simples que explican una pequeña parte del código podrás tener un sistema casi perfecto cuando el producto se entregue además de ganar en flexibilidad en caso de los requerimientos cambien en medio del desarrollo.

 

Está claro que escribir pruebas antes de programar es un trabajo extra que en muchos casos no se puede asumir pero, a la larga, ahorra mucho tiempo en mantenimiento y evita problemas de código heredado.

 

TDD, como otras herramientas dentro del agilísimo, solo te da una serie de "buenas practicas" que te ayudan a mejorar tu calidad como desarrollador. Está en tu mano decidir hasta qué punto utilizar TDD

 

 

 

 

 

 

 

 

 

 

 

 

 

 

7.     Referencias.