miércoles, 29 de septiembre de 2010

Toda la verdad acerca de los Tests de Unidad

Porque son tan importantes los tests de unidad?

Uno podria preguntarse que hay detrás de taaaanto rollo acerca de una de las prácticas fundamentales tanto de XP como de toda lista de Buenas Prácticas de Programación. Porque es tan importante hacer tests de unidad?. Este posteo intenta justamente responder a esa pregunta, explicando detalladamente los beneficios principales que se obtienen al hacer tests de unidad.

Esta lista no intenta ser extensiva, ni se obtienen solo al hacer Test Driven Development, TDD, ya que algunos de los beneficios se obtienen también haciendo tests de unidad "PostMorten", es decir, luego de escribir el código.

Vamos, primero, a la enciclopedia galactica:

Tests de unidad: son los tests que un programador debe escribir para testear que su código hace lo que él entendió que se le solicitó que haga, a través del documento de especificación funcional, sea una historia de usuario, caso de uso o conversación con usuarios.

Hacemos esta distinción para diferenciarlos de los tests de aceptación que son aquellos tests de más alto nivel que deben verificar que la funcionalidad desarrollada es aceptada de acuerdo a lo definido en el relevamiento con los usuarios.

Por otro lado cuando decimos que se hacen tests de unidad o TDD estamos asumiendo que se realizan correctamente y con un cubrimiento del total de código del modelo (en un framework MVC) escrito de más del 80%.

Para aquellos que quieran hacer el salto más significativo en el desarrollo de software desde la invención de la Programación Orientada a Objetos (y no estamos exagerando), los beneficios son:

El beneficio global primero y quizás más importante es que el conjunto de tests de unidad funcionan como una malla de seguridad que permite hacer cambios al código de una manera segura y órdenes de magnitud más rápida que cuando no tenemos tests. Y esto para un mundo no perfecto como el que vivimos en el cual los usuarios/la realidad/Lo-que-Sea piden cambios de funcionalidad constantemente no es poca cosa.

En detalle, los beneficios que nos proveen los tests de unidad son incontables (como los beneficios de la soja ;) :

  1. Los tests de unidad verifican que el código en realidad funciona (de acuerdo a lo que entendió el programador, vale la pena aclararlo) lo cual significa que vamos a tener menos bugs y errores.
  2. TDD fuerza a pensar en los contratos de clases y métodos antes de escribir el código, lo cual lleva a un diseño mejor y mucho más preciso de la solución al problema planteado.
  3. Los tests de unidad hacen posible mejorar iterativamente el diseño del sistema sin romperlo. Como contamos con un conjunto de tests de unidad que verifican todo lo que se puede romper, podemos aplicar mejoras al diseño de manera incremental a través de pequeños pasos de refactoring y luego verificar si funciona corriendo todos los tests. Ante un error podemos fácilmente determinar que parte del nuevo código rompió el sistema y corregirlo.
  4. No reemplazan pero complementan muy bien a los tests de aceptación y de integración.
  5. En si sirven como un conjunto de tests de regresión de bajo nivel.
  6. Funcionan como documentación y Reduce el tiempo necesario para entender el código escrito. En general nos olvidamos muy rápidamente de lo que hace el código que escribimos hace apenas unos días atrás (mas de una vez ha pasado que miramos el código asombrados pensando en el... espécimen que habrá escrito semejante zafarrancho para terminar dándonos cuenta que fuimos nosotros mismos.... y no, no digan que nunca les paso). Imaginen además cuando tenemos que entender código escrito por otros. Los tests de unidad funcionan como perfectos ejemplos de utilización del código, documentan la utilización correcta del cada método no trivial y de la relación entre las clases. Y a diferencia de la documentación no quedan desactualizados al instante porque en cuanto eso sucede uno o varios del 100% de los tests de unidad comienzan a fallar y debemos arreglarlo (o el test o el código).
  7. Reduce el costo de mantenimiento. En Lean Software Development se define el costo de un bug como el tipo de criticidad del error (bajo, medio, alto), multiplicado por el tiempo en que permanece no detectado. Tener un buen conjunto de tests de unidad permite reducir drásticamente el tiempo que lleva detectar un bug, y corregirlo. A la vez, si uno desarrolla el sistema haciendo TDD se introducirán muchos menos errores y además cuando se sucede se detectan casi inmediatamente, el error esta en lo ultimo que se cambió.
  8. Nos sacan de un viejo círculo vicioso del desarrollo de software: como tenemos miedo de realizar cambios en la estructura de un programa, hacemos las modificaciones imprescindibles mediante parches, lo que causa que el programa sea mas díficil de modificar, lo que causa que nos de miedo realizar cambios estructurales...
  9. TDD nos fuerza a a usar Pasos de Bebé y por lo tanto a pasar menos tiempo en el debugger.

Una de las objeciones principales que se hacen en contra de escribir tests de unidad es que como es más cantidad de código que hay que escribir entonces esto va a hacer que el desarrollo vaya más lento, verdad?. Esto sería verdad si fuera que nosotros, los programadores, somos una especie de taquígrafos que nos pasamos la mayor parte del tiempo tipeando en un teclado, y entonces la cantidad de teclas por minuto se contaría como productividad. Pero esto no es asi, nos pagan por pensar, por diseñar y evolucionar una solución a un dominio de problemas. Esta comprobado que la mayor parte del tiempo un programador la pasa pensando como modelar una solución y, sobre todo, buscando errores y bugs. Escribir tests de unidad sirve justamente para hacer estas dos actividades mucho mas eficientes.

Como conclusión, y aunque sea antiintuitivo, escribir tests de unidad lleva menos tiempo total que no escribirlos.

lunes, 20 de septiembre de 2010

"Pasos de Bebe", El fin del Debugging?

Baby Steps

Mirando a nuestro hijos aprender a caminar es fascinante. En un momento están agarrados de un sillón, una silla o de nuestras piernas y de repente comienzan a dar el primer paso, pequeño, muy cercano, y luego otro, tanteando con un pie hasta estar firme y luego , tambaleantes, llevan el otro pie al lado. Y así. Con el instinto que la madre naturaleza les dio, nunca intentan empezar a correr o dar zancadas.

Alla Lejos y hace tiempo

Hace muchos años, en nuestros primeros trabajos (en esa época teníamos que fabricar nuestros propios bytes :), nuestra forma de trabajo, a grandes rasgos, era algo asi:

1. Escribir un montón de código.
2. Compilar el código escrito y corregir los errores (no, no habia intellisense).
3. Probar y debuguear el código una y otra vez hasta que funcione.

Cada una de estas etapas tomaba días o incluso semanas. Cuando nos preguntaban como venía el proyecto, la respuesta típica era "ya terminé de escribir el código, me falta probarlo". Si tenías suerte, nadie te preguntaba cuanto tiempo de trabajo te faltaba...

El problema justamente es que luego de incluir en el proyecto una cantidad de lineas de código producida durante varios días, no hay forma de predecir cuanto tiempo llevará terminar el punto 3. Y ni siquiera estamos hablando de que este punto implicaba realizar "casos de prueba" exhaustivos, este punto solo significaba que lograbamos hacer que el código funcionara en los escenarios más comunes (con suerte).

Esta forma de encarar el desarrollo es un resultado natural de toda la corriente de Waterfall muy en boga en esos días. Si lo más productivo es primero hacer todo el análisis, después todo el diseño y después toda la codificación, tiene sentido dividir este último paso en etapas también.

Con el tiempo, y luego de muchos golpes, y algunas pesadillas, empezamos a trabajar de una forma bastante distinta:

1. Escribir 3 o 4 líneas de código.
2. Compilarlo.
3. Probarlo.

La gran diferencia es que generalmente los pasos 2 y 3 no llevan nada de trabajo, ya que somos capaces de escribir 3 líneas de código sin equivocarnos o cometiendo sólo errores obvios y luego probar que funcionan esas 3 o 4 lineas nuevas es algo manejable y en general muy sencillo de realizar(de hecho, programando así casi no se usa el debugger, vale la pena remarcarlo: Casi no se usa el Debugger, y ni les cuento si esas 4 lineas de código se escribieron a partir de un test, haciendo TDD).

Esta forma de trabajar fue descrita como una buena práctica de desarrollo por varios referentes de nuestra profesión, la mayoría proveniente de las metodologías ágiles, y se le dio el nombre de "Pasos de Bebe", "Baby Steps", en ingles. Recientemente se la describió con otro nombre mucho más técnico, "Desarrollo Nano Incremental".

Ahora, cuando nos preguntan como va el proyecto, generalmente la respuesta es del tipo "la aplicación ya puede hacer X e Y, falta que haga Z y terminamos". Además el tiempo total que lleva hacer las cosas es mucho menor, ya que los pasos 2 y 3 llevaban semanas (y a veces un tiempo indeterminado) en la forma de trabajo anterior, llamemosla, para ponerle un nombre igual de rimbombante: "Big Bang de Código".

¿Por qué se da esto?

En primer lugar, porque el desarrollo de software es complejo y nosotros los humanos no podemos manejar toda la complejidad de golpe si no la atacamos dividiendola en "trozos" manejables (Divide y Venceras, dice el dicho). Somos capaces de hacerlo bien para cambios pequeños, pero cuanto más grande es el salto que queremos dar, más probabilidades tenemos de cometer errores, que después llevan mucho tiempo corregir.
Esto se nota mucho cuando utilizamos TDD, donde escribir tests demasiado grandes nos lleva a cometer muchos errores e incluso produce que uno no pueda avanzar (esta fue una de las cosas que notamos en el último Code Retreat).

En segundo lugar, tomar pasos pequeños nos permite aprender de nuestros errores. El conocimiento que obtenemos en cada ciclo es utilizado en el siguiente, que tiene lugar unos minutos después. En cambio cuando se utiliza el "Big Bang de Código", las cosas que se aprenden se pueden utilizar recién en el siguiente proyecto.

Además, tener siempre código compilable y que funciona nos permite obtener feedback de la gente para la cual estamos trabajando. Es un hecho que la mayoría de los clientes no sabe exactamente lo que quiere hasta no ver una aplicación funcionando y si desaparecemos durante meses antes de mostrar algo es probable que terminemos programando algo muy distinto a lo que nuestros clientes necesitan.

Por último, este desarrollo nano-incremental permite justamente ir evolucionando muy despacio el diseño, y sólo con lo que se necesita para resolver el problema en cuestión sin agregar funcionalidad extra que no se necesitara. Justamente al diseño así obtenido se lo llama Diseño Incremental.

Pasen la Palabra

Pese a todos estas razones, y evidencia de muchas fuentes, y entre ellas, nuestra propia experiencia, vemos que la forma de trabajo más común entre los programadores sigue siendo el "big bang" de codigo. Y para ser sinceros, hemos tenido hasta ahora relativo éxito en conseguir convencer a nuestros compañeros de utilizar el "Pasos de Bebe".

Intuitivamente, se sigue creyendo que juntar grandes lotes de código para después probarlo es más eficiente y productivo. Es razonable, porque para muchas tareas de la vida real es así: si uno va a lavar los platos conviene primero ponerle detergente a todos los platos, después enjuagarlos y después lavarlos. No tiene sentido tomar un plato, ponerle detergente, enjuagarlo y secarlo.

Sin embargo, el desarrollo de software no se parece al lavado de platos (ni tampoco a la construcción de edificios para el caso).

Así que si se animan a probar un deporte de riesgo, si les gusta luchar con leones hambrientos y no son débiles de corazón, en su próximo proyecto apliquen esta practica de desarrollar en "Pasos de Bebe". Les aseguramos que van a ver una considerable mejora en su productividad.

Y mucho, mucho, menos debugging.

lunes, 13 de septiembre de 2010

2do Code Retreat

El Evento

El viernes 10/09 realizamos el Segundo Code Retreat en Buenos Aires, en las instalaciones del MUG (Microsoft User Group).

Queremos antes que nada agradecer a los que nos ayudaron a organizar este Retreat, particularmente a Juan Gabardini, referente de la comunidad ágil de Argentina (recomendamos su blog softwareagil.blogspot.com), a Martin Salias (de Southworks ), a Carlos Peix y especialmente a Oscar Turquet del MUG.

La experiencia fue muy satisfactoria, y en esta oportunidad tuvimos el aporte de la vision de gente con mucha experiencia como Carlos Peix, Martin Alaimo (de Kleer) o como Juan (Gabardini) que proviene del sector de Testing.

La lista de Asistentes al Retreat fue :

  • Carlos Meschini
  • Carlos Peix
  • Martin Alaimo
  • Matias Blanch
  • Carlos Pantelides
  • Fernando Claverino
  • Nicolas Bases
  • Juan Gabardini
  • Victor Jorge Paredes
  • Gonzalo Amestoy
  • Jose Vidal

Conclusiones del Retreat

En general la mecanica del Retreat funcionó, encarando el problema a resolver cada vez desde cero, borrando el codigo entre iteraciones y cambiando de compañero de Pair Programming. Falto quizás una iteración más para hacer mas aceitada la mecánica pero la hora y el día en particular no daba para mucho mas.

El tema de hacerlo un viernes ayuda y complica a la vez, porque hay gente que no puede salir de sus trabajos y por otro lado esta el cansacio de la semana, pero en general hubo muy buena onda y energia como para completar 3 iteraciones completas, con una retrospectiva final que se extendio por casi 40 minutos.

Uno de los puntos en que coincidieron todos los participantes, tratando de hacer TDD, es la dificultad para incrementar la funcionalidad de los tests de a pequeños pasos. En general los dos o tres primeros tests "triviales" salen mas o menos bien, simples y en pequeños pasos, pero cuando hay que avanzar en un test que permita introducir funcionalidad no-trivial en general se escribe un test con un salto demasiado grande lo cual dificulta luego el desarrollo.

Justamente otro de los temas que salieron fue la relación entre facilidad de testing y buen diseño del código. Es decir, si se dificulta testear algo muchas veces es porque hay problemas con el diseño del modelo de la solución.

A veces cuesta mantener los tests de unidad porque no prestamos la suficiente atención a la calidad del código de los mismos, ya que es código fuente se deben tener todos los cuidados y aplicar todos los principios y buenas prácticas como con el código de la aplicación principal, sino su mantenimiento se convierte en una pesadilla. Por ejemplo, si un tests prueba más o menos lo mismo que otro se deben eliminar porque la duplicación es uno de los Code Smells que más problema trae a la mantenibilidad del código.

Entre las cuestiones "negativas" que de alguna manera son causadas por la dinámica del Retreat se mencionó que la presión de avanzar en la solución del problema muchas veces hacia que uno le prestara poca atención a la etapa de Refactoring.

En TDD, despues de hacer el test, y de hacer lo necesario para pasar, cuando los tests funcionan (La barra esta toda en "verde") se debe evaluar el diseño del código para ver si es necesario refactorizar. Este punto quedaba de lado muchas veces causados por la "presión" de la duración de la iteración.

Otro punto "flojo" es que en general no se hizo Pair Programming respetando tajantemente los roles de "Conductor" y "Navegante". Es decir estaban ambos integrantes muy compenetrados en los detalles más chicos del código y el que no tecleaba no funcionaba como "navegante", mirando todo desde un punto de vista más alejado, previniendo de posibles desviaciones (como fue esta de no evaluar si correspondia aplicar Refactoring).

Ademas de C# y Java, hubo algunos participantes que utilizaron Ruby y RSpec. Invitamos a los que estuvieron trabajando con estas herramientas a que nos cuenten sus impresiones.

Finalmente, queremos volver a agradecer a todos los involucrados, por su buena onda, su energia y sus ganas. Sabemos que es un esfuerzo significativo dedicarse a aprender uno mismo, con el objetivo de mejorar profesionalmente, pero vale la pena. Tambien decirles que ya nos estamos poniendo a pensar en futuros encuentros. Hasta la proxima!

miércoles, 1 de septiembre de 2010

Inscripcion al Segundo Code Retreat!!

Se viene el segundo Code Retreat!! Para todos los que protestaron (con total justicia) porque el primero fue un sábado muy temprano, esta vez va a ser el Viernes 10/09, de 14:30 a 20 horas, en el MUG (Microsoft User Group), que queda en Rivadavia 1479 Piso 1 Of. A.

Estamos muy contentos porque la pasamos muy bien en el primer Code Retreat que hicimos a principio de año.

Aquí, en el sitio de la comunidad ágil de Argentina se puede ver más información sobre el evento y los interesados pueden anotarse en esta página.

Los esperamos!