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!

lunes, 30 de agosto de 2010

Algunas herramientas para usar con Selenium

Como contaba aquí estuve automatizando tests de aceptación con Selenium, utilizando como lenguaje de programación para los tests Ruby. En este post voy a hablar un poco de las herramientas que utilicé para facilitar esta automatización.

La tarea más importante (pero también la más tediosa) en este tipo de automatizaciones es la identificación de los objetos gráficos (botones, cuadros de texto, etc.) con los que queremos interactuar. Si la aplicación fue desarrollada de manera de que todos los objetos tengan identificadores únicos que se puedan predecir con algún tipo de lógica el problema se simplifica bastante, pero en general esto se da solamente si se fueron escribiendo tests de aceptación en paralelo con el desarrollo de la aplicación. En el caso más usual en que escribimos los tests después de que la aplicación está terminada, debemos dedicarle bastante tiempo a buscar maneras de identificar los componentes de la UI (y no solamente debemos identificarlos si no que debemos hacerlo de una manera robusta, que no cause que todos nuestros tests fallen ante pequeños cambios en la estructura)

Existen varios plugins de Firefox que pueden ayudarnos en esta tarea. En primer lugar está Firebug que es un plugin indispensable para cualquiera que desarrolle aplicaciones Web, con muchísimas funcionalidades que exceden el alcance de este artículo. Solo vamos a mencionar que utilizando este plugin podemos identificar cualquier objeto en la pantalla simplemente haciendo click en el despues de seleccionar la herramienta adecuada.

Otros plugins interesantes son XPather y FireFinder. Estos plugins nos permiten probar expresiones CSS y XPath contra la página que queremos testear, para verificar que estén construidas correctamente y que identifiquen a los objetos que necesitamos. Poder hacer esto desde un plugin contra la página viva nos permite darnos cuenta de errores en forma interactiva, sin necesidad de ejecutar todo un test de aceptación para llegar a la parte del código que contiene la expresión que queremos probar.

Otra manera de probar partes de nuestros tests sin necesidad de ejecutar un test de aceptación completo es utilizar la capacidad de Ruby para trabajar en forma interactiva. Para eso es muy útil el shell de Ruby.

AutoIt es una herramienta fundamental para automatización de tests de aceptación en ambiente Windows. Si bien Selenium está perfectamente equipada para interactuar con casi todos los elementos de la interfaz gráfica, tiene problemas con ciertas ventanas, como las confirmaciones de seguridad o los diálogos para el download de archivos. Esto se debe a que esas ventanas no son ventanas HTML si no que son ventanas nativas del sistema operativo. Por lo tanto, cuando necesitamos realizar actividades que incluyan ese tipo de ventana tenemos que recurrir a herramientas más nativas, como AutoIT. Por suerte, se engancha muy bien con Ruby.

Para poder generar datos para los tests, utilicé esta librería Es un port de una antigua libreria de Perl y contiene colecciones de datos para usar en los tests. Por ejemplo, si necesitamos un nombre para un test hacemos Faker::Name.first_name y esto nos devuelve un nombre al azar de la colección de datos interna de la librería.

Por la ausencia de buenos identificadores en la aplicación para la cual estoy escribiendo los tests, tuve que recurrir mucho a expresiones complejas XPath para identificar los objetos. Esto funcionaba perfectamente bien en Firefox, pero cuando probé con Internet Explorer, los tiempos de ejecución crecieron más de 10 veces!!

Esto se debe a que el motor de Javascript de IE es exasperantemente lento. Era un problema, ya que sabemos que los tests deben ser rápidos, así que estuve haciendo algunas pruebas y la solución que encontré fue la siguiente: en vez de pedirle al browser que identifique un objeto en base a una expresión XPath, le pido todo el DOM en formato HTML y realizo la búsqueda XPath en el cliente Ruby que ejecuta los tests. Esta solución es anti-intuitiva, ya que bajarse todo un documento HTML para hacer una búsqueda parece menos eficiente que hacer la búsqueda en el browser, pero el motor XPath de IE es tan lento que conviene hacerlo así. Para realizar las búsquedas en el documento HTML usé la excelente librería Hpricot, del desaparecido _why.

Finalmente, para organizar la corrida de los tests y para tener un output más o menos lindo con el resultado de la ejecución, usé la librería RSPec, que es un framework que normalmente se usa para BDD en Ruby.

miércoles, 25 de agosto de 2010

Code Smells : Obsesión Primitiva

Queridos Chimpances Entrenados, Dice la Enciclopedia Galatica:

Obsesión Primitiva: Es un Smell que se produce cuando uno utiliza tipos primitivos como Strings o Doubles o Ints para representar conceptos que realmente merecen ser representados por clases de objetos.

Es uno de los smells más comunes que podemos encontrar en cualquier código que escribamos o revisemos.

Cuando se trabaja con Objetos, para modelar la estructura interna de una clase uno tiene dos alternativas:


1 - Referenciar a otra Clase de objeto

2 - Utilizar un tipo primitivo.

En general el problema empieza cuando uno decide utilizar un tipo primitivo para modelar un concepto porque se resiste a crear una clase para el mismo porque en ese momento solo sirve para encapsular un simple entero, una String o un par de estos, con nada o muy poco comportamiento extra.

Tipicos ejemplos de esto es cuando uno modela por ejemplo un rango de enteros, como ser, rango de valores válidos para un atributo para un sistema que modele rangos de edades especificos, campos de calle, numero y ciudad para modelar la direccion de un Cliente, etc. . Otro ejemplo es utilizar un int o String para discriminar el "subtipo" o diferentes variaciones de un objeto. Pero el caso emblematico por definición es cuando uno modela sumas de Dinero como un Double para representar el valor, y una String que representa la especie (dolares, pesos, euros, etc.)

Cuando uno sigue trabajando luego de la elección del tipo primitivo al principio siente que ahorro en cantidad de código y clases creadas, y se felicita por ello.

Pronto, no obstante, a medida que el sistema crece comienza a pedir más y más comportamiento de ese tipo primitivo, que, como en general no puede agregarse en el mismo, en lenguajes como Java o C# , se terminan ensuciando el código de la clase adonde se encuentra el primitivo, adonde se agregan métodos y más métodos helpers o utils que trabajan con ese tipo primitivo y con ninguno de los otros atributos de la clase.

Sin entrar en campañas proselitistas, otros lenguajes si permiten agregar métodos no solo a las clases de los primitivos, sino también a instancias específicas (prototype) como por ejemplo en Ruby o Smalltalk, pero no obstante las reglas de Buen Diseño esta indicando la necesidad de un objeto que represente mejor el concepto y que va a permitir agregarle comportamiento a medida que la solución la vaya necesitando (no olvidemos: nunca especular que se necesitará por adelantado).

Ejemplo 1
package ar.com.smells.examples; 
public class ClienteFibertel { 
private String nombre;  
private  String apellido; 
private MedioPago medioPago; 
private long idCliente; 
 
... 
 
private String nombreCalle; 
private String numero; 
private String piso; 
private String Depto; 
private String codigoPostal; 
private String ciudad; 
 
//Metodos propios de clase Cliente 
 
   .... 
 
//Metodos que piden a grito una clase Direccion 
 
    public boolean esCodigoPostalRural() { 
     bla bla bla 
    }
    
    public String direccionFormateadaParaEnvioPostal() {
            bla bla bla 
    } 
 
    public boolean esEdificio() {
           bla bla y mas bla. 
    }
 
} 
En este caso, se deberia refactorizar a una clase Direccion que forme parte del Cliente y que deberia llevarse los tres métodos que estamos indicando que no forman parte de la lógica propia de la clase adonde están.

Ejemplo 2

Un ejemplo muy especial dentro de la obsesion por primitivos, aunque no es el tipico ejemplo, es cuando se utiliza un int como subtipo y esto genera que se repita la lógica de distinción entre subtipos entre todos los métodos de la Clase. La solución es muy clara, la clase debe separarse en una clase base y varias subclases con la diferencia.

 
public class ConexionInternet
{ 
   ... 
 
private int tipoConexion;
 ... 
 
    public String metodoA() { 
         if (tipoConexion == ConexionInternet.CABLEMODEM) { //ya no  hay mas, adios Fibertel
 
         } else if (tipoConexion == ConexionInternet.ADSL) { // speedy? arnet? }
 
           } else if (tipoConexion == ConexionInternet.Modem) { //El unico que va a quedar, la vuelta al 56K! 
 
             }
   } 
 
   public boolean metodoB() {
       if  (tipoConexion == ConexionInternet.CABLEMODEM) { //ya no hay mas, adios Fibertel
     
       } else if (tipoConexion == ConexionInternet.ADSL) { // speedy? arnet? 
 
       
        } else if (tipoConexion == ConexionInternet.Modem) { //El unico que va a quedar, la  vuelta al 56K! 
 
        } 
   }
 
} 

Es bastante lógico decir que parece razonable refactorizar en 3 clases ConexionInternetCableModem, ConexionInternetADSL, ConexionInternetModem, todas dependiendo de una clase abstracta o implementando una interfaz ConexionInternet.

De cualquier manera una de los ejemplos más clásicos y que mayores problemas trae al estar “Obsesionado por Primitivos” es cuando se modelan Sumas de dinero como objetos Double. Los que escribimos este blog damos plena fe de ello.

El primer problema aparece cuando uno comienza a realizar operaciones y surgen las "pequeñas" diferencias por redondeo!. Y claro, cuando uno tiene problemas de redondeo con kilometros entre planetas no hay mucho problema (salvo que este manejando una nave espacial :), pero si es dinero... ahi sii, esos centavos importan y como!

Luego la siguiente estación al desastre es cuando el Usuario, a pesar de habernos dicho que siempre trabajaba con dinero local (pesos, pesos Chilenos, reales, lo que sea) viene muy contentos a contarnos que ahora va a empezar a aceptar pagos en dolares, euros, y en… rupias indias!.

Y nosotros con la clase Producto definida asi:

 
public class Producto {
... 
private double precio; 
 
.... } 

Inmagínense el cambio en todo el sistema!

La solución es ampliamente conocida y no nos vamos a extender en ella por temor a hacer un post kilométrico, pero para los que les interesa pueden verla en detalle en el libro de Test Driven Development (Lo se, lo hemos recomendado muchas veces, de Kent Beck).

Para los ansiosos o remolones, la solucion es crear una clase Money que siga el patrón de diseño Value Object, sin mostrar el detalle de los métodos, sería algo asi:

 
public class Money { 
 
private BigDecimal monto; 
private Currency moneda; 
….
} 

Un hecho relativamente moderno que ha reafirmado e incentivado la Obsesión Primitiva es la utilización másiva de ORMs (Object Relational Mapping), como Hibernate.

El problema en este caso es cuando se esta definiendo un Bean que terminará como tabla en una base de datos y, y condicionados por la complejidad de la herramienta, se prefiere tener un bean al cual le agregan los atributos primitivos directamente y no múltiples tablas de objetos relacionados.

Por ejemplo, prefieren tener una sola clase SujetoAnses al cual le agreguen 2 int para guardar rango máximo y mínimo configurado a tener otra tabla relacionada con el Bean principal con los rangos permitidos.

O del ejemplo anterior, ClienteFibertel con todas las String en una misma tabla a tener una relacion con otra tabla Direccion. Es inútil explicar en estos casos que en general no es razonable decidir el Diseño de Objetos por las consecuencias que tendrán sobre la base de datos. Al menos no a priori hasta tener razones de peso, de performance, etc..

Para terminar, no estamos afirmando que TODO atributo o concepto simple debe ser representado como una clase de objetos que se convierta en simples wrappers de un int, String o double.

Pero ante el minimo comportamiento que haya que agregar, cuando uno comienza a pasar ese valor de aquí para allá o hace algún método que lo utiliza solo a él, etc., en esos casos mantenerlo como primitivo es… una obsesión y hay que tratarla!.

lunes, 16 de agosto de 2010

Test de unidad con Selenium

Hace algunos días que estoy automatizando un conjunto de Smoke Tests, es decir tests que se ejecutan para ver si algo se rompe al hacerlo, sin verificar que el resultado de las operaciones sea el esperado. Estos tests se ejecutaban antes a mano, con el esperable desperdicio de recursos.

Para la automatización decidí usar Selenium, por ser una de las herramienta open-source más populares y porque tiene muy buena portabilidad entre browsers. Selenium es una librería Javascript que se agrega a nuestra aplicación Web (en forma transparente, ya vamos a ver como) y que la extiende con una interfaz capaz de recibir comandos que permiten interactuar con los elementos gráficos de la UI.

Existen varias maneras de utilizar Selenium. La más sencilla es Selenium IDE, que permite crear tests mediante el proceso de "Record & Play", es decir que tiene la capacidad de registrar nuestra interacción con un sitio web y de reproducir luego exactamente las mismas acciones. Esta modalidad no exige saber nada sobre el funcionamiento de Selenium y permite que una persona sin conocimientos de programación pueda escribir sus propios tests. Sin embargo tiene el grave incoveniente de que los tests así creados son muy frágiles: cualquier cambio en la aplicación causa que los tests dejen de funcionar y deben ser creados de nuevo desde cero (este problema no es particular de Selenium IDE si no que es compartido por todas las herramientas que utilizan el esquema "Record & Play").

Debido a estas limitaciones decidí utilizar Selenium Remote Control. Esta solución tiene una arquitectura de cliente/servidor. El servidor se encarga de controlar una instancia de un Web Browser (puede ser Internet Explorer, Firefox, Opera, Chrome y Safari). El cliente interpreta los tests y envía comandos al server, que se ocupa de ejecutar los comandos recibidos en el browser que controla. Esta arquitectura permite al servidor injectar las librerías Javascript que componen el core de Selenium en forma transaparente, sin que tengamos que instalarlas en el servidor de la aplicación que queremos testear como en versiones anteriores.

Como la interfaz entre el cliente y el servidor es HTTP puro, los clientes (y por lo tanto los tests) pueden estar escritos en cualquier lenguaje capaz de ejecutar un request HTTP. Existen clientes para C#, Java, Perl, PHP, Python y Ruby.

Veamos un ejemplo sencillo de como utilizar Selenium RC con Ruby. En primer lugar es necesario realizar la instalación de Selenium. Para el server, solo es necesario bajar y descomprimir este archivo y tener disponible un run-time de Java.

Para instalar el cliente ruby, podemos utilizar el siguiente comando:

gem install selenium-client

Una vez realizada la instalación, hay que poner a correr el server:

cd selenium-server-1.0.3 java -jar selenium-server.jar

Y podemos probar este pequeño script de prueba, que ejecuta una búsqueda en Google:

#hello_google.rb require "selenium/client"

begin @browser = Selenium::Client::Driver.new \ :host => "localhost", :port => 4444, :browser => "*firefox", :url => "http://www.google.com", :timeout_in_second => 60

@browser.start_new_browser_session @browser.open "/" @browser.type "q", "Selenium seleniumhq.org" @browser.click "btnG", :wait_for => :page puts @browser.text?("seleniumhq.org") ensure @browser.close_current_browser_session end

Este script crea una instancia del cliente Ruby y ejecuta los comandos necesarios para escribir la frase "Selenium seleniumhq.org" en el cuadro de texto de google y a continuación presiona el boton de búsqueda.

El código que se ocupa de escribir el texto es un ejemplo muy representativo de como funciona Selenium asi que vamos a utilizarlo para entrar un poco más en detalle.

@browser.type "q", "Selenium seleniumhq.org"

El segundo parámetro del método type es solo el texto a cargar y por lo tanto no es muy interesante. El primer parámetro en cambio es el mecanismo para localizar el objeto en que queremos escribir (locator en la jerga Selenium) y es muy importante, ya que la identificación en forma efectiva y robusta de los distintos componentes de la interfaz es fundamental en estos trabajos de automatización.

En este caso el locator es simplemente el id del text box. Esta forma de identificación es claramente la más sencilla y probablemente la mejor desde el punto de vista de la mantenibilidad. Sin embargo, muchas veces no es posible utilizarla, ya sea por que los objetos que queremos identificar no tienen id o porque el id de un mismo objeto cambia entre sesiones. Para estos casos, Selenium tiene formas más poderosas de identificacion.

Por ejemplo podemos identificar objetos utilizando XPath (http://en.wikipedia.org/wiki/XPath), que es un lenguaje para acceder a las distintas partes de un documento escrito en un lenguaje de markup. Veamos algunos ejemplos de selectores XPATH

Escribe en el elemento de tipo input con el id "q" (equivalente al ejemplo anterior) @browser.type "xpath=//input[@id='q']", "texto a buscar"

Identifica un div con el texto "Hola", pero hace click en otro div, que es "hermano" del identificado @browser.click "xpath=//span[text()='Sales']/../div"

XPATH es muy poderoso y un tutorial completo escapa a los objetivos de este artículo. Pueden encontrar una introducción aquí: http://www.w3schools.com/xpath/xpath_intro.asp . Un detalle importante para la utilización de XPath con Selenium es que no todos los browsers proveen una buena implementación de XPath, por lo que muchas veces conviene pedirle a Selenium que utilice su propia versión de la herramienta, que es un poco más lenta pero más poderosa.

Otra alternativa para identificar objetos con Selenium es CSS. En este caso la forma de nombrar los objetos es exactamente la que utilizamos para describir su presentación en archivos CSS. Veamos algunos ejemplos:

Escribe en el elemento de tipo input con el id "q" (equivalente al primer ejemplo XPath) @browser.type "css=input#q", "texto a buscar"

Click en el link que contenga el texto "Mi Link" @browser.click "css=a:contains('Mi Link')

Una ventaja importante de CSS sobre XPath es que en Internet Explorer corren muchísimo más rápido los comandos que utilizan CSS.

Como conclusión, Selenium me pareció una herramienta muy interesante. Con un poco de práctica se pueden escribir tests con bastante facilidad y esos tests se pueden utilizar después en distintos browsers prácticamente sin modificaciones. En artículos posteriores vamos a hablar de buenas prácticas en la escritura de tests con Selenium y también de la utilización de algunas herramientas para hacer más fácil la detección de formas de identificar los componentes de la UI con los que queremos interactuar.