Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Seminario de Test Development Driven

Seminario de Test Development Driven

Seminario de introducción a Test Driven Development organizado por Paradigma Tecnológico y javaHispano. El seminario fue impartido por Carlo Scarioni el 23 de Abril 2010
Mas info: http://www.paradigmatecnologico.com/historico/seminario-de-test-development-driven/

Paradigma

April 23, 2010
Tweet

More Decks by Paradigma

Other Decks in Technology

Transcript

  1. Enfoque de la Charla  Presentar un ejemplo de principio

    a fin de una funcionalidad de un proyecto. Sin profundizar en las herramientas utilizadas. El objetivo es clarificar el proceso de TDD.
  2. Objetivos de la charla - - Introducir TDD como una

    alternativa viable al desarrollo tradicional. - - Crear cierta inquietud por profundizar mas en el tema. - - Exponer las ventajas que TDD tiene para el desarrollador. - - Explicar paso a paso como afrontar una funcionalidad con esta práctica.
  3. ¿Que ventajas trae TDD al desarrollador? • Confianza en el

    funcionamiento. • Foco en el desarrollo. • Código mas limpio. (Buenas practicas de desarrollo y patrones). • Menos Bugs y mas localizados. • Documentación del código con los Tests. • Menos reinicio del servidor para probar.
  4. Realizar el menor diseño posible antes de empezar. Solo lo

    necesario (Generalmente la Infraestructura de la aplicación). Los test guiarán el diseño. El ciclo de TDD
  5. El ciclo de TDD Refactor Escribir un test Unitario que

    falle Hacer que el test pase Escribir un Test Funcional Que falle
  6. ¿Cuanto tiempo pueden estar los tests en rojo? - -

    Test Unitarios deben pasar cuanto antes. - - Test funcionales tardarán mas en pasar. Y estarán en un ciclo distinto del BUILD. - - Nunca se subirán tests que fallan al repositorio de código fuente. - - Solo se desarrolla funcionalidad cuando exista un Test fallido que lo requiera.
  7. ¿Cuanto del código probar? - - Probar TODO lo que

    tenga sentido probar. - - No probar lo trivial. - - Spikes paraThird Party. - - Cobertura.
  8. ¿Como comenzar?. - - Escoger la funcionalidad (feature) mas pequeña

    posible que la aplicación deba cumplir. - - Luego ir escogiendo funcionalidades de nuestro Product Backlog.
  9. ¿Que es un test de aceptación o test funcional? Nos

    ayuda a definir exactamente la funcionalidad que queremos hacer. Un test define QUE queremos hacer, si importar realmente el COMO.
  10.  No debe conocer los objetos internos del sistema. 

    Debe reaccionar ante eventos que se produzcan en la capa "visible" (GUI, LOG, etc). Usualmente haciendo un poll para ver si hay cambios. Test Funcional
  11. Prueba la interacción entre distintos componentes del sistema, o entre

    el sistema y componentes externos. Por ejemplo con la Base de Datos, Con el sistema de ficheros, etc. Test Integración
  12. Definimos el Test Funcional. Selenium. Permite ejecutar Tests Directamente sobre

    la interfaz de usuario HTML, sobre un navegador en particular. Para esto la aplicación debe estar iniciada. El proceso de iniciar la apliciación debe ser automático, previo a la ejecución de los test, y terminado luego.
  13. Definimos el Test Funcional. Este Test podrá estar sin pasar

    por mucho tiempo. Nos sirve como guía de la funcionalidad final que queremos conseguir.
  14. Prueba comportamientos en aislamiento TOTAL respecto al resto del sistema.

    Prueba una y solo una caracteristica sin que los demas elementos del sistema afecten su ejecución. Test Unitario
  15. El primer Test Unitario.  La primera abstracción que se

    nos ocurre es una Cuenta. En la que podemos depositar y sacar dinero.  Hacemos el test para depositar dinero
  16. El primer Test Unitario. Hacemos el test compilar. Para eso

    creamos la clase Account y los métodos necesarios.
  17. Realizamos otro test que nos obligue a sustituir la implementación

    falsa. Esto es Triangulación en TDD El primer Test unitario.
  18.  Cuando se sepa claramente la implementación obvia, aplicarla. 

    No es necesario siempre dar los pasos mas pequeños posibles.  Si la implementación obvia resulta no ser tan obvia, y al implementarla los test fallan. Hacer los pasos mas pequeños posibles. El primer Test unitario.
  19. Probando los fallos. Hemos creado por TDD lo siguiente en

    AccountTest y Account para el retiro de dinero. Sin embargo como requerimiento tenemos que no podemos dejar una cuenta a un numero negativo. Por tanto hay que comprobar que esto no se pueda dar.
  20. Probando los fallos. Con esto decimos que esperamos que al

    producirse esta situación (Rerirar mas de lo que tenemos), se eleve la excepción WithdrawlException (Que aun no existe).
  21. Continuando con Account. Soporte de Divisas La siguiente caracterisitac que

    deben soportar nuestras cuentas, es que pueden estar en EUR, USD o GBP. Comenzamos con el TEST.
  22. Continuando con Account. Soporte de Divisas. Cuando uno crea un

    Test, la idea es imaginar el API mas correcto de lo que estamos creando. En este caso nos damos cuenta que una Cuenta DEBE tener una divisa. Por lo que la ponemos en le constructor.
  23. Hacemos los cambios necesarios a Account para que compile y

    damos la solución obvia. Nuestros Tests anteriores ya no compilan (requerían constructor vacio). Cambiamos la construcción de las Accounts para el nuevo constructor. Ejecutamos los Tests. Barra verde. Continuando con Account. Soporte de Divisas.
  24. Es muy importante ejecutar TODA la suite de Tests cuando

    se hacen cambios y refactorizaciones. Ya que aunque en el caso anterior se detectó en compilación el fallo (La falta de constructor). Muchas veces un cambió hará que cosas que asumiamos como correctas ya no lo sean. Y habrá que adaptar los Tests y el código a la nueva solución. Continuando con Account. Soporte de Divisas.
  25. Refactorización Uno de los mas importantes "Code Smells", es el

    hecho de utilizar Strings para representar cosas que son mas que texto y tienen significado propio. En nuestro caso, el String que se pasa al constructor. Creamos un ENUM para representar nuestras divisas. Adaptamos los Tests. Barra Verde Continuando con Account. Soporte de Divisas.
  26.  Añadimos también, por comodidad un constructor que aparte de

    la divisa, se le pueda enviar también el balance inicial. Continuando con Account. Soporte de Divisas.
  27. Probando el servicio de transferencia. Dejando Account a un lado,

    considerandolo terminado, nos centramos en el servicio de transferencia. Para nosotros una transferencia será sencillamente, retirar de una cuenta, y depositar en otra.
  28. El primer test que se nos ocurre es hacer una

    transferencia entre 2 cuentas con la misma divisa. Probando el servicio de transferencia.
  29. El código anterior es lo primero que se nos ocurre

    realizar. Sin embargo, viendo la interfaz del método transfer, los parámetros se prestan a confusión. ¿De que cuenta a que cuenta es la transferencia? Probando el servicio de transferencia.
  30. Decidimos que un mejor diseño es encapsular la transferencia es

    su propio objeto y pasar este objeto al metodo transfer. Probando el servicio de transferencia.
  31. La TransferOperation es mucho mas especifica. Creamos las clases que

    nos faltan Probando el servicio de transferencia.
  32. Haciendo una inspección de que puede estar fallando, nos damos

    cuenta que el error no esta en el servicio, ni en el Test.. sino en Account (Podriamos hacer un mock temporal de la clase account y ver que se llaman al deposit y withdrawal correctamente. Pero el servicio es muy simple y resulta obvio). Clase que habiamos dada por terminada. Vemos que el problema está en el metodo deposit. Por no haber triangulado lo suficiente en las pruebas. Probando el servicio de transferencia.
  33. Prueba de Regresión. Se reporta un Error (Bug) por una

    funcionalidad dada por cerrada y que por falta de input no se hizo el test adecuado. Normalmente la falta de input es por requerimientos no entendidos completamente. En nuestro caso es que no hicimos todas las pruebas que debimos.  Probando el servicio de transferencia.
  34. Vamos a AcountTest. y agregamos un Test para el error

    que hemos obtenido. Barra Roja Probando el servicio de transferencia.
  35. Vamos a la clase Account e implementamos de forma correcta

    (que al menos pase este test que es lo que entendemos por correcto) el metodo deposit. Probando el servicio de transferencia.
  36. Vemos que ahora el balance es null en deposit cuando

    aun no se ha depositado nada. La ejecución de los Tests, siempre va a capturar comportamientos no deseados como este, si se hacen los tests correctos. Corregimos Account y ejecutamos el Test. Barra Verde. Al fin. Probando el servicio de transferencia.
  37. Regresamos al punto donde realmente estabamos. En TransferTest. Lo ejecutamos.

    Barra Verde. Probando el servicio de transferencia.
  38. Refactoring. La forma de construir la TransferOperation no es del

    todo agradable. sustituimos los setters actuales por un semi-builder mas intutitivo y comodo, definido con un DSL propio. Cambiamos el Test a como realmente queremos que luzca la llamada. Probando el servicio de transferencia.
  39. Transferencias entre 2 monedas distintas. Diseñamos nuestro Test. Primera aproximación.

    Sin factor de conversión. Barra Verde Probando el servicio de transferencia.
  40. Transferencias entre 2 monedas distintas. Con factor de conversión. Primera

    aproximación Probando el servicio de transferencia.
  41. Transferencias entre 2 monedas distintas. Con factor de conversión. Primera

    aproximacion. Hacemos compilar el codigo. Ejecutamos. Barra roja Probando el servicio de transferencia.
  42. Transferencias entre 2 monedas distintas. Con factor de conversión. Primera

    aproximación. Implementamos. Probando el servicio de transferencia.
  43. Transferencias entre 2 monedas distintas. Con factor de conversión. Primera

    aproximación. Ejecutamos los Tests. Barra Roja Probando el servicio de transferencia.
  44. Transferencias entre 2 monedas distintas. Se arregla el bug que

    se acaba de introducir. Ejcutamos los Test. Barra Verde. (Ojo tambien habría que verificar, con test que los valores están metidos en el mapa de conversionRates) Probando el servicio de transferencia.
  45. Transferencias entre 2 monedas distintas. Refactorización SRP. Necesitamos otro servicio,

    Currency Service que se ocupe de la conversión. TransferService solo sabe de transferencias. Probando el servicio de transferencia.
  46. Ejecutamos CurrencyTest. Barra Verde. Sin emabrgo luego de una amplia

    refactorización como la hecha es necesario ejecutar toda la suite de tests para verificar que todo está correcto.
  47. Transfer Test se ha roto con la refactorización, ya que

    se ha incluido una nueva dependencia que no habiamos considerado cuando hicimos el servicio. Debemos adaptar nuestro Test a esta nueva casuistica.
  48.  En el caso anterior en realidad se debía hacer

    el test antes de añadir la dependencia a la clase implementación.  Siempre se debe atender primero al Test según los requerimientos y el comportamiento que se quiere lograr
  49. JMOCK Nos permite con un lenguaje especifico de dominio definir

    Dobles de objetos para nuestros Test, y establecer las expectativas sobre estos objetos.
  50. El uso de mocks El uso de mocks Se puede

    establecer su necesidad en cualquiera de los dos tiempos de diseño. Escribir el Test, o la refactorización.
  51. Refactorizando CurrencyService. Ese mapa de mapas es muy lioso. Se

    nos ocurre mover la responsabilidad de saber el rate de cambio a la propia divisa.
  52.  Para hacer esto decidimos también conversión establecer Rates a

    una divisa única y utilizar esta para los cambios. Usamos el Dólar.
  53. Escribimos los Tests (En realidad se escribieron 1 a 1

    estos tests). Y Ejecutamos, Barra Roja.
  54. Persistiendo los cambios Persistiendo los cambios. Servicio de persistencia de

    cuentas. Mock del Dao en Las transferencias. TDD. AccountService debe permitir persistir cuentas. No compila. Añadimos una nueva propiedad a la clase Account. El identificador. AccountNumber. Aqui incluimos el mock antes de implementar nada. Sabemos que por buena practica el Servicio no irá directo contra Repositorio.
  55. Persistiendo los cambios  - Diseñamos el Test con las

    dependencias en Mente.  - Nos damos cuenta de la necesidad de un identificador de cuenta. Y lo incluimos en nuestro Test.  - Siempre pensar en como queremos invocar a nuestras APIs
  56. Test Driving el DAO. Test de Integración. Ahora haremos la

    implementación del DAO guiado por tests. Exactamente igual que como hasta ahora. Solo que ahora interactuamos con elemento externo. La Base de Datos, o sistema de ficheros. Pero empecemos por el test.
  57. Utilizaremos el poderoso Spring Test Context para las pruebas de

    integración. (Las siguientes pruebas se hicieron 1 a 1).
  58. Implementamos (En realidad lo anterior se hizo test a test.

    Pero para recortar lo ponemos todo de hecho con la primera ejecución nos dimos cuenta que Account debía ser serializable y debiamos implementar el equals y el hashcode para comparar Accounts) .
  59. En algunos casos, los tests de integración son bastante mas

    lentos que los tests unitarios como vemos. Ejecutamos la suite entera de tests
  60. Persistiendo las transferencias El servicio de transferencias ahora mismo no

    persiste los datos. Debemos añadir esto a nuestra aplicación. Lo añadimos en el Test, lo hacemos compilar, y lo implementamos en el servicio. Con el mismo procedimiento paso a paso hecho hasta ahora. Al final obtenemos el siguiente resultado. Obteniendo la barra verde.
  61. TDD el controlador. Se basa en los mismos procedimientos que

    el probar las otras capas. Centrarnos en la funcionalidad requerida, y simular los demas componentes. Las dependencias. Sabemos que trabajaremos con Controllers de Spring. Lo primero que haremos será la funcionalidad para presentar el formulario.
  62. TDD el controlador Implementamos. Ejecutamos el Test. Barra Verde. En

    principio no hay refactorización que hacer.
  63. El test funcional. Creamos las JSPs requeridas segun nuestros Test,

    nuestro controller y nuestro conocimiento de Spring MVC. Transfer.jsp
  64. Todo dentro del Test  El Test funcional idealmente es

    capaz de iniciar todo su entorno. En nuestro caso iniciamos el Tomcat.
  65. Las 2 Fases del BUILD  En un entorno de

    integración continua.  Mvn test: Ejecuta los Tests unitarios, se ejecutara en cada momento  Mvn integration-test: Ejecuta los Tests funcionales. 2 veces al dia.
  66. Revisando el Code Coverage  Cobertura.  Mvn site 

    Mas que medir la cobertura por porcentaje. Estar conscientes de que hemos probado lo necesario.
  67. Reporte del Ejemplo PACKAGE CLASES LINE COVERAGE ALL PACKAGES 16

    75% ORG.PT.TDD 1 92% ORG.PT.TDD.DAO 2 75% ORG.PT.TDD.DOMAIN 3 69% ORG.PT.TDD.EXCEPTION 3 50% ORG.PT.TDD.FORM 1 100% ORG.PT.TDD.SERVICE 6 90% Cobertura 136/179 15/20 26/28 64/92 6 12 18/20 7/7
  68. Conclusiones - - TDD nos ayudan a mantener el foco

    de lo que queremos desarrollar. - - TDD nos sirve como red de seguridad para atrapar Bugs lo antes posible. - - TDD nos da seguridad de que lo que desarrollamos funciona.