P. 1
disenoAgilConTDD

disenoAgilConTDD

|Views: 51|Likes:

More info:

Published by: Danae Aguilar Guzmán on Oct 17, 2011
Copyright:Attribution Non-commercial

Availability:

Read on Scribd mobile: iPhone, iPad and Android.
download as PDF, TXT or read online from Scribd
See more
See less

10/17/2011

pdf

text

original

Sections

  • Base Te´orica
  • El Agilismo
  • 1.1. Modelo en cascada
  • 1.2. Hablemos de cifras
  • 1.3. El manifiesto ´agil
  • 1.4. ¿En qu´e consiste el agilismo?: Un enfoque
  • 1.5. La situaci´on actual
  • 1.6. ´Agil parece, pl´atano es
  • 1.7. Los roles dentro del equipo
  • 1.8. ¿Por qu´e nos cuesta comenzar a ser ´agiles?
  • 2.1. El algoritmo TDD
  • 2.1.1. Escribir la especificaci´on primero
  • 2.1.2. Implementar el c´odigo que hace funcionar el ejemplo
  • 2.1.3. Refactorizar
  • 2.2. Consideraciones y recomendaciones
  • 2.2.1. Ventajas del desarrollador experto frente al junior
  • 2.2.2. TDD con una tecnolog´ıa desconocida
  • 2.2.3. TDD en medio de un proyecto
  • 3.1. Las historias de usuario
  • 3.2. Qu´e y no C´omo
  • 3.3. ¿Est´a hecho o no?
  • 3.4. El contexto es esencial
  • Tipos de test y su importancia
  • 4.1. Terminolog´ıa en la comunidad TDD
  • 4.1.1. Tests de Aceptaci´on
  • 4.1.2. Tests Funcionales
  • 4.1.3. Tests de Sistema
  • 4.1.4. Tests Unitarios
  • 4.1.5. Tests de Integraci´on
  • Tests unitarios y frameworks
  • 5.1. Las tres partes del test: AAA
  • Mocks y otros dobles de prueba
  • 6.1. Cu´ando usar un objeto real, un stub o un
  • 6.2. La met´afora Record/Replay
  • Dise˜no Orientado a Objetos
  • 7.1. Dise˜no Orientado a Objetos (OOD)
  • 7.2. Principios S.O.L.I.D
  • 7.2.1. Single Responsibility Principle (SRP)
  • 7.2.2. Open-Closed Principle (OCP)
  • 7.2.3. Liskov Substitution Principle (LSP)
  • 7.2.4. Interface Segregation Principle (ISP)
  • 7.2.5. Dependency Inversi´on Principle (DIP)
  • 7.3. Inversi´on del Control (IoC)
  • Ejercicios Pr´acticos
  • Inicio del proyecto - Test Unitarios
  • 10.1. La frontera entre tests unitarios y tests de
  • 10.2. Dise˜no emergente con un ORM
  • 10.2.1. Dise˜nando relaciones entre modelos
  • Cap´ıtulo 10 10.3. La unificaci´on de las piezas del sistema
  • 10.3. La unificaci´on de las piezas del sistema
  • La soluci´on en versi´on Python
  • Antipatrones y Errores comunes

Dise˜o Agil con TDD n

Carlos Bl´ Jurado y colaboradores. e Prologo de Jos´ Manuel Beas e

Primera Edici´n, Enero de 2010 o www.iExpertos.com El libro se ha publicado bajo la Licencia Creative Commons

2

´ Indice general

I

Base Te´rica o
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

31
33 35 37 38 40 44 46 47 49 53 56 56 57 58 60 60 60 61 63 64 68 70 71

1. El Agilismo 1.1. Modelo en cascada . . . . . . . . . . . . . . . . . 1.2. Hablemos de cifras . . . . . . . . . . . . . . . . . 1.3. El manifiesto ´gil . . . . . . . . . . . . . . . . . . a 1.4. ¿En qu´ consiste el agilismo?: Un enfoque pr´ctico e a 1.5. La situaci´n actual . . . . . . . . . . . . . . . . . o ´ 1.6. Agil parece, pl´tano es . . . . . . . . . . . . . . . a 1.7. Los roles dentro del equipo . . . . . . . . . . . . . 1.8. ¿Por qu´ nos cuesta comenzar a ser ´giles? . . . . e a

2. ¿Qu´ es el Desarrollo Dirigido por Tests? (TDD) e 2.1. El algoritmo TDD . . . . . . . . . . . . . . . . . . . . . . . 2.1.1. Escribir la especificaci´n primero . . . . . . . . . . . o 2.1.2. Implementar el c´digo que hace funcionar el ejemplo o 2.1.3. Refactorizar . . . . . . . . . . . . . . . . . . . . . . 2.2. Consideraciones y recomendaciones . . . . . . . . . . . . . . 2.2.1. Ventajas del desarrollador experto frente al junior . . 2.2.2. TDD con una tecnolog´ desconocida . . . . . . . . ıa 2.2.3. TDD en medio de un proyecto . . . . . . . . . . . . 3. Desarrollo Dirigido por Tests 3.1. Las historias de usuario . 3.2. Qu´ y no C´mo . . . . . e o 3.3. ¿Est´ hecho o no? . . . . a 3.4. El contexto es esencial . de . . . . . . . . Aceptaci´n o . . . . . . . . . . . . . . . . . . . . . . . . . . . . (ATDD) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

3

´ INDICE GENERAL 4. Tipos de test y su importancia 4.1. Terminolog´ en la comunidad TDD ıa 4.1.1. Tests de Aceptaci´n . . . . . o 4.1.2. Tests Funcionales . . . . . . 4.1.3. Tests de Sistema . . . . . . 4.1.4. Tests Unitarios . . . . . . . 4.1.5. Tests de Integraci´n . . . . . o

´ INDICE GENERAL 73 74 75 76 76 78 80 83 84

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

5. Tests unitarios y frameworks xUnit 5.1. Las tres partes del test: AAA . . . . . . . . . . . . . . . . .

6. Mocks y otros dobles de prueba 95 6.1. Cu´ndo usar un objeto real, un stub o un mock . . . . . . . 97 a 6.2. La met´fora Record/Replay . . . . . . . . . . . . . . . . . . 108 a 7. Dise˜o Orientado a Objetos n 7.1. Dise˜o Orientado a Objetos (OOD) . . . . . n 7.2. Principios S.O.L.I.D . . . . . . . . . . . . . . 7.2.1. Single Responsibility Principle (SRP) . 7.2.2. Open-Closed Principle (OCP) . . . . . 7.2.3. Liskov Substitution Principle (LSP) . 7.2.4. Interface Segregation Principle (ISP) . 7.2.5. Dependency Inversi´n Principle (DIP) o 7.3. Inversi´n del Control (IoC) . . . . . . . . . . o . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 111 112 113 113 114 114 115 116

II

Ejercicios Pr´cticos a

119
121 157 . . . . . . . . . . . . . . . . 231 233 243 246 247 249 289

8. Inicio del proyecto - Test Unitarios 9. Continuaci´n del proyecto - Test Unitarios o 10.Fin del proyecto - Test de Integraci´n o 10.1. La frontera entre tests unitarios y tests de integraci´n o 10.2. Dise˜o emergente con un ORM . . . . . . . . . . . . n 10.2.1. Dise˜ando relaciones entre modelos . . . . . n 10.3. La unificaci´n de las piezas del sistema . . . . . . . . o 11.La soluci´n en versi´n Python o o 12.Antipatrones y Errores comunes

4

´ INDICE GENERAL

´ INDICE GENERAL 297 297 299 299 300 302 302 303 304 304 305

A. Integraci´n Continua (CI) o A.1. Introducci´n . . . . . . . . . . . . . . . . . . . . . . o A.2. Pr´cticas de integraci´n continua . . . . . . . . . . . a o A.2.1. Automatizar la construcci´n . . . . . . . . . o A.2.2. Los test forman parte de la construcci´n . . . o A.2.3. Subir los cambios de manera frecuente . . . . A.2.4. Construir en una m´quina de integraci´n . . . a o A.2.5. Todo el mundo puede ver lo que est´ pasando a A.2.6. Automatizar el despliegue . . . . . . . . . . . A.3. IC para reducir riesgos . . . . . . . . . . . . . . . . . A.4. Conclusi´n . . . . . . . . . . . . . . . . . . . . . . . o

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

. . . . . . . . . .

5

´ INDICE GENERAL

´ INDICE GENERAL

6

A la memoria de nuestro querido gatito Lito. que vivi´ con total atenci´n y o o entrega cada instante de su vida 7 .

8 .

ten´ n ıa que retocar algunas cosillas para que aquello le sirviera pero bueno. antes de escribir una sola l´ ınea de c´digo comenz´ acoro o dando con Iv´n dos cosas: qu´ era exactamente lo que podr´ ver dentro de a e ıa una semana y c´mo sabr´ que. se estaban acumulando sacos sin control y se estaban estropeando. un lejano pa´ donde viv´ dos cerditos. que era el m´s joven e impulsivo. en algunas partes del ıa e mismo. el gallo Iv´n (el gerente de la misma) organiz´ una a o reuni´n en el establo. ıa Adri´n. Algunas eran de un reciente programa que o hab´ ayudado a escribir para la guarder´ de la vaca Paca.Pr´logo o ´ Erase una vez que se era. e 9 . por eso. cu´ntos sacos de grano a ıa a a hab´ en cada parte del almac´n porque sospechaba que. ıa eliminar´ a uno de los dos. tan r´pido como fuera posible. eran hermanos. Adri´n pens´ que ıa ıa a o no eran muy diferentes un almac´n de grano y una guarder´ En el primero e ıa. o a escribir l´ ıneas y l´ ıneas de c´digo. peque˜os animalitos. inmediatamente se puso manos a a a la obra. De acuerdo. Les explic´ qu´ quer´ saber en todo e o e ıa momento: cu´ntos sacos de grano hab´ y qui´n met´ y sacaba sacos de a ıa e ıa grano del almac´n. Para ello s´lo ten´ un mes pero les advirti´ que. ¿no? Pablo. as´ que acordaron ir contabiliz´ndoıa o ı a los por zonas y apuntando a qu´ parte iba o de qu´ parte ven´ cada vez e e ıa. se guardan sacos y en el segundo. esto del software va de reutilizar lo que ya funciona. As´ en poco tiempo podr´ tener una idea ı. Pablo ıs ıan y Adri´n que. sin embargo. “¡No hay tiempo que perder!”. que entrara o saliera un saco. en e o ıan o una semana. efectivamente. no pod´ saber cu´ntos ıan ıa a hab´ y d´nde estaban en cada instante. o ıa Iv´n quer´ conocer. ıan clara del uso que se estaba dando a las distintas zonas del almac´n. estaba terminada cada cosa. donde les encarg´ desarrollar un programa de ordenador o o para controlar el almac´n de piensos. Al final de esa primera semana. Ambos eran los cerditos m´s listos a a a de la granja y. adem´s. dec´ Y empez´ r´pidamente a ıa. Como los sacos entraban y sal´ constantemente. quer´ ya ver algo funcionando.

pero a a ıan ıa a ten´ un aspecto “muy profesional”. La propuesta de Pablo era muy robusta y hac´ a e ıa justo lo que hab´ acordado. las posibilidades de un generador de informes muy potente n´ a que hab´ desarrollado para otra granja anteriormente. ¡S´lo ten´ una semana para convencer a Iv´n! e o ıan a Al final de la primera semana. pero cumpli´ con las expectativas de Iv´n y el programa no fall´ en o a o ning´n momento. Pablo hac´ TDD. Adri´n.Pr´logo o Mientras Adri´n adelantaba a Pablo escribiendo muchas l´ a ıneas de c´digo. la demo de Adri´n fue espectacular. por el otro lado. Sixto se encarg´ de dise˜ar una o n arquitectura que permitiera enviar mensajes desde el m´vil hasta un websero vice que permit´ encolar cualquier operaci´n para ser procesada en paralelo ıa o por varios servidores y as´ garantizar que el sistema estar´ en disposici´n de ı ıa o dar servicio 24 horas al d´ los 7 d´ de la semana. o Pablo escrib´ primero las pruebas automatizadas. Al otro no le dar´ nada. dijo. no dud´ ni un instante. ten´ a ıa un control de usuarios muy completo. Era una oferta complicada e porque. corr´ el riesgo de trabajar durante un mes ıan completamente gratis. Mmmmm. tan impulsivo y arrogante como siempre. a o “¡Trato hecho!”. La demostraci´n de Pablo fue mucho m´s o a modesta. A Iv´n le ıa ıa a pareci´ razonable y les convoc´ a ambos para que le ense˜aran el resultado o o n final en tres semanas. pero s´lo a aquel de los dos que me o haga el mejor proyecto. todo lo que ense˜o lo hab´ probado much´ u n´ ıa ısimas veces antes gracias a que hab´ automatizado las pruebas. ıan a ıa pero era muy prometedora. Pero. todo fue genial. ıa Iv´n no supo qu´ hacer. Claro. por un lado. Adri´n se march´ pitando y llam´ a su primo Sixto. Pablo explic´ que aceptar´ s´lo si Iv´n se comproo ıa o a met´ a colaborar como lo hab´ hecho durante la primera semana. ıa ıa es decir. ıa Ambos se pusieron r´pidamente manos a la obra. el que ganaba se llevaba mucho m´s de lo previsto. adem´s. que sab´ mucho a o o ıa y le asegurar´ la victoria. La propuesta de Adri´n ten´ cosillas que pulir. salvo eso. aunque tuviera que darle parte de las ganancias. ıa ıas 10 . ¡Hab´ hecho la demostraci´n desde un m´vil! ıa o o As´ que les propuso el siguiente trato: “Os pagar´ un 50 % m´s de lo que ı e a inicialmente hab´ ıamos presupuestado. nunca escrib´ una l´ ıa ınea de c´digo sin antes tener una prueba que le o indicara un error. El programa de ıa a Adri´n ten´ muchos botones y much´ a ıa ısimas opciones. Mientras Adri´n arreglaba a a los defectillos encontrados durante la demo.”. Muy a tentador. Durante la demostraıa ci´n hubo dos o tres problemillas y tuvo que arrancar de nuevo el programa o pero. Adri´n no pod´ creer que Pablo hubiera gastado m´s de la a ıa a mitad de su tiempo en aquellas pruebas que no hac´ m´s que retrasarle a ıan a la hora de escribir las funcionalidades que hab´ pedido Iv´n. A Adri´n eso le parec´ ıa a ıa una p´rdida de tiempo. probablemente muchas m´s de las que jam´s ser´ necesarias para lo que hab´ pedido Iv´n. hizo la demostraci´n desde un m´vil y o o ense˜o.

Y as´ poco a poco. Eran tantos los frentes que ten´ abiertos que tuvieron que prescindir ıan del env´ de SMS y buscaron un generador de p´ginas web que les permitiera ıo a dibujar el flujo de navegaci´n en un gr´fico y. A los inventores de ıa ıa los SMS y los webservices. ´l ya no cre´ u e ıa que fueran a ser capaces de ganar la competici´n. un o ıa coyote vegetariano que hab´ venido desde Am´rica a pasar el invierno. A continuaci´n. decidi´ que se o marchaba. resolvi´ un par de defectos que hab´ o ıan surgido durante la demostraci´n y le pidi´ a Iv´n que lo validara. Y lueo a go. De paso. Pablo ıa a le encarg´ que comprobara constantemente los criterios de aceptaci´n que o o ´l hab´ automatizado. Antes que nada. todas las pruebas de ıa. ¡Eso seguro que les ahorrar´ mucho tiempo! Al o ıa poco. fue escribiendo la parte del programa que hac´ que se cumplieran esos ıa criterios de aceptaci´n. A Iv´n. Les pidi´ que le explicaran. a partir de ah´ generar el o a ı. porque no a a les hab´ explicado detalles important´ ıa ısimos para el ´xito del proyecto. avisaba a Hudson y este hac´ una tras otra. fueron elaborando ı. como ya hab´ hecho durante la semana anterior. Cuando Pablo pens´ que. no podr´ hacer m´s o ıa a trabajo que el que ya hab´ discutido. A contio o a nuaci´n. por su experiencia. Pablo le pidi´ un par de veces a Iv´n y a Bernardo que le o a validaran si lo que llevaba hecho hasta aquel momento era de su agrado y les 11 . a´n renunciando a su parte de los beneficios. ¡Este Hudson o ıa a era realmente veloz e incansable! A medida que iba pasando el tiempo. Pablo le hab´ pedido ayuda a su amigo Hudson. Pablo fue. comenz´ a ıa o automatizar los criterios de aceptaci´n acordados con Iv´n y Bernardo. pero era muy r´pido haciendo cosas sencillas. Total. ıa u programa. Sixto. cogi´ la primera de o o ıa o las tarjetas y. qu´ beneficio obten´ la o o e ıa granja con cada nueva funcionalidad. una lista de funcionalidades priorizadas y resumidas en una serie de tarjetas. se march´ a casa a descansar. aceptaci´n que Pablo iba escribiendo. Hudıa e son no sab´ programar. discutiendo con Iv´n y Bernardo o a cu´nto tiempo podr´ tardar en terminarlas. Terminaron culpando a todo el mundo. esqueleto de la aplicaci´n. Adri´n y Sixto ten´ cada vez a ıan m´s problemas. dio por concluida la reuni´n y se ıan o dispuso a trabajar. A la e vaca Paca.Pr´logo o Mientras tanto. porque hab´ incluido una serie de cambios en el programa de la ıa guarder´ que hac´ que no pudieran reutilizar casi nada. Al d´ siguiente. para cada petici´n. As´ cada vez que Pablo hac´ alg´n cambio en su e ıa ı. aprovech´ para anotar a ıa o algunos criterios que luego servir´ para considerar que esa funcionalidad ıan estar´ completamente terminada y eliminar alguna ambig¨edad que fuera ıa u surgiendo. Y cada vez hab´ m´s. harto de ver que Adri´n no valoraba sus aportaciones y que ya a no se iban a usar sus ideas para enviar y recibir los SMS. tarjeta a tarjeta. porque no ten´ ni idea de c´mo funciona una ıan o granja. Pablo se reuni´ con Iv´n y Bernardo (el encargado del o a almac´n) para ver cu´les deber´ ser las siguientes funcionalidades a desarroe a ıan llar. o Mientras tanto.

A eso que solemos llaıa mar “criterios de aceptaci´n”. o ı Pero eran tantos los defectos que hab´ ido acumulando que. En este libro que ahora tienes entre tus manos. entre ellos comentaron m´s de una vez: “¿Qu´ estar´ haciendo Adri´n? ¿C´mo lo a e a a o llevar´?”. es evidente que escribir las pruebas antes de desarrollar la soluci´n es mucho m´s eficaz o a y eficiente. a duras penas. comportamientos inesperados. Iv´n y Bernardo a estaban francamente contentos con el trabajo de Pablo. Al evitar volver a trabajar sobre asuntos ya acabados.. te invito a leer c´mo Carlos explica bien clarito c´mo guiar el desao o o rrollo de software mediante la t´cnica de escribir antes las pruebas (m´s e a conocido como TDD). y despu´s de este inusual e pr´logo. sin embargo. le fallaba otra. Moraleja: Adem´s de toda una serie de buenas pr´cticas y un proceso de desarrollo a a ´gil. Pablo estaba siempre tranquilo de que no estaba estropeando nada viejo con cada nueva modificaci´n. y lo peor de todo: el programa no hac´ lo que hab´ acordado con Iv´n. Por ıa ıa si acaso. Adri´n no hab´ tenido tiempo ıan. que Iv´n hab´ olvidado mencionar en las primeras reuniones porque a ıa daba por sentado que se lo entregar´ Claro. pero en el medio y largo plazo. Pablo hab´ dejado de introducir nuevas ıas ıa caracter´ ısticas al programa porque quer´ centrarse en dar un buen manual de ıa usuario. cada vez que ıa arreglaba una cosa. Pablo le a˜adi´ la posibilidad de automatizar o n o su ejecuci´n e incorporarlos en un proceso de integraci´n continua (que es o o lo que representa su amigo Hudson en este cuento). las diferencias entre ambos ena foques no parecen significativas. lo que sirvi´ para o corregir algunos defectos y cambiar algunas prioridades.Pr´logo o hizo un par de demostraciones durante aquellas 3 semanas. no tuvo ning´n problema en ense˜ar lo que llevaba u n funcionando desde hac´ mucho tiempo y que tantas veces hab´ probado. Pablo hizo algo que Adri´n despreci´: acord´ con Iv´n (el cliente) y con a a o o a Bernardo (el usuario) los criterios mediante los cu´les se comprobar´ que a ıa cada una de las funcionalidades estar´ bien acabada. Adri´n se a qued´ sin dormir un par de noches para as´ poder entregar su programa. a Cuando se acercaba la fecha final para entregar el programa. funcionaba) y fue todo un desastre: ´ mensajes de error por todos sitios. cuando lleg´ la hora de la o demostraci´n. Sin embargo. 12 . En el corto plazo.. ıa ıan a Pablo. Adri´n s´lo pudo ense˜ar el programa instalado en su port´til o a o n a (el unico sitio donde. dos d´ antes de la entrega. De esta manera. o Pablo era m´s eficiente. De hecho. a ıa para nada de eso.

Ha revisado mi redacci´n o corrigiendo mil faltas de ortograf´ y signos de puntuaci´n. Estoy profuna damente agradecido a Juan Guti´rrez Plaza por haber escrito el cap´ e ıtulo 11 y por haber hecho correcciones desde la primera revisi´n del libro hasta la o ultima. ıa o El toque de calidad que dan los dem´s coautores al libro. En primer lugar tengo que agradecer a D´cil Casanova que haya sido a la responsable de calidad n´mero uno en el proyecto. ha o n concluido con un resultado tan gratificante. Espero a a que sigamos trabajando juntos Gregorio :-) Para terminar con los coautores quiero agradecer a Jos´ Manuel Beas que haya escrito el pr´logo del libro a e o pesar de lo muy ocupado que est´ liderando la comunidad ´gil espa˜ola y a a n 13 . es tambi´n un a e detallazo. Quiz´s no se hubiese a escrito nunca. No s´lo tengo que agradecer todo lo much´ o ısimo que me aporta en lo personal sino que adem´s me anim´ constantemente a no publicar el a o libro hasta que estuviera hecho lo mejor posible. Gracias a ´ e Fran Reyes Perdomo tenemos un gran ap´ndice sobre Integraci´n Cont´ e o ınua que de no haber sido por ´l no estar´ en el libro. Ha sido para m´ el m´s dif´ de ı a ıcil escribir de todo el libro y a ning´n revisor le acababa de convencer. Sin ella este libro no u se hubiera escrito de esta forma y en este momento. Gregorio u ha refactorizado medio cap´ ıtulo d´ndole un toque mucho m´s serio. lo har´ hacia los que han tenido una relaci´n m´s directa con el e o a libro.Agradecimientos Una vez o´ a un maestro zen decir que la gratitud que expresa una perı sona denota su estado moment´neo de bienestar consigo misma. Tengo que agradecer cosas a miles de personas pero para no extenderme demasiado. Adem´s de escribir texto han sido buenos revisores. con lo importante que es e ıa esta pr´ctica. Gregorio Mena es el responsable de que el a cap´ ıtulo 1 no sea un completo desastre. Mil gracias Fran. Ha sido un placer discutir juntos tantos detalles t´cnicos. Estoy muy a contento de ver que este proyecto que se empez´ hace casi a˜o y medio.

-D A continuaci´n quiero agradecer a la decena de personas que han le´ o ıdo las revisiones del libro previas a la publicaci´n y que han aportado correccioo nes. que me diera la oportunidad de dirigir un proyecto y carta blanca para aplicar TDD desde el primer d´ porque ıa. 14 . Pedro y Roda. Juan Jorge P´rez e e e L´pez. todos en 2009. aunque finalmente ı queda para un libro futuro. palabras de ´nimo e incluso texto. aportando comentarios t´cnicos de calidad. durante el a˜o que trabaj´ ah´ aprend´ toneladas de cosas. e o Ahora. Pedro Gonz´lez Yanes y Jes´s Alberto e e ıs a u Gonz´lez por abrirme las puertas de la facultad de inform´tica y tambi´n por a a e permitirme que impartiese mi primer curso completo de TDD. Un bonito detalle JM . Menci´n especial a las personas que ayudaron a que saliesen o ´ adelante: Alvaro Lobato y los ya citados Gregorio. Yeray Darias adem´s o a a de revisar. Gracias a los dos grupos que he tenido en Tenerife y al de Sevilla. Sin ıa Marcos y Goyo. De la facultad tengo tambi´n que agradecer a Marcos Colebrook que me diese el empuj´n e o que necesitaba para terminar la carrera. No puedo olvidar al e resto de revisores que han seguido el libro en distintas etapas aportando tambi´n comentarios muy brillantes: N´stor Bethencourt. Mi buen amigo Eladio L´pez de Luis ha dejado o alg´n comentario en cada revisi´n que he ido enviando. Gracias tambi´n a Jos´ Lu´ Roda. quiero a a o agradecer a Rodrigo Trujillo. no deja de intentar abrir puertas a a los estudiantes que est´n terminado ni de preocuparse por la calidad de su a universidad. ıguez de la Rosa. Jos´ Ram´n D´ Jose M. el cambio de plan hubiera hecho que abandonase la idea de terminar la carrera. Agradecimientos especiales a Esteban a Manchado Vel´zquez que tiene much´ a ısimo que ver con la calidad de la redacci´n y que ha sido uno de los revisores m´s constantes.Agradecimientos haciendo de padre de familia. solo aquellos temas de relaci´n directa con TDD. m´s all´ de los que han tenido relaci´n directa con el libro. que la ten´ casi abandonada. Rodrigo es una de n e ı ı las personas que m´s se mueve en la ULL. Pablo Rodr´ o ıguez S´nchez. Hadi Hariri me influenci´ mucho para que partiese el libro en dos y dejase o para ´ste. Alberto Rodr´ u o ıguez Lozano ha sido una de las personas m´s influyentes en las correcciones del a cap´ ıtulo 9. Tambi´n agradezco a todas las a e e personas que han le´ alguna de estas revisiones previas y me han enviado ıdo emails personales de agradecimiento. Rodr´ a e o ıaz. escribi´ el cap´ o ıtulo que le ped´ sobre DDD. Los alumnos de mis cursos de TDD tienen mucho que ver con el resultado final del libro ya que he volcado en ´l muchas de sus dudas y comentarios e t´ ıpicos. V´ ıctor Rold´n y N´stor Salceda. A Esteban Abeledo y al resto del Colegio de Informaticos de Canarias les agradezco mucho hayan homologado nuestros cursos de TDD. ex director de la Oficina de Software Libre de la Universidad de La Laguna (ULL).

Aunque ellos no leer´n estas l´ o a ıneas por ser en espa˜ol. Tambi´n he aprendido mucho e de todos aquellos desarrolladores con los que he trabajado en proyectos open source. a regala muy buenos consejos en mi nueva etapa en iExpertos. una de las mentes e m´s brillantes que he conocido. o Gracias Roberto por brindarnos un libro tan brillante. Innova7 y Rodrigo Trujillo. me hubiese ahorrado bastantes p´rrafos o a de la parte te´rica. En aquel entonces nos cre´ ıamos que ´ramos ´giles e a aunque en realidad lo est´bamos haciendo fatal. por entrenar a tantos equipos ´giles en nuestro pa´ Angel adem´s me a ıs. Pedro fue qui´n me habl´ por a e o primera vez de XP en 2004. a pocos d´ de terminar el proyecto. Si lo hubiese le´ en ıas ıdo octubre. Xavier Quesada o n . Jorge ı. Armando Fumero.-) Alfredo Casado adem´s ha escrito la sinopsis del libro. Inform´tica a Profesional[13]. Luismi e a a u e Cavall´. ı porque han sido claves para que desarrolle mi capacidad docente. Academia ESETEC. cuando Leo me lo regal´. Gracias a la comunidad TDD internacional que se presenta en un grupo de discusi´n de Yahoo. Por ultimo no quiero dejar de decirle a mi familia que sin ellos esto ´ 15 . David Calavera. Ha sido tremendamente enriquecedor cambiar de empresa y de proyectos. Iv´n P´rraga. Xavier Gost. A saber: Ala n fredo Casado. Xavi Albaladejo y Rodrigo Corral. Gracias a Kent Beck y Lasse Koskela por sus libros. Thank you all guys at Buy4Now in Dublin during my year over there in 2007. n quiero dejar claro que sin su ayuda no hubiese sabido resolver tantos problemas t´cnicos. Agust´ ın Benito. Y tambi´n a los que est´n promoviendo XP en Latinoam´rica: Fabi´n e a e a Figueredo. a Quiero saludar a todas las personas con las que he trabajado en mi paso por las muchas empresas en que he estado. Ricardo Rold´n y tantos otros profesionales de la e a comunida Agile Spain. Jes´s P´rez. que han e sido para m´ fuente de inspiraci´n. A quienes han confiado en m´ para impartir cursos de materias diversas.Agradecimientos Gracias a todos los que est´n promoviendo XP en Espa˜a. Un visionario. Juan Palacio. Eric Mignot. un amigo. Angel L´pez. ın u Jim´mez. La experiencia sirvi´ para a o que yo continuase investigando sobre el tema. entre los cuales est´n los coautores y revisores de este a libro. Gracias a Angel Medinilla. Y al pibe o que vos viste nos rrre-ayud´ a traer el Agile Open a Espa˜a. Es una pena que me haya puesto a leerlo cuando ya ten´ ıa escrito este libro. ı o Aprovecho para felicitar a Roberto Canales por su libro. Un libro. De todos he aprendido algo. Carlos Lone y tantos otros. porque de todos he aprendido cosas. Jorge Uriarte. Agradecimientos tambi´n a Pedro Gracia Fajardo. en mi tiempo libre. David Esmerodes. Gracias a los que me han apoyado y gracias tambi´n a los que me han querido e tapar. Leo Antol´ Agust´ Yag¨e. Carlos Peix. Es un pedazo de libro que recomiendo a todo el mundo.

A mis primos. ya que mis padres no pod´ ıan costearmelo. Ella me ayuda siempre que lo necesito. ı.Agradecimientos no ser´ posible. 16 . A mis abuelos por estar siempre ah´ como mis segundos padres. A mis padres por traerme al mundo y ense˜arme tantas cosas. A D´cil de nuevo por todo lo much´ ıa a ısimo que me aporta diariamente. n A mis hermanas por querer tanto a su hermano mayor. A mi t´ Tina por acogerme en su casa durante mis a˜os de universidad y ıa n darme la oportunidad de estudiar una carrera.

Primero en Lanzarote y despu´s en Tenerife. e Amiga 1200. Sin tener suficiente experiencia como desarrollador y ninguna experiencia como empresario ni comercial. En 2003 e entr´ de lleno en el mercado laboral. la mayor parte de mi vida ı o la he pasado en Canarias. Lo e trajo un franc´s al pueblo cordob´s de La Vice e toria en tiempos de Carlos III.Autores del libro Carlos Bl´ Jurado e Nac´ en C´rdoba en 1981. y luego unos cuantos PC. desde el AMD K6 hasta el Intel Core Duo de hoy. pero quer´ ponerlo aqu´ para que los que ıa ı padecen de titulitis vean que el que escribe es titulado. a En 2005 se me ocurri´ la genial idea de montar una empresa de desarrollo o de software a medida con mis colegas. Para m´ el t´ e a ı ıtulo no es ninguna garant´ de profesionalidad. Al principio trabaj´ administrando sise e temas adem´s de programar. hijo de cordobeses ı o pero cuando ten´ 4 a˜os emigramos a Lanzaıa n rote y. He tenido tambi´n Amiga 500. e Mi primer apellido significa trigo en franc´s. La primera vez que gan´ dinero con un desarrollo de software fue en e 2001. salvo algunos a˜os intercalados en los n que viv´ en C´rdoba. Poco despu´s de que instalase Debian GNU/Linux en mi PC. Soy ingeniero t´cnico en inform´tica de sistemas. Cuando ten´ 6 a˜os mi padre trajo a casa un ıa n 8086 y aquello me fascin´ tanto que supe que o me quer´ dedicar a trabajar con ordenadores ıa desde entonces. m´s bien hago un balance negativo ıa a de mi paso por la Universidad. fue toda 17 .

Al final me d´ cuenta de que la odisea me sobrepasaba y no era capaz ı de llevar la empresa a una posici´n de estabilidad donde por f´ dejase de o ın amanecerme sentado frente a la pantalla. trabajo para o la empresa2 . Mi esp´ ıritu emprendedor me lleva a poner en marcha nuevas ideas en la nube. Adem´s me dedico a formar a desarrolladores a impartiendo cursos sobre TDD.com y en el blog de iExpertos. n ı Fueron dos a˜os equivalentes a cuatro o cinco trabajando en otro sitio. cobrando 5000 euros mensuales y sin hacer ni una sola hora extra. Desarrollo software profesionalmente de manera vocacional. mientras estaba o u con el agua al cuello. me brind´ la oporo tunidad de darme cuenta de muchas cosas y valorar cuestionas que antes no valoraba. a 1 2 Quality Assurance El matiz viene del libro. metodolog´ y herramientas o ıa de programaci´n. Aprend´ que la venta era m´s deciı a siva que el propio software. Llegu´ a vivir el boom de los sueldos e e en Dublin. Aprend´ lo importante que era tener un equipo de QA1 y ı me volqu´ con los tests automatizados. Tambi´n aprend´ cosas feas como que Hacienda e ı no somos todos. en 2007 me mand´ a mudar a Irlanda para trabajar e como desarrollador. e En la actualidad he vuelto a emprender.com Habitualmente escribo en www.iExpertos. En lugar de intentar trabajar en mi empresa. Cansado de los morosos y de que la administraci´n p´blica nos tuviera sin cobrar meses y meses. que el notario da u n m´s miedo que el dentista. He escrito la mayor parte del libro con la excepci´n de los fragmentos o que nos han regalado los dem´s autores. cuya web es www. Ver n el negocio del software desde todos los puntos de vista. El Mito del Emprendedor. de Michael E. que los concursos p´blicos se ama˜an.carlosble. ı ıa No pod´ limitarme a probar las aplicaciones 10 minutos a mano antes de ıa entregarlas y pensar que estaban hechas. En 2008 regres´ a Tenerife.Autores del libro una aventura sobrevivir durante los dos a˜os que permanec´ en la empresa. Aprend´ que no pod´ tratar al cliente como si fuera un tester. Gerber 18 . c´digo limpio. que el pez grande se come al peque˜o y muchas a n otras.

Aco tualmente es el Presidente de la asociaci´n Agio le Spain. sobre todo. Puedes localizarlo f´cilmente a trav´s del portal agilismo. Tambi´n e participa en la elaboraci´n de los podcasts de o Podgramando. Y vaya que si los encontr´. una iniciativa de “agilismo. hacia adelante. estuvieran buscando maneras mejores de e hacer software. en a e la lista de correo de Agile Spain o en su blog personal http://jmbeas. y rellenar su “caja de herramientas” o con cosas como Groovy y Grails. 19 . mirar atr´s.es.Autores del libro Jose Manuel Beas En 2008 decidi´ aprovechar sus circunstancias o personales para tomarse un respiro.com”. Y as´ ı. como ´l.es powered by iExpertos. aprovech´ ese tiempo para mejorar su formao ci´n en temas como Scrum asistiendo al curo so de Angel Medinilla. Pero sobre todo vi´ que merec´ la pena poner parte de su o ıa energ´ en revitalizar una vieja iniciativa llamaıa da Agile Spain y buscar a todos aquellos que.iexpertos. que representa a la comunidad agilista en Espa˜a y organizadora de los Agile Open n Spain y las Conferencias Agile Spain. a a los lados y.com.es. Pero tambi´n quiso poe ner su peque˜o granito de arena en el desarron llo y la difusi´n de Concordion. una herramieno ta de c´digo abierto para realizar pruebas de o aceptaci´n.

Cuando el tiempo lo permite. he escrito poco. trabajo como “Agile Coach” en F-Secure donde intento ayudar a equipos de Finlandia. compartimos ideas y experiencias del mundo del software.Autores del libro Juan Plaza Guti´rrez e Escribir. ¿Pero c´mo he llegado hasta aqu´ o ı? Mi devoci´n los ordenadores me llev´ a estuo o diar la carrera de ingenier´ en inform´tica en ıa a la UPM de Madrid. En las largas y oscuras “tardes” del invierno Finland´s e estudi´ un master en “industrial management” e antes de cambiar a F-Secure. Entre o n cervezas (una fase importante para asimilar lo aprendido). TDD). e Tambi´n he sacado del ba´l de los recuerdos mi e u LaTeX y he revisado mucho. ıa ıa http://es. y me habl´ adem´s o a del proyecto en el que se encontraba embarcado. en el cual me brind´ la oportunidad de o participar con un peque˜o ap´ndice sobre inten e graci´n continua.linkedin. much´ ısimo. Conoc´ a o u ı Carlos Bl´ en un provechoso curso de TDD que e imparti´ para un grupo de compa˜eros. escribo en http://agilizar. ha sido discutir y rea discutir con Carlos sobre cual era la mejor forma de hacer esto y aquello (siempre ha ganado ´l). por supuesto. Trabaj´ en Espa˜a por un e n tiempo antes de decidir mudarme a Finlandia en el 2004 para trabajar en Nokia. que intentamos. Cuando no estoy discutiendo con la gente de Agile Spain. Rusia y Francia en su transici´n a las metodolog´ o ıas a ´giles (tanto en gesti´n como en pr´cticas de o a software entre las que se incluye.es Soy un apasionado desarrollador de software interesado en “pr´cticas ´giles”. He intentado hacer algo con sentido con mi oxidado Python en el cap´ ıtulo 11 aunque m´s que escribir.com/in/franreyesperdomo Fran Reyes Perdomo 20 . pero aprender. Llevo cerca de a a 4 a˜os trabajando para la r´ n ıgida Administraci´n p´blica con un fantastico equipo. Una pr´ctica. Malasia. o a forme parte del d´ a d´ en nuestros proyectos.

com. Auno que en mi caso el camino no es doloroso. Ha sido esta forma de ver nuestra profesi´n la que me o llev´ a colaborar con Carlos en este lio bro. 21 .Autores del libro Gregorio Mena Mi corta vida profesional ha sido suficiente para dar sentido a la frase de Horacio “Ning´n hombre ha llegado a la u excelencia en arte o profesi´n alguna. Pensaba aportar algo. sino apasionante. Por ello debo dar a Carlos las gracias por ofrecerme esta oportunidad y por el esfuerzo que ha hecho para que este libro sea una realidad para todos los que vemos en la formaci´n o continua el camino al ´xito. formaci´n. por lo que he organizado un o curso de TDD impartido con gran acierto por Carlos Ble y voy a participar en futuras ediciones. pues tambi´n colaboro con la platae forma ScrumManager. o sin haber pasado por el lento y doloroso proceso de estudio y preparaci´n”. Trabajo desde iExpertos para que entre todos hagamos posible el primer curso de Scrum en Canarias. Siguiendo esta filosof´ intento formarme y fomentar la ıa.blogspot. e Habitualmente escribo en http://eclijava. pero lo cierto es que lo que haya podido aportar no tiene comparaci´n con lo que he tenido o la suerte de recibir.

Autores del libro 22 .

a Las referencias bibliogr´ficas tienen un formato como este:[3]. Cuando se citan elementos a de dichos bloques o sentencias del lenguaje. En la era de la infoxicaci´n3 o tal repetici´n sobra. H´gase a la idea de que tiene 600 p´ginas en lugar a a de 300. Sobre los paquetes de c´digo fuente que acompa˜an a este libro (listo o n para descarga en la web).wiktionary. es que soy tan mal escritor que. Las ultimas a ´ p´ginas del libro contienen la bibliograf´ detallada correspondiente a todas a ıa estas referencias. 3 http://es. Ser´ como ver una pel´ a ıcula dos veces: en el segundo pase uno aprecia muchos detalles que en el primero no vio y su impresi´n sobre el contenido puede o variar. Otros libros de divulgaci´n repiten determinadas ideas a lo largo de varios o cap´ ıtulos con el fin de reforzar los conceptos. el escrito en C# es un proyecto de Microsoft Visual Studio 2008 (Express Edition) y el escrito en Python es una carpeta de ficheros. En realidad.org/wiki/infoxicaci %C3 %B3n 23 . por lo que es recomendable a una segunda lectura del mismo para quienes desean profundizar en la materia. se usa este tipo de letra. Para hacerlos m´s legibles se incluyen dentro de o a rect´ngulos que los separan del resto del texto. He intentado minimizar las repeticiones de conceptos o para que el libro se pueda revisar r´pidamente. A lo largo del texto aparecen referencias a sitios web en el pie de p´gina. si no lee el libro dos veces no lo va a entender.Convenciones y Estructura Este libro contiene numerosos bloques de c´digo fuente en varios leno guajes de programaci´n. a Todas ellas aparecen recopiladas en la p´gina web del libro.

Convenciones y Estructura 24 .

el conocimiento no es propiedad de nadie sino que es como la energ´ simplemente fluye. empezando por la entrada de la Wikipedia4 .wikipedia. sino en todos los campos del saber.Net gracias al framework Mono6 . Este argumento es uno de los principales pilares del software libre5 . no s´lo en lo referente al o avance cient´ ıfico y tecnol´gico. Gracias a la libre circulaci´n del o conocimiento hemos llegado a donde estamos hoy. De ıa. o Afortunadamente. A El libro ha sido maquetado usando LTEX. En Internet existe mucha documentaci´n sobre la libertad del conocimieno to. se transforma y nos transforma. La intenci´n es hacerlo llegar a toda persona que o desee aprovecharlo. es de todos los seres. no tiene due˜o.org/wiki/Conocimiento libre http://es. e a El conocimiento contenido en este libro no pertenece al autor. concretamente con teTex y MikTeX(software libre) y ha requerido de multitud de paquetes libres desarro- http://es.org/wiki/C´digo libre o 6 http://www. No pertenece a nadie en concreto. al que tanto tengo que agradecer. De no haber sido por Mono probablemente no hubiera conocido C#. Las principales herramientas que utilizaremos en este libro est´n basadas en softa ware libre: frameworks xUnit. desarrollado por ıa Novell con licencia libre. hecho no pertenece a las personas. n Observando a los animales podemos ver c´mo los padres ense˜an a su prole o n las t´cnicas que necesitar´n para desenvolverse en la vida.mono-project. sistemas de control de versiones y frameworks para desarrollo de software.La Libertad del Conocimiento El conocimiento ha sido transmitido de individuo a individuo desde el comienzo mismo de la vida en el planeta. Personalmente me he convertido en usuario de la tecnolog´ Microsoft .com 5 4 25 .wikipedia.

simplemente me mantengo al margen de esa contienda. 26 . Pese a mi simpat´ por el software de fuente abierta. que raya en la mala educaci´n y empa˜a el buen hacer de o n los verdaderos profesionales. o Dokuwiki.La Libertad del Conocimiento llados por la comunidad. quien quiera. El versionado de los ficheros de texto se ha llevado a cabo con Subversion. este libro va m´s all´ de ıa a a la dicotom´ software libre/software propietario y se centra en t´cnicas apliıa e cables a cualquier lenguaje de programaci´n en cualquier entorno. lo use para sus propias publicaciones. con el cual tambi´n he editado c´digo. Los diagramas de clases los ha generado SharpDevelop. En la web del libro se encuentra el esqueleto con el que se ha maquetado para que. Vim y Emacs. Es mi deseo aclarar que mi posici´n personal no o es ni a favor ni en contra del software propietario. Estoy muy agradecido a todos e o los que han escrito todas estas piezas. Para la edici´n del texto se ha usado Texmaker. Uno de los o peores enemigos del software libre es el fanatismo radical de algunos de sus evangelizadores.

La Web del Libro Los enlaces a sitios web de Internet permanecen menos tiempo activos en la red que en las p´ginas de un libro impreso.dirigidoportests. dado que el libro es gratis y ni siquiera su venta en formato papel se traducir´ en algo de beneficio. a 27 . Si bien es cierto que escribir el libro ha sido un placer. Si el a libro le gusta y le ayuda. Ha supuesto casi a˜o y medio de trabajo n y.com Si el lector desea participar informando de enlaces rotos. la lista de referencias se a mantendr´ actualizada en un sitio web dedicado al libro: a http://www. podr´ hacerlo diria gi´ndose a la web del libro o bien mediante correo electr´nico al autor: e o carlos[Arroba]iExpertos[Punto]com El c´digo fuente que acompa˜a al libro se podr´ descargar en la misma o n a web. le invito a que haga una donaci´n para que en el o futuro puedan haber m´s libros libres como este. en la web es posible hacer donaciones. tambi´n es cierto e que ha sido duro en ocasiones.

La Web del Libro 28 .

En mi opini´n.iexpertos. e n ıses No cabe duda de que los que inventaron la computaci´n y el software nos o siguen llevando ventaja en cuanto a conocimiento se refiere. 7 http://jmbeas. Es n n comprensible y normal que la mayor´ se limite a leer lo que est´ documentado ıa a en espa˜ol.¿Cu´l es el Objetivo del Libro? a El objetivo fundamental del libro es traer a nuestro idioma. n conocimiento t´cnico que lleva a˜os circulando por pa´ de habla inglesa. al menos el ingl´s t´cnico escrito.com/hablar-ingles-es-facil-si-sabes-como/ 29 . Se aprende much´ ısimo porque no s´lo lo usan o los nativos de habla inglesa sino que se ha convertido en el idioma universal en cuanto a tecnolog´ Sin embargo. porque a o ı d´ de hoy cada vez m´s clientes apuestan por el outsourcing. Cuando no dominamos a e el ingl´s nos perdemos muchos matices que son significativos7 . Conozco a pocos compa˜eros que hagan este esfuerzo. S´lo as´ competiremos en igualdad de condiciones. no nos queda m´s remedio ıa a que dominar el ingl´s. a˜os era muy reacio a leer textos en ingl´s. e Apenas hay libros sobre agilismo en espa˜ol. sea como fuere. A d´ de hoy. reconozco que yo mismo hace unos ıa. no podeo mos perder la oportunidad de subirnos al carro de las nuevas t´cnicas de e desarrollo y difundir el conocimiento proporcionado por nuestros compa˜eros n angloparlantes. Conocimiento ıa a es poder. Los unicos libros novedosos n ´ que se editan en nuestra lengua relacionados con el mundo del software. aunque el buen profesional deber´ tener presente las m´ltiples a a u ventajas que le aportar´ el dominio del ingl´s escrito. o es una cuesti´n cultural en su mayor parte pero. Por n a eso concluyo que hay que traer la informaci´n a nuestro idioma para llegar o a m´s gente. el espa˜ol. por suerte o por desgracia. Al fin y al cabo es de los idiomas m´s hablados del planeta. He tenido que hacer un gran n e esfuerzo y leer mucho con el diccionario en la mano hasta llegar al punto en que no me cuesta trabajo leer en ingl´s (adem´s de vivir una temporada e a fuera de Espa˜a). y es conveniente e e e leer mucho en ese idioma.

tan s´lo le invito a que e o explore con una mente abierta las t´cnicas aqu´ recogidas. ı Ahora que existen editoriales en la red tales como Lulu. o No me cabe duda de que las ideas planteadas en este libro pueden resultarles controvertidas y desafiantes a algunas personas. ¿Qui´n se anima e con un libro sobre Programaci´n Extrema?. que las ponga en e ı pr´ctica y despu´s decida si le resultan o no valiosas. Angel Medinilla ha traducido el libro de Henrik Kniberg[8]. ya no hay excusa para no publicar contenidos t´cnicos. n a Est´ muy bien que haya libros sobre Java. El unico libro sobre agilismo que hay a d´ de ´ ıa hoy es el de Scrum. a e 30 . Scrum y XP desde las Trincheras y Leo Antol´ ha traducido The Scrum Primer.¿Cu´l es el objetivo del libro? a tratan sobre tecnolog´ muy espec´ ıas ıficas que hoy valen y ma˜ana quiz´s no. Personalmente me daba reparo e afrontar este proyecto sin saber si alguna editorial querr´ publicarlo pero ıa ya no es necesario que las editoriales consideren el producto comercialmente v´lido para lanzarlo a todos los p´blicos.com. Por otro lado. El lector no tiene por qu´ coincidir conmigo en todo lo que se expone. de Juan Palacio[15]. Otro objetivo del libro es animar a a u los muchos talentos hispanoparlantes que gustan de compartir con los dem´s. sobre . Estos regalos son de agradecer.Net o sobre Ruby. pero no a tenemos que limitarnos a ello. a a que nos deleiten con su conocimiento y sus dotes docentes.

Parte I Base Te´rica o 31 .

.

wikipedia. Ya veremos m´s adelante. o a como el militar o el cient´ ıfico. Para llegar al momento en el que el primer e computador que almacenaba programas digitalmente corri´ exitosamente su o primer programa. los componentes hardware acaban duplicando su capacidad cada a˜o. que el concepto es realmente simple y queda plasmado en cuatro postulados muy sencillos. Todo esto tiene una consecuencia evidente: los computadores ya no s´lo se encuentran en ´mbitos muy restringidos. Pero creo que llegar a comprenderlo requiere un poco m´s de esfuerzo y. en este mismo cap´ a ıtulo. los computadores van reduciendo su tama˜o consideran blemente. seguramente.org/wiki/Moore %27s Law 33 .wikipedia. se reducen los costes de producci´n del hardware y avanzan las o comunicaciones entre los sistemas. A la vez. pero debemos entender que casi no ha transcurrido tiempo como para que exijamos estabilidad. en muy poco tiempo. Esto nos o hace reflexionar sobre el hecho de que nos encontramos ante una disciplina que es apenas una reci´n nacida frente a otras centenarias con una base de e conocimiento s´lida y estable.org/wiki/Tom Kilburn http://en. o o Ya desde el punto de partida es necesario hacer una reflexi´n.Cap´tulo ı 1 El Agilismo Para definir qu´ es el agilismo. aparecen m´quin a nas muy potentes capaces de procesar miles de millones de operaciones en segundos. Con lo que. Por nuestra propia naturaleza nos oponemos o al cambio. s´lo tenemos que remontarnos al verano de 19481 . la a mejor manera sea haciendo un repaso a la historia del desarrollo de software. Siguiendo la ley de Moore2 . probablemente basten un par de l´ e ıneas. para al final ver como el agilismo no es m´s que una respuesta l´gica a los a o problemas que la evoluci´n social y tecnol´gica han ido planteando. 1 2 http://en. Al otear la o historia nos damos cuenta de que el origen del desarrollo de software est´ a a unas pocas d´cadas de nosotros.

a En este punto. o Gesti´n Predictiva de proyectos: es una disciplina formal de gesti´n. se ofrecen soluciones a sistemas cada vez m´s a complejos y se plantean nuevas necesidades a una velocidad vertiginosa que implican a los desarrolladores de Software. Esta nueva disciplina se basa en la planificaci´n. surge la necesidad de profesionalizar la gesti´n a o de proyectos para poder abordar el desarrollo de complejos sistemas que requer´ coordinar el trabajo conjunto de equipos y disciplinas diferentes en la ıan construcci´n de sistemas unicos. como ocurre en otras ´reas. Posteriormente.wikipedia. unos pocos “aventureros” empiezan a desarrollar las primeras aplicaciones que dan respuesta a las nuevas necesidades pero es un reto muy complejo que no llega a ser resuelto con la inmediatez y la precisi´n necesao rias. relojes. podemos sacar conclusiones reveladoras que luego nos llevar´n a la mejor comprensi´n del agilismo. En la d´cada de los cincuenta nos encontramos con otro hito impore tante. disciplinado y cuantificable al a desarrollo. Los proyectos no llegan a buen puerto. la gesti´n predictiva de proyectos establece como criterios de ´xito o e 3 http://en. ejecuci´n y seguimiento a trav´s de procesos o o e sistem´ticos y repetibles. . o o basada en la planificaci´n.). e a Hasta este punto.org/wiki/Principio de Pareto 4 34 . Por a o un lado. Dicho principio nos indica que la calidad del o resultado depende b´sicamente de la calidad de los procesos.Cap´ ıtulo 1 Al extenderse el ´mbito de aplicaci´n del hardware (ordenadores persoa o nales. juegos.net/files/Flexibilidad con Scrum. ejecuci´n o o o y seguimiento a trav´s de procesos sistem´ticos y repetibles. ya que es en 1968 cuando se acu˜a este t´rmino en ıa n e la NATO Software Engineering Conference3 . con el breve recorrido hecho. a Producci´n basada en procesos: se crean modelos de procesos basao dos en el principio de Pareto5 .pdf 5 http://es. operaci´n y mantenimiento del software.. a permita aplicar un enfoque sistem´tico. Sin informaci´n y conocimieno to suficiente.wikipedia.org/wiki/Software engineering http://www. Los esfuerzos realizados producen tres ´reas de conocimiento que se rea velaron como estrat´gicas para hacer frente a la crisis del software4 : e Ingenier´ del software: este t´rmino fue acu˜ado para definir la neıa e n cesidad de una disciplina cient´ ıfica que.En esta conferencia tambi´n se e acu˜a el t´rmino crisis del software para definir los problemas que estaban n e surgiendo en el desarrollo y que hemos comentado anteriormente. la industria del autom´vil o ´ o sigui´ estos pasos. empleado con buenos resultados en la producci´n industrial.. hemos hablado s´lo de desarrollo de software y no de o ingenier´ de software.navegapolis. En el ´mbito militar. o lo hacen muy tarde.

26-30 35 . el objetivo es ver que el agilismo es la respuesta a una necesidad. o La vida de los productos es cada vez m´s corta y una vez en el mercado. para las que era m´s rentable este producto que las posibles a novedades pero. el tiempo de vida de un producto acabado era muy largo. Estas particularidades tan caracter´ ısticas del software no tuvieron cabida en la elaboraci´n del modelo m´s ampliamente seguio a do hasta el momento: El modelo en cascada. Para ello. Est´ basado en el o a a ciclo convencional de una ingenier´ y su visi´n es muy simple: el desarrollo de ıa o software se debe realizar siguiendo una secuencia de fases. generaba beneficios a las empresas. se empiezan a emular modelos industriales e ingenieriles que surgieron en otros ´mbitos y con otros desencadenantes. Modelo en cascada Este es el m´s b´sico de todos los modelos6 y ha servido como bloque de a a construcci´n para los dem´s paradigmas de ciclo de vida.Cap´ ıtulo 1 1. el trabajo comienza estableciendo los requisitos de todos los elementos del sistema y luego asignando alg´n u subconjunto de estos requisitos al software.1. Lo cierto es que ni los productos de software se pueden definir por completo a priori. son a novedad apenas unos meses. Pag. Esto obliga a e cambiar la filosof´ de las empresas. ni son totalmente predecibles. Adem´s. 1. para entender su o funcionamiento y poder concluir por qu´ en determinados entornos era pree ciso un cambio. Como ya comentaba al principio. durante este tiempo. los a procesos aplicados a la producci´n industrial no tienen el mismo efecto que o en desarrollo de software. se asume que el proyecto se desarrolla en un entorno estable y predecible. quedando fuera de ´l enseguida. Por otro. 6 Bennington[4]. Cada etapa tiene un conjunto de metas bien definidas y las actividades dentro de cada una contribuyen a la satisfacci´n de metas de esa fase o quiz´s a una subsecuencia o a de metas de la misma. al principio. a partir de los ochenta. Modelo en cascada obtener el producto definido en el tiempo previsto y con el coste estimado. El arquetipo del ciclo de vida abarca las siguientes actividades: Ingenier´ y An´lisis del Sistema: Debido a que el software es siempre ıa a parte de un sistema mayor. que se deben adaptar a este cambio consıa tante y basar su sistema de producci´n en la capacidad de ofrecer novedades o de forma permanente. ya que en un caso se aplican sobre m´quinas y en a otro.1. ni son inmutables. sobre personas. esta situaci´n empieza a cambiar. a Debemos tener en cuenta que. En el siguiente punto de este cap´ ıtulo veremos una breve descripci´n de dicho modelo.

El proceso o de dise˜o traduce los requisitos en una representaci´n del software con n o la calidad requerida antes de que comience la codificaci´n. ı o Dise˜o: el dise˜o del software se enfoca en cuatro atributos distintos n n del programa. Modelo en cascada Cap´ ıtulo 1 An´lisis de los requisitos del software: el proceso de recopilaci´n de a o los requisitos se centra e intensifica especialmente en el software. el detalle procedimental y la caracterizaci´n de la interfaz. o Codificaci´n: el dise˜o debe traducirse en una forma legible para la o n maquina. Los cambios ocurrir´n debidos a que se haya encontrado a errores. a que el software deba adaptarse a cambios del entorno externo (sistema operativo o dispositivos perif´ricos) o a que el cliente requiera e ampliaciones funcionales o del rendimiento. la estructura de los datos. e 36 . Mantenimiento: el software sufrir´ cambios despu´s de que se entrega a e al cliente. es dif´ para el cliente establecer todos los ıcil requisitos expl´ ıcitamente. as´ que debemos atender tambi´n a ´l y ı e e saber que: Los proyectos reales raramente siguen el flujo secuencial que propone el modelo. realizando pruebas que aseguren que la entrada definida produce los resultados que realmente se requieren. puede ser desastroso. El ciclo de vida cl´sico lo requiere y tiene a dificultades en acomodar posibles incertidumbres que pueden existir al comienzo de muchos productos. En el modelo vemos una ventaja evidente y radica en su sencillez. Hasta llegar a las etapas finales del proyecto no estar´ disponible una versi´n operativa del programa. el rendimiento y las interfaces requeridas. Un a o error importante que no pueda ser detectado hasta que el programa est´ funcionando.1. a Prueba: una vez que se ha generado el c´digo comienza la prueba del o programa. Si el dise˜o se realiza de una manera detallada. ya que sigue los pasos intuitivos necesarios a la hora de desarrollar el software. la arquitectura del software. El cliente debe tener paciencia. Normalmente. la codificaci´n n o puede realizarse mec´nicamente. La prueba se centra en la l´gica interna del software y en o las funciones externas. Pero el modelo se aplica en un contexto. El ingeniero de software debe comprender el ´mbito de la informaci´n del a o software as´ como la funci´n. Siempre hay iteraciones y se crean problemas en la aplicaci´n o del paradigma.1. al principio.

Hablemos de cifras 1. ITIL. con el 61 % de la funcionalidad prometida) Atendiendo a estos resultados poco esperanzadores. o Microsoft. Factores e que se centran.com 37 . en 2004 los resultados segu´ sin ser alentadores: ıan Porcentaje de proyectos exitosos: crece hasta el 29 %. Sin embargo. la industria invirti´ varios miles de millones de d´lares en el desan o o rrollo y perfeccionamiento de metodolog´ y tecnolog´ (PMI. Porcentaje de proyectos fracasados: 71 %. Seg´n el informe de Standish.2. Para ello nos basaremos en los estudios realizados por un conjunto de profesionales de Massachussets que se uni´ en o 1985 bajo el nombre de Standish Group7 . por u orden de importancia. CMMI.Cap´ ıtulo 1 1.) A lo largo del tiempo. Oracle.). son: Escasa participaci´n de los usuarios o Requerimientos y especificaciones incompletas Cambios frecuentes en los requerimientos y especificaciones Falta de soporte ejecutivo Incompetencia tecnol´gica o 7 http://www. fundamentalmente.standishgroup. sobre los factores que inciden en el ´xito o fracaso de los proyectos de IT. las diez causas principales de los fracasos. durante los ultimos ´ diez a˜os. en los proyectos de software y se aplican tanto a los desarrollos como a la implementaci´n de paquetes (SAP. El objetivo de estos profesionales era obtener informaci´n de los proyectos fallidos en tecnolog´ de la informaci´n o ıas o (IT) y as´ poder encontrar y combatir las causas de los fracasos. Hablemos de cifras Quiz´s nos encontremos en un buen punto para dejar de lado los datos a te´ricos y centrarnos en cifras reales que nos indiquen la magnitud del proo blema que pretendemos describir. El buen ı hacer de este grupo lo ha convertido en un referente.2. el Standish Group revel´ 50. a nivel mundial. en o promedio.000 proyectos fallidos o y en 1994 se obtuvieron los siguientes resultados: Porcentaje de proyectos que son cancelados: 31 % Porcentaje de proyectos problem´ticos: 53 % a Porcentaje de proyectos exitosos: 16 % (pero estos s´lo cumplieron. ıas ıas etc. etc.

despu´s se implementa y. a En 2001.1. sino que deber´ o ıamos recapacitar sobre si estamos aplicando las pr´cticas adecuadas. 17 representantes de nuevas metodolog´ y cr´ ıas ıticos de los modelos de mejora basados en procesos se reunieron. al que. son factores humanos. Conoc´ perfectamente las desventajas del cl´sico modelo en cascada donde ıan a primero se analiza.com/manifiesto agil manifiesto es de Agile Spain http://www. ıas a o 1. Estos profesionales. El manifiesto ´gil a Hasta ahora hemos visto que quiz´s para algunos proyectos se est´ reaa e lizando un esfuerzo vano e incluso peligroso: intentar aplicar pr´cticas de a estimaci´n. El manifiesto ´gil a Cap´ ıtulo 1 Falta de recursos Expectativas no realistas Objetivos poco claros Cronogramas irreales Nuevas tecnolog´ ıas Cabe destacar de estos resultados que siete de los factores nombrados. El manifiesto ´gil8 se compone de cuatro principios. las metodolog´ ´giles y la gesti´n de proyectos.agile- 38 . con una dilatada experiencia coa mo aval. por ultimo (en n e ´ algunos casos). convocados por Kent Beck.3. el agilismo intenta dar respuesta.3. una y otra vez hasta la saciedad. o En el libro de Roberto Canales[13] existe m´s informaci´n sobre los m´toa o e dos en cascada. para discutir sobre el desarrollo de software. llevaban ya alrededor de una d´cada utilizando t´cnicas que les e e fueron posicionando como l´ ıderes de la industria del desarrollo software. luego se dise˜a. a Es peque˜o pero bien cargado de significado: n 8 La traducci´n del o spain. Las cifras evidencian la existencia de un problema. se escriben algunos tests autom´ticos y se martiriza a un a grupo de personas para que ejecuten manualmente el software. No es conveniente pensar o o ıa que estas pr´cticas son malas en s´ mismas o que los fracasos se deben a una a ı mala aplicaci´n de estas. planificaci´n e ingenier´ de requisitos. como veremos a continuaci´n. Fue un grito de ¡basta ya! a las pr´cticas tradicionales.

html & Dave Thomas.agilemanifesto. Alistair Cockburn. Jeff Sutherland. D´ndoles el a lugar y el apoyo que necesitan y confiando en ellos para hacer el trabajo. Los procesos ´giles aprovechan al cambio para ofrecer a una ventaja competitiva al cliente. Robert C.3. Brian Marick. Ward Cunningham. Jim Highsmith. James Grenning. De hecho es com´n entregar cada tres o u cuatro semanas. Construimos proyectos entorno a individuos motivados. a Kent Beck. Arie van Bennekum. Steve Mellor. Andrew Hunt. Entregamos software que funciona frecuentemente. aunque los elementos a la derecha tienen valor. % principios publicados en 39 . Las personas del negocio y los desarrolladores deben trabajar juntos diariamente a lo largo de todo el proyecto. Ron Jeffries. A trav´s de esta experiencia hemos aprendido a valorar: e Individuos e interacciones sobre procesos y herramientas Software que funciona sobre documentaci´n exhaustiva o Colaboraci´n con el cliente sobre negociaci´n de cono o tratos Responder ante el cambio sobre seguimiento de un plan Esto es. o 9 Traducci´n o libre de los http://www.Cap´ ıtulo 1 ' 1. incluso en las etapas finales del desarrollo. Ken Schwaber. entre un par de semanas y un par de meses. Martin Fowler. Tras este manifiesto se encuentran 12 principios de vital importancia para entender su filosof´ 9 : ıa Nuestra m´xima prioridad es satisfacer al cliente a trav´s de entregas a e tempranas y continuas de software valioso. El m´todo m´s eficiente y efectivo de comunicar la informaci´n hacia e a o y entre un equipo de desarrollo es la conversaci´n cara a cara.org/principles. El manifiesto ´gil a $ Estamos descubriendo mejores maneras de desarrollar software tanto por nuestra propia experiencia como ayudando a terceros. Jon Kern. Martin. nosotros valoramos por encima de ellos los que est´n a la izquierda. Los requisitos cambiantes son bienvenidos. Mike Beedle.

pero el estudio de ambas en su totalidad queda fuera del alcance de este libro. existiendo m´todos para organizar ıas a e equipos y t´cnicas para escribir y mantener el software. concretamente e TDD10 y Programaci´n por Parejas11 . Por ilustrarlo a modo de alternativa al modelo en cascada: podemos gestionar el proyecto con Scrum y codificar con t´cnicas de XP. La atenci´n continua a la excelencia t´cnica y el buen dise˜o mejora o e n la agilidad. requisitos y dise˜os emergen de la auton organizaci´n de los equipos. A d´ de hoy. XP) como o forma de atacar la implementaci´n del producto y hacia Scrum como forma o de gestionar el proyecto. Los patrocia nadores. La simplicidad. El abanico de metodolog´ ´giles es amplio. desarrolladores y usuarios deben poder mantener un ritmo constante. o A intervalos regulares.4.4. ¿En qu´ consiste el agilismo?: Un enfoque e pr´ctico a El agilismo es una respuesta a los fracasos y las frustraciones del modelo en cascada. me e inclino hacia la Programaci´n Extrema (eXtreme Programming. es esencial. o Este libro no pretende abarcar el vasto conjunto de t´cnicas y metodoe log´ del agilismo pero. o o Test Driven Development o Desarrollo Dirigido por Test Pair Programming o Programaci´n por Parejas. el equipo reflexiona sobre c´mo ser m´s eficaces. 1. o a a continuaci´n mejoran y ajustan su comportamiento en consecuencia. ¿En qu´ consiste el agilismo?: Un enfoque pr´ctico e a Cap´ ıtulo 1 La principal medida de avance es el software que funciona.1. Las mejores arquitecturas. considerando la poca literatura en castellano que ıas existe actualmente sobre este tema. las metodolog´ ´giles de desarrollo de software ıa ıas a est´n en boca de todos y adquieren cada vez m´s presencia en el mundo a a hispano. sin olvidar la propiedad colectiva del o c´digo y la Integraci´n Continua12 . Personalmente. V´anse en la bibliograf´ e ıa los textos relacionados con XP para mayor informaci´n o 12 V´ase el ap´ndice sobre Integraci´n Continua al final del libro e e o 11 10 40 . si bien llevan siendo usadas m´s de una d´cada en otros pa´ a e ıses. Los procesos ´giles promueven el desarrollo sostenible. merece la pena publicar el manifiesto. es otra de las t´cnicas que como e ponen XP y que no vamos a estudiar en detalle en este libro.

ya no se le considera un oponente. se escriben pruebas automatizadas. en a lugar de ser consecutivas. se escriben antes que n o el mismo. Las iteraciones suelen durar de dos a seis semanas y en cada una de ellas se habla con el cliente para analizar requerimientos. o dentro del agilismo se contempla una t´cnica en la que las pruebas son una e herramienta de dise˜o del c´digo (TDD) y. La automatizaci´n de procesos es uno de sus pilares.4. arquitecto. No es casual que hayamos situado las pruebas autom´ticas o a antes de la escritura de nuevo c´digo ya que.) pierde sentido y los roles se disponen sobre un eje horizontal en lugar de vertical. La finalidad o de los distintos m´todos que componen el agilismo es reducir los problemas e cl´sicos de los programas de ordenador. el agilista reconoce que el softa ware es propenso a errores por la naturaleza de quienes lo fabrican y lo que hace es tomar medidas para minimizar sus efectos nocivos desde el principio. a la par que dar m´s valor a las a a personas que componen el equipo de desarrollo del proyecto. satisfaciendo al cliente. como veremos en este libro. junior . Al igual que en el e modelo tradicional. Llegado el caso las pruebas se consideran ejemplos. es m´s. Del control se encargar´n o a los procesos autom´ticos que nos avisar´n de posibles problemas o puntos a a a mejorar. puliendo adem´s los a detalles t´cnicos (no resolviendo defectos sino puliendo). analista de negocio. La estrategia de juego ya no es el control sino la colaboraci´n y la confianza. En lugar de trabajar por a horas. se escriben l´ ıneas de c´digo nuevas y se mejora c´digo existente.Cap´ ıtulo 1 1. desarrollo y pruebas pero.. trabajamos por objetivos y usamos el tiempo como un recurso m´s y a no como un fin en s´ mismo (lo cual no quiere decir que no existan fechas de ı de ah´ el nombre de Scrum. No busca desarrolladores perfectos sino que reconoce que los humanos nos equivocamos con frecuencia y propone t´cnicas que nos aportan confianza a e pesar ello. requerimientos que pueden ser confirmados (o validados) por una m´quina (validaci´n a o automatizada). est´n solapadas. Esta combinaci´n de etapas se a o ejecuta repetidas veces en lo que se denominan iteraciones.. donde cada cual cumple su cometido pero sin estar por encima ni por debajo de los dem´s. La jerarqu´ cl´sica (director t´cnico. a cada vuelta (iteraci´n). Al cliente se le o o ense˜an los resultados despu´s de cada iteraci´n para comprobar su aceptan e o ci´n e incidir sobre los detalles que se estimen oportunos o incluso reajustar o la planificaci´n. por tanto. palabra del argot del Rugby ı e usada para designar la uni´n de los jugadores en bloque o 13 41 . se alimenta con nuevos requerimientos o aproximaciones m´s o a refinadas de los ya abordados en iteraciones anteriores. ¿En qu´ consiste el agilismo?: Un enfoque pr´ctico e a Agilismo no es perfeccionismo. que se traduce por Mel´. existen fases de an´lisis. al desarrollador y al analista de negocio. formando una pi˜a13 y el cliente es parte n de ella. El viejo modelo en cascada se transforma en una noria que. Todo el equipo trabaja unido. ıa a e programador senior.

aunque la empresa o pueda tener una planificaci´n a muy alto nivel que cubra m´s tiempo. Dichas historias contienen los requisitos de negocio y se ordenan por prioridad seg´n las necesidades del u cliente. Nos encontramos ante el famoso a “divide y vencer´s”. Cuando los proyectos son muy grandes y hace falta m´s personal. a El an´lisis no es exhaustivo ni se dilata indefinidamente antes de empezar a la codificaci´n. En cualquier m´todo ´gil. los equipos deben ser peque˜os. Los cambios en los requisitos se suman a la planificaci´n de las iteraciones o siguientes y se priorizan junto con las dem´s tareas pendientes. menores de seis meses. En el an´lisis buscamos cu´les son a a las historias de usuario y. El analista de negocio adoptar´ el rol de due˜o del producto cuando el a n cliente no pueda participar tan frecuentemente como nos gustar´ y camıa biar´ los cientos de p´ginas de documentaci´n en prosa por tests de acepa a o taci´n14 lo suficientemente claros como para que el cliente los apruebe y o la m´quina los valide. se e obtienen resultados satisfactorios sin lugar para el caos. al final de la iteraci´n. Hablaremos sobre las a historias de usuario en el cap´ ıtulo de ATDD. en los cuales los analistas estar´n asignados con tiempo m´s reducido. Cada requisito debe implementarse en un m´ximo de una semana para a que. Ejecutando las diversas t´cnicas que engloba. esto es. el cliente pueda ver funcionalidad con valor de o negocio.4. a fin de desarrollar antes unas u otras. Evia o En el cap´ ıtulo sobre ATDD se describe este proceso Esta cifra puede ser relativa a las personas por grupo de trabajo. a Los planes se hacen frecuentemente y se reajustan si hace falta. se crean varios equipos. Por ejemplo con 16 desarrolladores y 2 analistas pueden hacerse 4 grupos de 4 desarrolladores y un analista pero cada analista en 2 grupos 15 14 42 . sino que se acota en el tiempo y se encuadra dentro de cada o iteraci´n y es el propio progreso de la implementaci´n el que nos ayuda a o o terminar de descubrir los “pormenores”. Los desarrolladores estar´n en contacto diario con los analistas para a resolver cualquier duda del ´mbito de negocio lo antes posible. con la debida disciplina. es decir. se deshacen u con ejemplos concisos en forma de tests autom´ticos. al menos o ıa un analista de negocio por cada cuatro desarrolladores15 . La experiencia a ha demostrado que una buena proporci´n podr´ ser 1:4. o a El c´digo pertenece a todo el equipo (propiedad colectiva) y cualquier o desarrollador est´ en condiciones de modificar c´digo escrito por otro. Siempre son planes de corta duraci´n. o La esencia del agilismo es la habilidad para adaptarse a los cambios. ¿En qu´ consiste el agilismo?: Un enfoque pr´ctico e a Cap´ ıtulo 1 entrega para cada iteraci´n). t´ e a n ıpicamente menores de siete personas.1. Tambi´n se encargar´ de priorizar el orden de implea e a mentaci´n de los requisitos acorde a lo que se hable en las reuniones con el o cliente. estar´n en m´s a a a a grupos. las ambig¨edades que puedan surgir.

ya se o o sabe que no vivimos en un mundo ideal. no hay que ser demasiado precisos con el porcentaje. “esto s´lo lo sabe tocar Manolo que lleva o meses trabajando en ello”.. ¿En qu´ consiste el agilismo?: Un enfoque pr´ctico e a tamos las situaciones del tipo. al menos una vez a la semana.Cap´ ıtulo 1 1. TDD habla de que la arquitectura se forja a base de iterar y refactorizar. cada metodolog´ o t´cnica ´gil detalla con ıa e a exactitud c´mo se gestiona el proyecto y c´mo es el proceso de desarrollo o o del c´digo. Los desarrolladores env´ sus cambios al repositorio de c´digo fuente al ıan o menos una vez al d´ (commit). esta pasa al o equipo de calidad para que la valide aunque el resto todav´ no est´n listas. Por norma general. Las arquitecturas ´giles son evolutivas. n La aplicaci´n se ensambla y se despliega en entornos de preproducci´n a o o diario. Este lin o bro defiende particularmente las arquitecturas que emergen de los requisitos. Todo el equipo se re´ne peri´dicamente. Las bater´ de tests se ejecutan varias veces ıas al d´ La cobertura16 de los tests debe ser en torno al 60 % o mayor. o Las reuniones tienen hora de comienzo y de final y son breves. De hecho. no son impuestas por el arquitecto.4. se hace.. nos contentaremos con que el cliente acuda a las reuniones de comienzo de iteraci´n. En ıa. se admite que s´lo los desarrolladores se re´nan diariamente o u y que la reuni´n con el cliente/analista sea s´lo una vez a la semana. La superaci´n de obst´culos imprevistos tiene prioridad sobre las cono a venciones o reglas generales de trabajo preestablecidas.. a no se dise˜an al completo antes de escribir el c´digo de negocio. Las grandes decisiones de arquitectura las toma todo el equipo. ıa e Partiendo de estas premisas. si hay que saltarse el protocolo de la empresa para resolver un problema que se nos ha atravesado. Por protocolo nos referimos a la forma en que habitualmente cooperan unas personas con otras. a ser posible diariamente y si no. A veces se pueden combinar varias metodolog´ aunque algunos o ıas autores recomiendan seguir al pie de la letra la metodolog´ en cuesti´n sin ıa o La cobertura de c´digo mediante tests se refiere al porcentaje de c´digo que tiene o o tests asociados. en lugar de dise˜arla completamente de antemano. incluidos usuarios y analistas de u o negocio. realidad. Cuando suena la alarma de fin de la reuni´n.. considerando todos los cauces que puede tomar el flujo de ejecuci´n o 16 43 . Es decir. Sigue siendo recomendable utilizar patrones de dise˜o y otras buenas pr´cticas pero siempre dando m´xima importancia y n a a prioridad a los requisitos de negocio.. es como si sonase la campana de o incendio en la central de bomberos: cada uno de vuelta a su puesto de trabajo inmediatamente. ıa Cada vez que se termina de desarrollar una nueva funci´n. de forma automatizada. se trata de tener en cada iteraci´n una cobertura a´n mayor que en o u la anterior. o tal vez la manera en que se lanzan nuevas versiones.

esto hace a que no se imponga como requisito que los profesores sean profesionales que se dediquen o se hayan dedicado profesionalmente a construir software. o a qu´ estamos tardando tanto en apuntarnos a este movimiento? e En Espa˜a. el arte y la cultura. desarrollo. En e definitiva. han modificado el proceso de desarrollo para reaccionar ante esta crisis. en lugares como Norte Am´rica o Reino o e Unido o bien siguen su corriente. todo tiene su coste y tenemos que poner en la balanza las dificultades y los beneficios para determinar qu´ decisi´n tomamos e o frente a cada problema. lo que es negativo es que aceptemos. Esto no es bueno ni malo. No se pretende capacitarles para afrontar problemas concretos en circunstancias concretas. o India y China. La situaci´n actual o Cap´ ıtulo 1 mezclar ni salirse del camino en ning´n momento. el objetivo principal es la creaci´n. transmisi´n. u No podemos negar que las metodolog´ son a menudo disciplinas y que ıas implantarlas no es sencillo. a pesar de esta realidad. sino abrir un debate o interior en cada uno de los que ya han salido de su largo periodo de formaci´n o y piensan que aquello que han aprendido es el unico camino posible. todo lo ´ que tienen que aplicar. La situaci´n actual o Son muchos los que se han dado cuenta de la necesidad de un cambio y. o ıas Esperemos que en el futuro cercano contemos con literatura en castellano sobre cada una de las metodolog´ ´giles m´s populares. difusi´n o o o y cr´ ıtica de la ciencia. no est´n escritos por e ´ e a profesores de universidad sino por l´ ıderes de la industria con treinta a˜os n de batalla sobre sus espaldas. promoviendo una visi´n e o integral del conocimiento. la t´cnica. En el libro de Canales[13] se habla precisamente de la implantaci´n y puesta en marcha de metodolog´ en la empresa. sino hacer que sean profesionales capacitados para afrontar con ´xito su cometido sea cual sea la tendencia que les toque vivir. En el caso concreto de la inform´tica. que lo que nos han ense˜ado es la unica manera de sacar n ´ adelante los proyectos.5. el objetivo de las universidades es la formaci´n integral del n o alumno. miremos con tanto escepticismo las a nuevas t´cnicas de gesti´n de proyectos software. sin la menor duda ni cr´ ıtica. guiados por aquellos que ya lo han emprendido. que copian lo mejor del sistema anglosaj´n. No pretendo abrir un debate sobre cu´l es el a objetivo de la universidad ni de la formaci´n profesional. cada cual cumple su funci´n en las distintas o etapas por las que pasamos. Los grandes libros sobre e o software escritos en ingl´s en las ultimas dos d´cadas.5. Sin olvidar el o pa´ de la tecnolog´ Jap´n. Es ciertamente a dram´tico que. La mayor parte de los que lo han hecho viven en el mundo anglosaj´n. ¿Por ıs ıa. como las grandes potencias en expansi´n.1. cuando en realidad lo que han adquirido es tan s´lo o 44 . Que ya no hay que aprender nada m´s. que adem´s de copiar marca tendencias. ıas a a 1.

al mismo tieme po. Hoy d´ son muchas las empresas de tecnolog´ que est´n compuestas por ıa ıa a gente muy joven y con poca experiencia que no tiene m´s remedio que llea var la batuta como buenamente puede. no ser´ una ingenier´ tan distinta del resto. Las cosas de palacio van despacio. una autoconfianza peligrosamente arrogante. el mensaje de fondo no es que todo lo aprendido durante nuestros a˜os de estudiantes sea err´neo sino que n o el camino est´ empezando. La tecnolog´ cambia a velocidad de v´rtigo. no podremos e discernirlo.. Por a otro lado. Las o ıas n oportunidades aparecen y se desvanecen fugazmente y nos vemos obligados 45 . a Todo lo dicho es aplicable a nuestros mentores en la empresa privada. Estoy convencido de que llegar´n pero no podemos esperar a que nos lo cuenten a ah´ para aplicarlos. La cuesti´n es plantearse que quiz´s o a la manera en que se resuelven los problemas no es la m´s apropiada. que realmente no las necea e sito. podemos considerar que la artesan´ ıa es simplemente una forma de producir demasiado compleja como para sintetizarla y reproducirla mec´nicamente. porque el cliente nos est´ pidiendo el producto ya. el personal se sienta realizado. Lo que pasa es que sin conocimiento. ¿Lo estamos o haciendo?. porque si las conoci´semos de antemano. Esta rutina en s´ misma no es negativa siempre que el cliente n ı est´ satisfecho y le estemos ofreciendo el mejor servicio y. Desgraciadamente. es importante saber reconocer cuando estamos sacando el m´ximo a partido a los m´todos. ıa En el mundo tecnol´gico los meses parecen d´ y los a˜os.Cap´ ıtulo 1 1. En cuanto a las empresas con personal experimentado. La parte artesanal de los programas de ordenador se podr´ deber a que ıa desconocemos la totalidad de las variables de las que depende. e ıa ıa Desde un punto de vista muy ingenieril. No cabe duda de que.. Neı a cesita software de calidad ahora. Por tanto. Es m´s dif´ que el modelo a ıcil de la M´quina de Turing quede obsoleto. en esa etapa. hay materias que llevan a˜os en uso y a las que se n les augura larga vida pero que todav´ no han llegado a los temarios de los ıa institutos ni de las universidades. no podemos esperar ıa e que en la universidad nos ense˜en continuamente lo ultimo que va saliendo n ´ porque en poco tiempo puede quedar obsoleto. hemos convertido el cerebro en un m´sculo bien u entrenado. meses. Este arte no se desarrolla estudiando a teor´ sino practicando. La situaci´n actual o una base y. no est´n a libres de caer en la autoreferencia y limitarse a reproducir lo que han hecho durante a˜os. al igual que a andar se aprende andando. Entonces la cuesti´n es.5. La labor de nuestros profesores es fundamental y debemos estar agradecidos porque nos han ense˜ado las reglas de los lenguajes formales y nos n han hablado de Alan Turing o del algoritmo de Edsger Dijkstra. en algunos casos. Recuerdo cuando me quejaba pora que en la ingenier´ t´cnica no hab´ visto nada de ciertas herramientas de ıa e ıa moda y ahora resulta que est´n extingui´ndose.

¿Nos adaptaremos a los cambios del entorno a tiempo?. es decir. Saber adaptarse al cambio es esencial para la evoluci´n. ahora.6. Tendremos que valernos de confianza y dedicaci´n junto con gusto por el trabajo para alo canzar esta meta pero. pl´tano es a Cap´ ıtulo 1 a tomar decisiones con presteza. El software es una herramienta de presente y de futuro. Ser´ una traves´ que nos ir´ descubriendo las claves de c´mo hacer a ıa a o mejor software al tiempo que nos sentimos m´s satisfechos con nuestro traa bajo. simplicidad y sostenibilidad. no siempre a a est´ bien empleada. elegancia. Tambi´n se dice que no se e necesitan arquitectos pero. por desgracia. Incluso hay empresas que creen estar siguiendo m´todos ´giles pero que en realidad no lo hacen (y no e a saben que no lo hacen).6. la palabra agilidad va adquiriendo valores de eficacia. El mal uso de la palabra ´gil causa malas y falsas ideas sobre lo que a 17 agile en ingl´s. creada para hacer m´s agradable la vida de los usuarios. a Bienvenidos a bordo. lo que sucede es que las decisiones de arquitectura se toman en equipo. Ahora nos est´ comenzando a llegar a la onda expansiva de un movimiento que pone en entredicho t´cnicas que e ten´ ıamos por buenas pero que con el paso de los a˜os se est´n revelando n a insostenibles. 1. pl´tano es a Se est´ usando mucho la palabra ´gil17 y. pronunciada como ´yail e a 46 . con la escasez de recursos econ´micos y la o mayor exigencia de los usuarios.. Algunos aprovechan el t´rmino ´gil para referirse a cowa e a boy programming (programaci´n a lo vaquero). zarpamos con preguntas hacia el fascinante mundo del desarrollo ´gil de a software. Es posible escribir software de mayor calidad con menos complicaciones y aportar m´s a los negocios de las personas que lo utilizan.´ 1. Las decisiones tecnol´gicas han convertido o en multimillonarias a personas en cuesti´n de meses y han hundido impeo rios exactamente con la misma rapidez. como quieren y cuando quieren. Existen mitos sobre el agilismo que dicen que no se documenta y que no se planifica o analiza. Agil parece. ´ Agil parece. Todos esos o pa´ de los que habl´bamos son competidores en realidad y lo ser´n cada ıses a a vez m´s dada la r´pida expansi´n de Internet. aunque tienda a olvidarse. hacer lo que les o viene en gana. no es cierto. tambi´n a e puede ser muy gratificante para los desarrolladores/analistas. ¿No estar´ a a o ıamos mejor si fuesen simplemente colegas?. Y. ¿Podemos beneficiarnos de esta nueva corriente?. Si bien hace poco gust´bamos de dise˜ar complejas arquiteca n turas antes de escribir una sola l´ ınea de c´digo que atacase directamente o al problema del cliente.. ¿C´mo podemos fomentar estas condiciones? Como o ven.

tal como pasa con quien dice que vende a a productos ecol´gicos. quienes realmente quieren subirse a este carro. lo que se traduce en mayor calidad. hay que mirar con lupa a quien dice que est´ siguiendo un desarrollo ´gil. como todo lo ıa que est´ bien hecho y. papeles m´s comunes en un proyecto software. Roles distintos no necesariamente significa personas distintas. o rotar de rol a lo largo del d´ Hagamos un repaso a los ıa. En Internet existen a multitud de grupos y foros donde se ofrece ayuda desinteresadamente y tambi´n existen profesionales que ofrecen formaci´n y entrenamiento en estas e o ´reas y se desplazan a cualquier parte del mundo para trabajar con grupos.yahoo.Cap´ ıtulo 1 1. a no ser que tenga a˜os de experiencia que le avalen. muy extendida en Argentina). 1. Una persona puede adoptar m´s de un rol. as´ como ı multitud de openspaces bajo la marca “Agile Open”. sobre todo aquellos cuyos autores firmaron el manifiesto ´gil. que entre otras cosas. son s´lo paıas a o peles que no garantizan la profesionalidad de la persona certificada(conf´ en ıo que las certificaciones de la agricultura ecol´gica s´ sean aut´nticas). Quiz´s por eso aparecieron las certificaciones en determinadas a metodolog´ ´giles aunque.7. a En ingl´s hay bastante literatura al respecto y es m´s que recomendable leer e a varios libros. a Sobre recursos en castellano. Los roles dentro del equipo verdaderamente es. n Adoptar una metodolog´ supone aprendizaje y disciplina. Los roles dentro del equipo Saber distinguir las obligaciones y limitaciones de cada uno de los roles del equipo ayuda a que el trabajo lo realicen las personas mejor capacitadas para ello. Llegado este punto.groups. a Due˜o del producto n Cliente Analista de negocio Desarrolladores 18 http://tech. No nos o ı e debemos fiar de alguien que ha asistido dos d´ a un curso de Scrum y ya ıas dice ser un maestro. actualmente hay mucho movimiento y grandes profesionales en Agile Spain (comunidad de ´mbito espa˜ol) y en el Foro a n Agiles18 (la comunidad latinoamericana.7. Hay quien cree que es ´gil porque “habla mucho con o a el cliente”. puede ir adoptando distintos roles con a el paso del tiempo. como muchas otras certificaciones.com/group/foro-agiles 47 . sobre todo en equipos muy reducidos. organiza el evento internacional anual Agiles. nea cesitar´n la ayuda de personas expertas en la materia.

. En el agilismo todos los desarrolladores son arquitectos en el sentido de que se les permite tomar decisiones de arquitectura conforme se va escribiendo o refactorizando c´digo.. hace que no seamos capaces de a ponernos en nuestro sitio. De hecho.. Lo mismo para la accesibilidad. Aparte de escribir o a o c´digo. Desarrolladores: Toman la informaci´n del analista de negocio y deciden o c´mo lo van a resolver adem´s de implementar la soluci´n. Recordemos que existe o a propiedad colectiva del c´digo y fluidez de conocimiento dentro del equipo. Administradores de sistemas: Se encargan de velar por los servidores y servicios que necesitan los desarrolladores. los ingenieros. entregan al equipo de a desarrollo interfaces de usuario (pantallas dibujadas con Photoshop o con cualquier otro dise˜ador) adem´s de las tablas que creen que lleva la base de n a 48 . c´mo tenemos que o hacer las cosas. el analista. dice el due˜o del producto n o. ¡y nosotros lo aceptamos!. los confundimos a menudo. Hay que resaltar que se hacen o revisiones de c´digo entre compa˜eros. Adem´s. es una persona que adopta varios roles. En lugar de un rol. ante decisiones complejas o n a se pide opini´n a desarrolladores m´s experimentados. En el mundo anglosaj´n se habla mucho del arquitecto del software. les explica qu´ hay que e hacer y resuelve sus dudas. Sabemos que la escasez de profesionalidad ha tenido mucho que ver con esto y. sino n o o el qu´) y aceptar o pedir correcciones sobre lo que se le entrega. o Parece que no son tan complicados los roles.1. el hecho de no tener claro cu´les son los roles de cada uno. sin embargo.7. Quien dice el cliente. n Analista de negocio: Tambi´n es el due˜o del producto porque trabaja e n codo a codo con el cliente y traduce los requisitos en tests de aceptaci´n o para que los desarrolladores los entiendan. El o arquitecto es la persona capaz de tomar decisiones de dise˜o pero adem´s n a se le supone la capacidad de poder hablar directamente con el cliente y entender los requisitos de negocio. es decir. a menudo nos encontramos con analistas de negocio que. Los roles dentro del equipo Cap´ ıtulo 1 Arquitectos Administradores de Sistemas Due˜o del producto: Su misi´n es pedir lo que necesita (no el c´mo. llegado el caso. los desarrolladores deben tener conocimientos avanzados sobre usao bilidad y dise˜o de interfaces de usuario. cuando hacen el an´lisis. e Cliente: Es el due˜o del producto y el usuario final. Nos dice que quiere una pantalla con tal bot´n y tales men´s. o u que las tablas de la base de datos tienen tales columnas. aunque es conveniente contar con n una persona experimentada para asistir en casos particulares.. En nuestra industria del software hemos llegado al extremo de el que el cliente nos dice a nosotros. que la base de datos tiene que ser Oracle.

Si las pantallas se dise˜an primero. incluso as´ las tablas son de los ultimos19 elementos que aparecen en ı. implementamos objetos.8. entonces es comprensible que dise˜e e n una interfaz de usuario pero entonces no deber´ pintarla con un programa ıa de dise˜o gr´fico y endi˜´rsela a otro. 1.8. cuando los requisitos de negocio ya n se cumplen. limit´monos a cuestiones de e ciencia y tecnolog´ ıa. limit´ndoles de modo que no les dejamos a hacer lo mejor que saben. Si los ingenieros y los cient´ ıficos pens´semos as´ entonces tendr´ a ı. luego bajamos a tablas en una base de datos relacional y finalmente le ponemos una carcasa a la aplicaci´n que se llama o interfaz gr´fica de usuario. es como debe ser”. vamos desde el test de aceptaci´n a tests de desarrollo que a o acabar´n en la capa de datos que pide persistencia. lo cual lo hace dif´ de modificar ante camıcil bios futuros en el negocio. Todav´ forma parte de nuestra cultura pensar que las cosas de toda la ıa vida son las mejores. Pensamos en requisia tos. a e Si cada cual no se limita a ejercer su rol o roles. 19 Cuando la l´gica de negocio es tan simple como guardar y recuperar un dato. a Existen fuertes razones hist´ricas para ser tan reticentes al cambio pero los o que trabajamos con tecnolog´ podemos dar un paso al frente en favor del ıa desarrollo. ¿No hab´ ıamos quedado en que el due˜o del producto n pide el qu´ y no dice el c´mo?. Si la persona que tiene el rol de analista e o tambi´n tiene el rol de desarrollador.. ¿Por qu´ no lo est´ practicando ya e a todo el mundo? La resistencia al cambio es uno de los motivos fundamentales. sino trabajar en ella. Los requisitos son frases cortas en lenguaje natural que ejecuta una m´quina autom´ticamente. Ya se sabe.. ¿Por qu´ nos cuesta comenzar a ser ´giles? e a datos y sus consultas. estaremos restringiendo a aquellos que saben hacer su trabajo. ¡Ojo! Podemos dejar nuestro lado conservador para otros aspectos no tecnol´gicos. “Si es de toda la vida. ya que tienen forma de test.Cap´ ıtulo 1 1. es o aceptable empezar por los datos. El due˜o del producto tampoco debe dise˜ar las n n tablas de la base de datos a no ser que tambi´n adopte el rol de desarrollador e pero. Es decir. ´ el proceso de implementaci´n del requisito. Las pantallas n a na no se dise˜an al comienzo sino al final. con lo que se a a sabe cu´ndo se han implementado. se a n contamina la l´gica de negocio con la interpretaci´n que el dise˜ador pueda o o n hacer de los requisitos y corremos el riesgo de escribir un c´digo sujeto a la o UI en lugar de a los requisitos. 49 . tal como ocurre con la interfaz o gr´fica. ¿Por qu´ nos cuesta comenzar a ser ´giles? e a Si el agilismo tiene tantas ventajas. que seguro nos aportar´ muchos beneficios. ıamos m´quinas de escribir en lugar de computadoras (en el mejor de los casos). No al rev´s. No se trata de o a ser progresista ni estamos hablando de pol´ ıtica.

.... Que no tenemos tiempo ahora para aprender eso. es que.. La corriente popular en t´rminos de software no es capaz de evolue cionar lo suficientemente r´pido como para que sus teor´ sean las mejores. Los que han tenido ıan e confianza en el cambio y han sabido crecer org´nicamente trabajan en lo a que les gusta y no tienen problemas para llegar a fin de mes.. Hace poco no nos imagin´bamos que comprar´ n a ıamos por Internet ni que las personas encontrar´ pareja a trav´s de la red. Que est´ todo al rev´s.... En este caso despertar del sue˜o significa n n cambia a mejor. Esto es.. Que no sabemos por d´nde empezar. La Pastilla o Roja tambi´n es el t´ e ıtulo de un libro sobre Software Libre escrito por Juan Tom´s a Garc´ ıa(http://www. Hemos u ıa a n llegado al punto en que la inform´tica ya no es una cuesti´n de matem´ticos a o a sino de especialistas en cada una de las muchas ´reas de la inform´tica. ¿Por qu´ nos cuesta comenzar a ser ´giles? e a Cap´ ıtulo 1 ¿Estamos preparados para darle una oportunidad a la innovaci´n o nos o quedamos con lo de “toda la vida”. Las nuevas tecnolog´ son el tren de alta velocidad que une el presente con el futuro en ıas un abrir y cerrar de ojos.html) 20 50 .net/resumen ejecutivo. aunque s´lo tenga una vida de medio o siglo? (en el caso de programadores junior.8.. Ni a a siquiera se han jubilado a´n los primeros expertos en software de la historia. s´lo un par de a˜os). Que no tenemos los conocimientos previos para empezar. ¿Prefiere la pastilla azul o la roja?20 .. a e Que es un caos. o Ma˜ana empiezo. a ıas Hay que plantearse si seguirla es buena idea o conviene cambiar de corriente. aprender una nueva t´cnica supone esfuerzo. No negaremos que hay que C´lebre escena de la pel´ e ıcula Matrix en que Morfeo ofrece a Neo la posibilidad de despertar del sue˜o.. a diferencia de lo que sucede en esta pel´ ıcula de ficci´n... Hemos o n aceptado una determinada forma de trabajar (en el mundo del software) y nos parece inmutable a´n cuando esta industria todav´ est´ en pa˜ales. Es natural que e nos d´ pereza dar los primeros pasos hacia el cambio y por eso usamos mil e excusas: Que es antinatural. Neo escoge la roja. Ahora bien. Nos comportamos como lo har´ un fumador al que le dicen que deje de ıa fumar....lapastillaroja..1. n Es que.. u Cuando era ni˜o no se me pasaba por la cabeza que todo el mundo llevase n un tel´fono m´vil encima y se comunicase desde cualquier lugar (hace s´lo e o o 15 a˜os)...

No pretende ser completo. Si esperamos a ma˜ana para que se den las condiciones pera n fectas y empezar a ser ´giles.8. alguna vez tendr´ tiempo y dinero de sobra para todo lo que quiera? El plan a es m´s bien parecido al de echar monedas a la hucha para irse de vacaciones. de cap´ ıtulos. quiz´s nunca llegue el d´ ¿Acaso piensa que a a ıa. a peque˜as inversiones poco a poco. el siguiente paso es admitir que es positivo mantener un cierto aire inconformista en nuestra actitud profesional. ¿vale?. ya que se podr´ escribir un libro entero ıa sobre metodolog´ sino solo establecer un contexto de partida para el resto ıas. ¿Por qu´ nos cuesta comenzar a ser ´giles? e a hacer una inversi´n en tiempo y esfuerzo para aprender y poner en pr´ctica o a una nueva forma de funcionar. 51 . No se interprete que podemos jugar con n el dinero del cliente. Este cap´ ıtulo ha sido compuesto a base de pinceladas procedentes diversos temas.Cap´ ıtulo 1 1. la cuesti´n es que tal inversi´n se amortiza o o r´pidamente. Si aceptamos que el software siempre se puede mejorar. Todos los d´ aprendemos algo nuevo. La autocr´ ıtica nos lleva a escribir c´digo de mayor calidad o y a reconocer nuestros errores. El juicio sano sobre nuestro trabajo nos gu´ ıa en la b´squeda de mejoras estrategias y nos ayuda a superar la pereza que u nos produce la idea del cambio. El ıas d´ que deje de ser as´ habr´ que reflexionar seriamente sobre si estamos ıa ı a ejerciendo bien el puesto de ingenieros de software. aprender no significa jugar con su dinero.

1.8. ¿Por qu´ nos cuesta comenzar a ser ´giles? e a Cap´ ıtulo 1 52 .

si no les gustan las met´foras. a La minimizaci´n del n´mero de defectos que llegan al software en fase o u de producci´n. O. TDD es una t´cnica para dise˜ar software a e n que se centra en tres pilares fundamentales: La implementaci´n de las funciones justas que el cliente necesita y no o m´s2 .youtube. convierte al programador en desarrollador3 . altamente reutilizable y preparado o para el cambio.com/watch?v=JMEO6T6gkAA Evitamos desarrollar funcionalidad que nunca ser´ usada a 3 http://www. es una t´cnica de dise˜o e implementaci´n de software e e n o incluida dentro de la metodolog´ XP.html 53 . algo e o que siempre es deseable. al cual me referir´ como TDD.com/No Programmers. o La producci´n de software modular.ericsink. algo como Dise˜o Dirigido por Ejemplos n hubiese sido quiz´s mas apropiado. pero es realmente una herramienta de dise˜o que n convierte al programador en un “oficial de primera”. Cuando empezamos a leer sobre TDD creemos que se trata de una buena t´cnica para que nuestro c´digo tenga una cobertura de tests muy alta. Coincido con Peter Provost1 en que el ıa nombre es un tanto desafortunado. TDD es la respuesta a a las grandes preguntas de: 1 2 http://www.Cap´tulo ı 2 ¿Qu´ es el Desarrollo Dirigido por e Tests? (TDD) El Desarrollo Dirigido por Tests (Test Driven Development).

. sino de dise˜ar adecuan damente seg´n los requisitos. lo cual ya propicia un determinado soporte. a tendremos que usar servicios web o servicios REST.NET MVC7 . nos limitamos a escoger el framework correspondiente y a usar su arquitectura como base. eran las unidades de trabajo m´s peque˜as sobre las que ponerse a a n desarrollar c´digo. No es que nos despreocupemos por completo a de las caracter´ ısticas t´cnicas de la aplicaci´n a priori. de pensar en implementar tareas. ¿Por d´nde empiezo?. pensamos si vamos a usar un patr´n Facade4 y otro Singleton5 o y una comunicaci´n mediante eventos. si escog´ ıesemos Django6 o ASP. es decir. m´s que nada porque tenemos que elegir a unas herramientas de desarrollo conformes a las exigencias del gui´n. Con TDD intentamos traducir el caso de uso o tarea en X o ejemplos. Sin o embargo. hagan emerger la arquitectura que necesitamos usar. ¿C´mo s´ qu´ es lo que o o o e e hay que implementar y lo que no?. Ni m´s ni menos.asp. primero nos preocupamos de definir ıas c´mo va a ser nuestra arquitectura.djangoproject. en constantes iteraciones. para una a e o web o para un pc de escritorio.com 7 http://www..wikipedia. a pensar en ejemplos certeros que eliminen la ambig¨edad creada por la prosa en lenguaje natural (nuestro u idioma). u Pasamos. Lo que eliminamos son las arquitecturas encima de esas arquitecturas. ¿Y si luego resulta que no necesitamos todo eso? ¿Cu´nto vamos a tardar en darnos cuenta de ello? ¿Cu´nto dinero vamos a a a malgastar? En TDD dejamos que la propia implementaci´n de peque˜os o n ejemplos. A ser posible. u En otras metodolog´ de software. hasta que el n´mero de ejemplos sea suficiente como para describir u la tarea sin lugar a malinterpretaciones de ning´n tipo. o DTOs. o los casos a de uso.net/mvc/ 5 54 . ya tendr´ ıamos definida buena parte de la base antes de empezar a escribir una sola l´ ınea de c´digo. las que intentan que todo se haga siempre igual y tal como se le ocurri´ al “genio” de la empresa.Cap´ ıtulo 2 ¿C´mo lo hago?. y una clase central que va o a hacer esto y aquello. Hasta ahora est´bamos acostumbrados a que las tareas. l´gicamente e o o tendremos que saber si el desarrollo ser´ para un tel´fono m´vil.org/wiki/Singleton 6 http://www. No es que trabajemos sin arquitectura. si en los requisitos est´ la interoperabilidad en las comunicaciones. u 4 o n Facade: http://es. ¿C´mo escribir un c´digo que o o se pueda modificar sin romper funcionalidad existente? No se trata de escribir pruebas a granel como locos.org/wiki/Facade (patr´n de dise˜o) Singleton: http://es.wikipedia. l´gio o camente. Por ejemplo. esas que nos obligan a moo dificar siete ficheros para cambiar una cadena de texto. TDD produce una arquitectura que emerge de la no-ambig¨edad de los tests automatizados. Pensamos en las clases de infraestruco tura que van a homogeneizar la forma de trabajar en todos y cada uno de los casos.

como cualquier t´cnica. o El trabajo en equipo se hace m´s f´cil. Para el arquitecto es 55 . n Cuando revisamos un proyecto desarrollado mediante TDD. por qu´ es e e beneficioso convertirla en nuestra herramienta de dise˜o principal. Personalmente. a a Nos permite confiar en nuestros compa˜eros aunque tengan menos n experiencia. no es una varita m´gica y no dar´ el e a a mismo resultado a un experto arquitecto de software que a un programador junior que est´ empezando. Nos hace descubrir y afrontar m´s casos de uso en tiempo de dise˜o. es util para ambos y para todo el a ´ rango de integrantes del equipo que hay entre uno y otro. Estas son n algunas de las razones que da Kent junto con otras destacadas figuras de la industria: La calidad del software aumenta (y veremos por qu´). a n La jornada se hace mucho m´s amena. a Las primeras p´ginas del libro de Kent Beck [3] (uno de los padres de a la metodolog´ XP) dan unos argumentos muy claros y directos sobre por ıa qu´ merece la pena darle unos cuantos tragos a TDD. Sin embargo.Cap´ ıtulo 2 lo cual no exime de las revisiones de c´digo entre compa˜eros ni de hacer o n preguntas a los desarrolladores m´s veteranos del equipo. nos damos cuenta de que los tests son la mejor documentaci´n t´cnica que podeo e mos consultar a la hora de entender qu´ misi´n cumple cada pieza del e o puzzle. e Conseguimos c´digo altamente reutilizable. evitando sobredise˜ar. a˜adir´ lo siguiente: n ıa Incrementa la productividad. a Uno se marcha a casa con la reconfortante sensaci´n de que el trabajo o est´ bien hecho. a Ahora bien. Multiplica la comunicaci´n entre los miembros del equipo. Escribir el ejemplo (test) antes que el c´digo nos obliga a escribir el o m´ ınimo de funcionalidad necesaria. o Las personas encargadas de la garant´ de calidad adquieren un rol m´s ıa a inteligente e interesante. o mejor. une a las personas.

Todos los que disfrutamos trabajando en el software llevamos dentro al personaje del buen arquitecto. ıa o Cuando el equipo practica de esta manera. Y as´ un paso e ı. el test). ¿C´mo escribimos un test para u o 8 http://es.1.2. la comunicaci´n fluye. Si no.1. lo haremos con un o o framework tipo Fit. como tantas otras cosas.1.1. o si es el toro quien le va a coger a ´l (o a ella). El algoritmo TDD La esencia de TDD es sencilla pero ponerla en pr´ctica correctamente es a cuesti´n de entrenamiento. Escribir la especificaci´n primero o Una vez que tenemos claro cu´l es el requisito. lo expresamos en forma a de c´digo. Concordion o Cucumber. ATDD. tras otro. se convierte en e el “Pepito Grillo” que le cuenta qu´ paso tiene que dar ahora. Ni que decir tiene que el e ambiente de trabajo en estos casos no es nada bueno ni productivo. Si estamos a nivel de aceptaci´n o de historia.org/wiki/Continuous integration 56 . La pr´ctica que aqu´ se describe nos lo trae para que nos ayude a ı y nos haga la vida m´s f´cil. El algoritmo TDD s´lo o o tiene tres pasos: Escribir la especificaci´n del requisito (el ejemplo. entre muchos otros de nuestros personajes. o u Refactorizar para eliminar duplicidad y hacer mejoras. la o gente se vuelve m´s activa y la maquinaria funciona como un engranaje a bien lubricado. a 2. Esto es. le gu´ en la implementaci´n de la tarea que le ha sido asignada. Para el programador junior que no sabe por d´nde va o o a coger al toro. o Implementar el c´digo seg´n dicho ejemplo. lo haremos con alg´n framework xUnit. 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´ 8 y las estad´ o ınua ısticas del repositorio de c´digo. nos encontramos con gente muy desconfiada que mira con lupa el c´digo de su equipo antes de que nadie pueda hacer “commit” al o 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. a a 2. Fitnesse. una gu´ que le hace clarificar el dominio de negocio a cada ıa test y que le permite confiar en su equipo aunque tenga menos experiencia.wikipedia. Frecuentemente. El algoritmo TDD Cap´ ıtulo 2 su mano derecha. Ve´mosla en detalle.

m´s claro. entonces es posible!.Cap´ ıtulo 2 2. Por eso. s´lo la atenci´n nos ayudar´ a contener el o o a Subject Under Test. En este paso. nos vendr´n a la mente dudas sobre el comportamiento a del SUT ante distintas entradas. Si estamos a o bien concentrados. los distintos flujos condicionales que pueden entrar en juego. sino que vamos una a una siguiendo los tres pasos del algoritmo TDD. no lo es. pero no es o as´ Un test se puede modificar..2. la m´xima es no implementar nada m´s que lo estrictamente obligatorio a a para cumplir la especificaci´n actual. Y no se trata de hacerlo sin pensar. Parece f´cil pero. El hecho de tener que usar una funcionalidad antes de haberla escrito le da un giro de 180 grados al c´digo o resultante. En los pr´ximos cap´ o ıtulos veremos c´mo o mediante ejemplos. 9 57 . es decir. algo preestablecido y fijo. La palabra especificaci´n podr´ tener o o ıa la connotaci´n de que es inamovible. Implementar el c´digo que hace funcionar el ejemplo o Teniendo el ejemplo escrito. No vamos a empezar por fastidiarnos a nosotros mismos sino que nos cuidaremos de dise˜ar lo que nos sea m´s c´modo. codificamos lo m´ ınimo necesario para que se cumpla. un comportao n miento del SUT bien definido y s´lo uno. Es el objeto que nos ocupa. ¡ah. el m´ ınimo c´digo es el de menor o n´mero de caracteres porque m´ u ınimo quiere decir el que menos tiempo nos llev´ escribirlo. El framework Mono se ha implementado bas´ndose en las especificaciones del a ECMA-334 y ECMA-335 y funciona. un test no es inicialmente un test sino un ejemplo o especificaci´n. primero en c´mo queremos que sea la API del SUT9 . o sino concentrados para ser eficientes.1. eso lo o o vamos a enmendar en el siguiente paso y en las siguientes iteraciones. hace lo que le pedimos que haga.1. efectivamente. para que el test pase. T´ ıpicamente. el resto de especificaciones de este bloque de funcionalidad. a veremos que siempre escribimos m´s c´digo del que hace falta. el que estamos dise˜ ando a trav´s n e de ejemplos. tenemos que o trazar antes de implementar. las JSR (Java Specification Request) se escriben para que luego terceras partes las implementen. La diferencia con los que dictan una JSR es que no dise˜amos todas las n especificaciones antes de implementar cada una. Estaremos tentados de escribir el c´digo que los gestiona sobre o la marcha y. es decir.. Pero s´lo una parte peque˜a. 2. tenemos que pensar ı. Tenemos que hacer el esfuerzo o de imaginar c´mo seria el c´digo del SUT si ya estuviera implementado y o o c´mo comprobar´ o ıamos que. siempre n a o a que cumpla con el requisito objetivo. en ese momento. No importa que el c´digo parezca feo o chapucero. El algoritmo TDD un c´digo que todav´ no existe? Respondamos con otra pregunta ¿Acaso o ıa no es posible escribir una especificaci´n antes de implementarla? Por citar o un ejemplo conocido. Para poder escribirlo. al principio.

com/ Ver Cap´ ıtulo7 en la p´gina 111 a 58 .O.1. se aplican unos determinados cambios que mejoran el dise˜o del softwan re mientras que su comportamiento sigue siendo el mismo. reescribir es m´s general o a que refactorizar.L. A ser posible. refactorizar10 es modificar el dise˜o ın n sin alterar su comportamiento. Dadas unas precondiciones. existen otras m´s complejas que tienen que ver con a la maestr´ del desarrollador y que a veces recuerdan al mago sacando un ıa conejo de la chistera. Siempre que llego al paso ı de refactorizar.1. sin alterar su API p´blica. asegur´ndonos que no se cometen errores en la transici´n. Eso ya depende del conocimiento y la experiencia de cada uno. se ejecutan todos los tests n 10 11 http://www. El IDE modifica el c´digo por nosoo tros.D) y refactorizamos para que as´ sea. Refactorizar Refactorizar no significa reescribir el c´digo.2. Netbeans o VisualStudio. Mejora es una palabra ciertamente subjetiva. durante la refactorizaci´n podemos permitirnos a o darle una vuelta de tuerca al c´digo para hacerlo m´s claro y f´cil de mano a a tener. Algunas de ellas tienen nombre y est´n catalogadas a a modo de patr´n y otras son an´nimas pero igualmente eliminan la duplicidad. 2. Se hace un cambio. por lo que empleamos la m´trica del c´digo e o duplicado como par´metro de calidad. El algoritmo TDD Cap´ ıtulo 2 impulso y a anotar las preguntas que nos han surgido en un lugar al margen para convertirlas en especificaciones que retomaremos despu´s. y elimino la duplicidad. Pull-down o e o cualquiera de las muchas disponibles. Segun Mart´ Fowler. Adem´s.3. es una refactorizaci´n. en iteraciones e consecutivas. Basta con se˜alar un bloque de c´digo a n o y elegir la refactorizaci´n Extraer-M´todo. Extraer-Clase. En u este tercer paso del algoritmo TDD. Los IDE como Eclipse. a Mas all´ de la duplicidad. Si no existe c´digo duplicado. e a Cada una de ellas es como una receta de cocina. son capaces de llevar a cabo las refactorizaciones m´s comunes. me planteo si el m´todo en cuesti´n e o ´ y su clase cumplen el Principio de una Unica Responsabilidad11 y dem´s a principios. que mantenga su API p´blio u ca. El propio Fowler escribi´ uno de los libros m´s grandes de la literatura o a t´cnica moderna[7] en el que se describen las refactorizaciones m´s comunes. rastreamos el c´digo (tambi´n el del o e test) en busca de l´ ıneas duplicadas y las eliminamos refactorizando. La clave de una buena refactorizaci´n es hacerlo o o en pasitos muy peque˜os. o o Cualquier cambio en los adentros del c´digo.refactoring. Al margen de a o estas refactorizaciones.I. Pull-up. entona o ces hemos conseguido uno de m´s calidad que el que presentaba duplicidad. a revisamos que el c´digo cumpla con ciertos principios de dise˜o (me inclino o n por S.

En la segunda parte del libro se expondr´ el algoritmo TDD mediante a ejemplos pr´cticos. Es el momento de detectar malos olores y eliminarlos. a a No se preocupe si no lo ve del todo claro ahora. nos resulta la mejor traducci´n del t´rmino refactoring. Cuando refacn torizamos. El verbo refactorizar no existe como tal en la Real Academia Espa˜ola pero. As´ cuando escribimos el test. pero actuamos en local. Todav´ no he encontrado ning´n ıa u proyecto en el que se desaconseje aplicar TDD. La clave est´ en saber dividir. a o Otra forma de enumerar las tres fases del ciclo es: Rojo Verde Refactorizar Es una descripci´n metaf´rica ya que los frameworks de tests suelen colorear o o en rojo aquellas especificaciones que no se cumplen y en verde las que lo hacen. Si de las tres etapas que tiene el algoritmo TDD dejamos o atr´s una. donde iremos de menos a m´s.Cap´ ıtulo 2 2. El algoritmo TDD y. se pasa o a verde. Connotaciones alcoh´licas a un lado. espero que se capte o el mensaje. ıa no existe c´digo que implemente el requisito. De a ah´ la ayuda de Scrum para gestionar adecuadamente el backlog del producto. Cuando hemos dado los tres pasos de la especificaci´n que nos ocupa. es la que m´s tiende a olvidarse.1. si todo sigue funcionando. Me gusta decir que o tiene una similitud con un buen vino. iterando progresivamente. o e La tarea de buscar y eliminar c´digo duplicado despu´s de haber completado o e los dos pasos anteriores. y as´ sucesivamente olvidando a ı la refactorizaci´n. l´gicamente no estamos practicando TDD sino otra cosa. Es com´n entrar en a u la din´mica de escribir el test. luego el SUT. la primera vez que se prueba el vino en la vida. Una vez implementado. en saber priorizar. pero a fuerza de repetir se convierte en un placer para los sentidos. tras n discutirlo en la red. El tama˜o del proyecto no guarda relaci´n con la a n o aplicabilidad de TDD. la reacci´n de los asistentes a mis cursos es mayoritariamente incr´dula y es o e que el efecto TDD s´lo se percibe cuando se practica. el primer color es rojo porque todav´ ı. 59 . pensamos en global. se hace otro peque˜o cambio. no gusta a nadie. contemplamos la perspectiva general. Parece demasiado simple. ¿Y TDD sirve para proyectos grandes? Un proyecto grande no es sino la agrupaci´n de peque˜os subproyectos y es ahora cuando toca aplicar aquello o n de “divide y vencer´s”. o tomamos la siguiente y volvemos a repetirlos. ı Por eso tanta gente combina XP y Scrum.

2. 2. contar´ con un important´ a ısimo respaldo detr´s en forma de bater´ de tests. La diferencia entre el desarrollador experimentado que se sienta a hacer TDD y el junior. TDD es bueno para todos los individuos y en todos los proyectos. Consideraciones y recomendaciones Cap´ ıtulo 2 2. Debe tenerse en cuenta que se supone que el principiante no est´ soa lo. no recomendar´ lanzarse a escribir espeıa cificaciones. sin saber que la soluci´n que elige quiz´s le traiga quebraderos de o a cabeza m´s adelante. El junior probablemente se siente a escribir lo que mejor a le parece.2.2. matices. Es hacerse alguna funci´n o alguna aplicaci´n peque˜a que nos aporte el conocimiento o o n que no tenemos. El l´ debe intentar que las personas que a a ıder trabajan con ´l est´n contentas de trabajar ah´ y quieran seguir haci´ndolo. Hay que respetar el tiempo de aprendizaje con la herramienta y 60 .2. aplicando las buenas a a n pr´cticas que conoce. Si el spike es peque˜o. y resulta que nos damos cuenta n que su propio c´digo es v´lido tal cual. entonces escribiremos el test justo a o a continuaci´n. En estos casos.Un spike es un peque˜o programa que se escriıa n be para indagar en la herramienta. explorando su funcionalidad. se cuidar´ de no dise˜ar una API que le resulte casi imposible de a n usar. Dista de la realidad. o que el SUT. TDD con una tecnolog´ desconocida ıa La primera vez que usamos una determinada tecnolog´ o incluso una ıa nueva librer´ es complicado que podamos escribir la especificaci´n antes ıa.1. e e ı e 2. El experto en dise˜o orientado a objetos a a o n buscar´ un test que fuerce al SUT a tener una estructura o una API que a sabe que le dar´ buenos resultados en t´rminos de legibilidad y reusabilidad. Consideraciones y recomendaciones Ventajas del desarrollador experto frente al junior Existe la leyenda de que TDD unicamente es v´lido para personal alta´ a mente cualificado y con much´ ısima experiencia.2. a e Un experto es capaz de anticipar futuros casos de uso y futuros problemas y ser´ m´s cuidadoso dise˜ando la API test tras test. gran diferencia de que el l´ ´gil est´ para responder preguntas y ayudar a ıder a a los dem´s y no para darles l´tigo. hay desarrolladores de m´s experiencia que a supervisar´n y habr´ momentos en los que se programe en parejas. Por poco experimentado a ıa que sea. La ventaja es que. es c´mo enfocan los tests. Sin un conocimiento b´sico de la o a API y las restricciones del sistema. o e m´s all´ del c´digo que escriben. en lugar de dejarlo sin test. sino que en un contexto XP. qu´ tests escriben.2. XP habla de spikes (disculpen que no lo traduzca. La figura a a de los l´ ıderes es importante en XP al igual que en otras metodolog´ con la ıas. Eso s´ hay algunos ı. porque no sabemos las limitaciones y fortalezas que ofrece la nueva herramienta. cuando se d´ cuenta de que su dise˜o a e n tiene puntos a mejorar y empiece a refactorizar. no sabr´ como) . es decir.

Para los nuevos a requisitos de la aplicaci´n. Intentar practicar TDD en un entorno desconocido es. Adem´s de tener dos API o a podemos sobrecargar m´todos para intentar que el c´digo legado y su nueva e o versi´n convivan. Si es una web. primero aprendemos a usar la herramienta y luego la usamos. “Working Effectively with Legacy Code” de Michael C. Una vez sepamos si es de cambio a manual o autom´tico. la de los ejemplos pr´cticos. tests unitarios) que o minimicen los posibles efectos colaterales de la reescritura. Otra alternativa para hacer TDD con c´digo nuevo a o 12 Ver Cap´ ıtulo 7 en la p´gina 111 a 61 . iniciamos el a desarrollo de una aplicaci´n desde cero. La pregunta de los asistentes aparece antes o despu´s: ¿no se puede e aplicar TDD en un proyecto que ya est´ parcialmente implementado? Claro a que se puede. u Mi recomendaci´n.. si es que la API antigua nos sigue sirviendo. Tratar con c´digo legado no es moco de pavo. “Si puede o u salir mal. antes de ponerse a reescribir partes de c´digo legado.Cap´ ıtulo 2 2. Feathers[6]. c´digo legado que nos dificulta su reutilizaci´n. d´nde se encienden las luces y d´nde se activa el lima o o pia parabrisas. podremos aplicar eso de escribir el test primero y luego el c´digo (¡y o despu´s refactorizar!). Es probable que el nuevo SUT colabore con partes e legadas que no permiten la inyecci´n de dependencias y que no cumplen una o 12 . por c´digo leo o gado entendemos que se trata de aquel que no tiene tests de ning´n tipo.2. agarrar Selenium o similar y grabar todos los posibles usos de la interfaz gr´fica para poderlos reproducir despu´s de las modificaciones y a e comprobar que todo el sistema se sigue comportando de la misma manera. es o o crear tests de sistema (y cuando el c´digo lo permita.) 2. aquello que todav´ falta por implemeno ıa tar. Viene siendo o cuesti´n de aplicar el sentido com´n y recordar la ley de Murphy. por ejemplo.. TDD en medio de un proyecto En la segunda parte del libro. es decir. o en lugar de reescribir eliminando la versi´n legada. aunque con m´s consideraciones en juego. a mi parecer. En general.3. sino que primero tendremos que aprender a pilotar la m´quina. un antipatr´n poco documentado. a pero es mucho m´s seguro que lanzarse a reescribir alegremente. Igual que hacemos en los cursos que o imparto. minusvalorar el riesgo de no dominar las herramientas (y frameworks y lenguajes. podremos echar a rodar. Es un esfuerzo de usar y tirar porque estos tests son tremendamente fr´giles. Consideraciones y recomendaciones avanzar una vez que tengamos confianza con ella. El unica responsabilidad ´ o o libro m´s recomendado por todos en los ultimos tiempos sobre este asuna ´ to es. Es s´lo cuesti´n de aplicar el sentido o o com´n. La siguiente a recomendaci´n es que la nueva API y la vieja convivan durante un tiempo.2. saldr´ mal”. Teneu mos que evitar algo que pasa muy frecuentemente. o Tampoco es que descartemos forzosamente TDD.

Digamos o que los tests van a ser m´s fr´giles de lo que deber´ pero es mejor usar a a ıan paraca´ ıdas que saltar sin nada.2. 13 Los veremos en el Cap´ ıtulo 6 en la p´gina 95 a 62 .2. Se lo u recomiendo encarecidamente. Y por supuesto. si el nuevo c´digo es m´s o a independiente. podemos seguir haciendo TDD sin ning´n problema. Consideraciones y recomendaciones Cap´ ıtulo 2 que colabora con c´digo legado es abusar de los objetos mock13 .

ni la mejor t´cnica de desarrollo de todos los tiempos producir´ un e a buen resultado. son ejemplos ejecutables. La mayor diferencia entre las metodolog´ cl´sicas y la Programaci´n ıas a o Extrema es la forma en que se expresan los requisitos de negocio. Son ejemplos escritos por los due˜os de producto. o El algoritmo es el mismo de tres pasos pero son de mayor zancada que en el TDD practicado exclusivamente por desarrolladores. t´cnica conocida tambi´n como Story Testo e e Driven Development (STDD). es igualmente TDD pero a un nivel diferente. por ejemplos ejecutables surgidos del consenso entre los distintos miembros del equipo. a ATDD/STDD es una forma de afrontar la implementaci´n de una manera o totalmente distinta a las metodolog´ tradicionales. puede considerarse probablemente el m´s importante de todo el libro. El Desarrollo Dirigido por Test de Aceptaci´n (ATDD). En ATDD la lista de ejemplos (tests) de cada historia. n Es el punto de partida del desarrollo en cada iteraci´n. se escribe en una reuni´n que incluye a o 63 .Cap´tulo ı 3 Desarrollo Dirigido por Tests de Aceptaci´n (ATDD) o A pesar de la brevedad de este cap´ ıtulo. sino los requisitos. El trabajo del analista de ıas negocio se transforma para reemplazar p´ginas y p´ginas de requisitos escritos a a en lenguaje natural (nuestro idioma). o los cuales considero un subconjunto de la documentaci´n. Si no somos capaces de entendernos con a el cliente. la conexi´n pero o fecta entre Scrum y XP. En lugar de documentos de word. Los tests de aceptaci´n o de cliente son el criterio escrito de que el softwao re cumple los requisitos de negocio que el cliente demanda. No hablo de reemplazar toda la documentaci´n. all´ donde una se queda y sigue la otra. incluido por supuesto el cliente.

mientras que contarlo con ejemplos ilustrativos. que resume qu´ es lo que e hay que hacer. 64 . En ATDD cada historia de usuario contiene una lista de ejemplos que cuentan lo que el cliente quiere. los distintos roles del equipo se apoyan o entre s´ para darles forma. Sin embargo. con total claridad y ninguna ambig¨edad. ı 3. Esto es: a base de iterar.1. Muy importante: Est´n a escritas con el vocabulario del negocio del cliente. Las historias de usuario Cap´ ıtulo 3 due˜os de producto. Ejemplos de historias podr´ ser: ıan Formulario de inscripci´n o Login en el sistema Reservar una habitaci´n o A~adir un libro al carrito de la compra n Pago con tarjeta de cr´dito e Anotar un d´a festivo en el canlendario ı Informe de los art´culos m´s vendidos ı a Darse de baja en el foro Buscar casas de alquiler en Tenerife Breves. transo mite la idea sin complicaciones.1. ı ıcil Lo que las hace estimables y nos hace ser capaces de estimarlas cada vez mejor. es el proceso evolutivo que llamamos ´gil. Escribir una definici´n formal incurre en el peligro de la imprecisi´n o o y la malinterpretaci´n. la historia no pretende definir el requisito.3. e Por s´ misma una historia aislada es dif´ de estimar incluso con este formato. de alrededor de cinco palabras. Las historias de usuario Una historia de usuario posee similitudes con un caso de uso. salvando ciertas distancias. Todo el equipo n debe entender qu´ es lo que hay que hacer y por qu´. para concretar el modo e e en que se certifica que el sotfware lo hace. Son el resultado de escuchar al cliente y ayudarle a resumir el requisito en una sola frase. podr´ ıamos decir que el t´ ıtulo de la historia se corresponde con el del caso de uso tradicional. a estimar en cada iteraci´n y hacer restrospectiva al final de la misma. desarrolladores y responsables de calidad. vamos o refinando la habilidad de escribir historias y estimarlas. Como no hay unica manera de ´ decidir los criterios de aceptaci´n. Por hacer una correspondencia entre historias de usuario y casos de uso. concretas y algo estimables. El enunciado de una historia es tan s´lo una frase en u o lenguaje humano. no con vocabulario t´cnico.

Como artesano del software. Antes de conocer ATDD. es en cierto modo opuesto a lo que se expone en este libro. discrepo con su forma de afrontar el a an´lisis. Definir entidades/modelos y hablar de pantallas antes de que haya una lista de ejemplos ejecutables y c´digo ejecutable que los requiera. de manera que nos aproximamos al dominio del cliente de una manera m´s a intuitiva. realmente lo que leemos son a descripciones. es un camino o problem´tico. es a o una fuente de problemas. Para m´ la unica utilidad que tiene el UMLa es la de representar meı.Cap´ ıtulo 3 ' 3. En todos los “ejemo plos” que aparecen en las citadas p´ginas.1. es decir o el modelado. a Cada historia provoca una serie de preguntas acerca de los m´ltiples u contextos en que se puede dar. peligroso cuanto menos. no son ejemplos potencialmente ejecutables. En cambio. Es m´s que recomendable leerlo. tambi´n trabajaba como nos dice en a e esas p´ginas pero la experiencia me ha ense˜ado que no es la mejor maa n nera. o para poder echar un vistazo a las entidades de manera global pero nunca hago un diagrama de clases antes de programar. ¿Qu´ hace el sistema si el libro que se quiere a˜adir al carrito ya e n est´ dentro de ´l? a e ¿Qu´ sucede si se ha agotado el libro en el almac´n? e e ¿Se le indica al usuario que el libro ha sido a˜adido al carrito de la n compra? & Lenguaje de Modelado Universal % 65 . utilizo o herramientas que autogeneran diagramas de clases. es en mi opini´n. Pueden ayudarnos a comprender el dominio hasta que llegamos a ser capaces de formular ejemplos concretos. Son las que naturalmente hacen los desarrolladores a los analistas de negocio o al cliente. a partir de c´digo. representar elementos que formar´n parte del c´digo fuente mediante diagramas. Saltar de casos de uso a crear un diagrama de clases modelando entidades. Traducir diagramas en c´digo fuente. Las historias de usuario $ Canales[13] da una gu´ estupenda para las estimaciones entre las p´giıa a nas 305 y 319 de su libro. no creo en los generadores de a aplicaciones. Sin embargo. Mis entidades emergen a base de construir el c´digo conforme a ejemplos. Los diagramas nos o pueden ayudar a observar el problema desde una perspectiva global. Es decir. c´digo fuente existente. a desde la p´gina 320 hasta la 343. ´ diante un diagrama de clases.

B´sicamente lo que proponen es escribir a las frases con un formato determinado como por ejemplo HTML. tanto el cliente. usando etiquetas de una manera espec´ ıfica para delimitar qu´ partes de la frase son e variables de entrada para el c´digo y cuales son datos para validaci´n del o o resultado de la ejecuci´n. Fitnesse. el sistema devuelve n un mensaje que dice: ‘‘El libro X ha sido a~adido al carrito’’ n Al mostrar el contenido del carrito aparece el libro X El libro X ya no aparece entre los libros a a~adir al carrito n Cuantas menos palabras para decir lo mismo. adem´s de mostrar las estad´ a ısticas generales sobre cu´ntos tests pasaron y a cu´ntos no. se enviar´ un email al usuario cuando llegue”. ejemplos. entienden. a a Los tests de aceptaci´n son as´ afirmaciones en lenguaje humano que o ı. El equipo a o a a de desarrollo tiene que hacer el esfuerzo de conectar esas frases con los puntos de entrada y salida del c´digo. Los m´s conocidos son FIT.1. Como salida. produce un o HTML modificado que marca en rojo las afirmaciones que no se cumplieron. los cuales transformamos en tests de aceptaci´n. Cucumber y Robot.3. mejor: A~adir libro X en stock produce: ‘‘El libro X ha sido a~adido al carrito’’ n n Libro X est´ contenido en el carrito a Libro X ya no est´ en cat´logo de libros a a Las preguntas surgidas de una historia de usuario pueden incluso dar lugar a otras historias que pasan a engrosar el backlog o lista de requisitos: “Si el libro no est´ en stock. ¿La a m´quina? ¿c´mo puede entender eso la m´quina? M´gicamente no. como los desarrolladores. cada historia de usuario tiene o asociados uno o varios tests de aceptaci´n (ejemplos): o Cuando el libro X se a~ade al carrito. a Concordion. Las historias de usuario Cap´ ıtulo 3 Las respuestas a estas preguntas son afirmaciones. Por tanto. Veamos un ejemplo de la sintaxis de Concordion: a 66 . Para esto existen diversos frameworks libres o y gratuitos que reducen el trabajo. Concordion por ejemplo. como la m´quina.

Concordion u sabe d´nde buscar la funci´n greetingsFor y reconoce que el argumento o o con que la invocar´ es Manolo. En pria mer lugar. no le pedimos al cliente que se aprenda la sintaxis de Cono cordion y escriba el c´digo HTML.Cap´ ıtulo 3 ' 3. Las historias de usuario $ L´gicamente. a nivel de c´digo. o a a primero. 1 & <html xmlns:concordion="http://www. Lo interesante para el a cliente es que el renderizado del HTML contiene el ejemplo que ´l entiende e y es una bonita tarjeta que Concordion colorear´ con ayuda de la hoja de a estilos. Por tanto. Le pedimos que nos ayude a definir la o frase o que nos la valide y luego.1. a o Un test de cliente o de aceptaci´n con estos frameworks. habr´ un cono a junto de tests unitarios y de integraci´n de grano m´s fino que se encargar´. se escribir´ el HTML. subrayando en verde o en rojo seg´n funcione el software. de afirmar que funciona n como sus creadores quer´ que funcionase. El propio o framework se encarga de hacer la pregunta de si las afirmaciones son ciertas o no.org/2007/concordion"> <body> <p> El saludo para el usuario <span concordion:set="\#firstName">Manolo</span> ser´: a <span concordion:assertEquals="greetingFor(\#firstName)"> ¡Hola Manolo!</span> </p> </body> </html> % El famoso YAGNI (You ain’t gonna need it) 67 . a n sino marcando unos criterios de aceptaci´n que nos ayuden a ir desde el lado o del negocio hasta el lado m´s t´cnico pero siempre concentrados en lo que a e el cliente demanda. ni m´s ni menos. de ayudar a dise˜ar el software y. Por eso ATDD o STDD es el ıan comienzo del ciclo iterativo a nivel desarrollo. no trabajaremos en funciones que finalmente no se van a usar1 . desarrolladores y testers (equipo de calidad). su aspecto dista mucho de un test unitario o de integraci´n o con un framework xUnit. Las ventajas son numerosas.concordion. entre analistas de negocio. No empezamos el dise˜o en torno a una supuesta o n interfaz gr´fica de usuario ni con el dise˜o de unas tablas de base de datos. segundo. Para cada test de aceptaci´n de una historia de usuario. Comparar´ el resultado de la ejecuci´n con la a a o frase ¡Hola Manolo! y marcar´ el test como verde o rojo en funci´n de ello. porque partiendo de un test de aceptaci´n vamos profundizando en la implementaci´n con sucesivos test o o unitarios hasta darle forma al c´digo que finalmente cumple con el criterio o de aceptaci´n definido. o o es un enlace entre el ejemplo y el c´digo fuente que lo implementa.

es lo natural. a consecuencia de esto. Evitamos a toda costa ejemplos que se meten en el c´mo hacer. o los resultados aparecen en la tabla de la derecha Al introducir la fecha y hacer click en el bot´n de a~adir. adem´s.3. En la ıa a construcci´n de viviendas eso no se puede hacer pero en el software. para ir ense˜´ndolas al cliente. se podr´ o a a ıa interpretar que deber´ ıamos partir de una interfaz gr´fica de usuario para la a implementaci´n pero no es cierto. ¡Porque a una aplicaci´n inform´tica no es una casa!. o ı. Qu´ y no C´mo e o Cap´ ıtulo 3 En segundo lugar. Es m´s f´cil hacer modificaciones cuando se a a ha dise˜ado as´ de arriba a abajo. aunque estemos acostumbrados a lo contrario. Si el arquitecto n ı. forjaremos un c´digo que est´ listo para cambiar si fuera o a necesario porque su dise˜o no est´ limitado por un dise˜o de base de datos n a n ni por una interfaz de usuario. marcar con un tick o las l´neas a borrar y verificar que se eliminan de la tabla ı al pulsar el bot´n aplicar. m´s o a all´ del qu´ hacer: a e Al rellenar el cuadro de texto de buscar y pulsar el bot´n contiguo.2. Aprovechamos los frameworks tipo Concordion para e o desarrollar nuestra habilidad de preguntar al cliente qu´ quiere y no c´mo lo e o quiere. porque la interfaz gr´fica puede a o no ser intuitiva. Qu´ y no C´mo e o Una de las claves de ATDD es justamente que nos permite centrarnos en el qu´ y no en el c´mo. o n se crea un nuevo registro vac´o ı Los libros se almacenan en la tabla Libro con campos: id. utilizable y. Dada esta met´fora. dise˜a mal la estructura de un edificio. Primero. o 3. s´ Y. en segundo lugar. seguro que al final na colocar´ la mejor de las estructuras para darle soporte a lo dem´s. titulo y autor Seleccionar la opci´n de borrar del combo. ser´ muy complejo hacerle cambios n a en el futuro pero si pudiera ir montando la cocina y el sal´n sin necesidad o de estructuras previas. o Aplicaci´n Flash con destornilladores y tornillos girando o en 3D para vender art´culos de mi ferreter´a por Internet ı ı Cuando partimos de especificaciones como estas corremos el riesgo de pasar por alto el verdadero prop´sito de la aplicaci´n.2. la informaci´n con aut´ntico o o o e 68 . Ver el dibujo de una interfaz gr´fica de o a usuario no es como ver una cocina. no es el medio adecuado para expresar qu´ es lo que el cliente necesita sino que e la interfaz de usuario es parte del c´mo se usa. en vez de abajo a arriba.

no lo son: ı.Cap´ ıtulo 3 3. Al principio a no son capaces de distinguir entre una descripci´n y un ejemplo preciso por o lo que se apresuran a darme descripciones que consideran suficientes como para implementar el software pero que para m´ ajeno a su negocio. de consultor´ ıa. o A base de colaboraci´n encontramos y clasificamos la informaci´n que m´s o o a beneficio genera para el usuario. Buscando por Santa Cruz de Tenerife. empiezo a formular ejemplos para que me digan si son v´lidos o no. igual que no le o o decimos al fontanero c´mo tiene que colocar una tuber´ La mayor´ de las o ıa. abordando los ejemplos. de cara a ofrecer o determinados servicios: servicio de venta. La forma en que comprenden el proceso iterativo.. 69 . de alquiler. Les digo que la abstracci´n de los requisitos en forma de m´dulos o o o grupos no sirve m´s que para contaminar el software con falsos requisitos de a negocio y para limitarnos a la hora de implementar. Con ATDD nos convertimos un poco en psic´logos en lugar de pretender ser adivinos. Qu´ y no C´mo e o valor para el negocio del cliente. aquellos conjuntos l´gicos que o tengan relaci´n con una estrategia de negocio. es decir. Entonces reconduzco la conversaci´n haci´ndoles ver que su descripci´n o e o se corresponde en realidad con varios ejemplos. Una vez llevo la voz cantante. Por ejemplo. aparece una lista de pisos en alquiler.. Salvo casos muy justificados. Los unicos m´dulos que hay que identificar son ıcil ´ o los que tienen valor de negocio. Uno de los motivos por los que el cliente se empe˜a en n pedir la soluci´n de una determinada manera es porque se ha encontrado con o profesionales poco experimentados que no le han sabido sugerir las formas adecuadas o que no llegaron a aportarle valor para su negocio. generalmente sabe decirnos si es o u no es eso lo que busca. Encuentro particularmente dif´ practicar ATDD cuando los due˜os de ıcil n producto est´n mal acostumbrados al sistema cl´sico en el que el an´lisis a a a de los requisitos acaba produciendo un diagrama de componentes o m´dulos o y luego un diagrama de clases. En las primeras reuniones de an´lisis. se a empe˜an en que dibujemos ese diagrama de m´dulos en los que el sistema n o se va a dividir a pesar de que les explique que eso no aporta m´s valor a su a negocio. cuando le sugerimos ejemplos sin ambig¨edad ni definiciones. es sent´ndose frente a a ellos en un lugar c´modo y adoptando el rol de psic´logo de las pel´ o o ıculas norteamericanas. ıa veces. el usuario no sabe exactamente lo que quiere pero. el Due˜o del n Producto no debe decir c´mo se implementa su soluci´n.2. aunque a veces les resulta dif´ de ver en un principio.

e As´ comienzan a involucrarse m´s en el desarrollo y todos comenzamos a ı a hablar el mismo idioma. el sistema muestra la lista de pisos de toda la provincia de Santa Cruz de Tenerife En la lista. entienden que la distancia entre los expertos en desarrollo y los expertos en negocio va menguando y dejan de preocuparse por diagramas abstractos. no dejan de ser m´quia nas muy tontas. a su vez. el sistema muestra una lista de pisos que cumplen las tres condiciones Buscando con el texto "Tenerife". cada piso se muestra mediante una fotograf´a ı y el n´mero de habitaciones que tiene u Para responder si los ejemplos son verdaderos o falsos.. Dejan de ir teniendo pensamientos m´gicos para ser conscientes de la precisi´n con que tenemos a o que definir el funcionamiento del sistema. que no soy experto en su negocio. el sistema muestra una lista de pisos que no superan los 600emensuales de alquiler y que se encuentran en la ciudad de Santa Cruz de Tenerife Buscando que el precio est´ entre 500ey 700ey e que tenga 2 habitaciones e introduciendo el texto "Santa Cruz de Tenerife". Les cuento que si esas decisiones sobre el negocio no me las validan ellos. e introduciendo el texto "Santa Cruz de Tenerife". El Due˜o de Producto puede revisar los tests de acepn taci´n y ver cu´ntos se est´n cumpliendo. e n introduciendo el texto "Santa Cruz de Tenerife". aunque los ordenadores hayan avanzado mucho. ¿Est´ hecho o no? a Cap´ ıtulo 3 Buscando que el precio sea inferior a 600e. Conocemos en qu´ punto estamos y c´mo a e o vamos progresando. es que vamos a poder comprobar muy r´pido si el programa a est´ cumpliendo los objetivos o no. A partir de ese momento.. ellos mismos descubren dudas sobre lo que necesitan para su negocio. por los ejemplos. “¿Tenemos que pensar todas estas cosas?” Y tengo que contarles que. 3. tendr´ que decidir yo. Al final. Entonces dicen. as´ que nuestro trabajo gana una o a a ı 70 . ¿Est´ hecho o no? a Otra ventaja de dirigir el desarrollo por las historias y.3. todo esto no consiste en otra cosa que en escribir ejemplos e implementarlos. el sistema muestra una lista de pisos que cumplen las tres condiciones Buscando que tenga 3 habitaciones y 2 cuartos de ba~o.3.3.

el o o mantenimiento se encarecer´ demasiado. se siguen escribiendo descripciones. la regresi´n completa de una aplicaci´n puede llevar ıa o o varios d´ en los que el equipo de calidad ejecuta cada parte de ıas la aplicaci´n con todas y cada una de sus posibles variantes. si no. Si en lugar de ejemplos. o $ El contexto es esencial % Fuera del contexto ´gil. Regresi´n viene de regresar. una tortura y un gasto econ´mico importante. ejecutamos o o todas las pruebas posibles.pdf 71 . Antes de lanzar una nueva versi´n de un producto en producci´n. Pi´nselo bien: ¡la propia m´quina es o e a capaz de decirnos si el programa cumple las especificaciones el cliente o no! De cara a hacer modificaciones en nuevas versiones del programa y lanzarlo a producci´n. Es una gran manera de fomentar una estrecha colaboraci´n entre todos los roles el equipo. puesto que regresamos a o funcionalidad desarrollada en la versi´n anterior para validar que o no se ha roto nada. La herramienta con la que se escriben los tests de aceptaci´n tiene que minimizar la cantidad de c´digo o o que requiere esa conexi´n entre las frases y el c´digo del sistema.com/papers/storytest.4. lo cual se traduce en un ahorro considerable. El contexto es esencial confianza tremenda. a ' 3. ATDD/STDD es un engranaje que a cuesta poner en marcha pero que da sus frutos. como se puede leer en este art´ ıculo de la revista Better Software de 20042 . & Los tests de regresi´n deben su nombre al momento en que se o ejecutan.Cap´ ıtulo 3 3. No entraremos en ese tema tan escabroso al que yo llamar´ m´s bien estar dispuestos a invertir el tiempo. estaremos aumentando la cantidad de trabajo considerablemente con lo cual el aumento de coste puede no retornar la inversi´n. tanto manuales como autom´ticas paa ra corroborar que tanto las nuevas funciones como las existentes funcionan. ATDD tiene pocas probabilidades de ´xito ya que a e si los analistas no trabajan estrechamente con los desarrolladores y testers. 2 http://industriallogic. no a su formato ni a otras caracter´ ısticas. no tiene sentido encarg´rselos o a a los desarrolladores. ¿Alguien tiene tiempo?. m´s que tener ıa a a o no tener tiempo. Tener tiempo es un asunto ıa muy relativo y muy delicado. el tiempo que tardamos en efectuar las pruebas de regresi´n o o disminuye de manera dr´stica. Cuando no se dispone de una completa bater´ de tests. no se podr´ originar un flujo de comunicaci´n suficientemente rico como para a o que las preguntas y respuestas aporten valor al negocio.4. ser´ malgastar el dinero. Hao blando en plata. Si los due˜os de producto (cliente y analistas) no tienen o n tiempo para definir los tests de aceptaci´n.

Gojko Adzic[1] tiene un libro basado en FitNesse y por supuesto cabe destacar su famoso libro sobre Acceptance Testing[2]. Mike Cohn escriıa bi´ uno muy popular titulado User Stories Applied[5] que le recomiendo eno carecidamente leer. Menci´n especial tambi´n al cap´ o e ıtulo sobre ATDD de Lasse Koskela en Test Driven[9] y los sucesivos.pdf 72 . En la parte pr´ctica de este libro tendremos ocasi´n de ver algunos ejema o plos m´s aunque. por motivos de espacio. o public´ un paper que puede leerse online3 e incluye ejemplos en el framework o Robot. si bien se podr´ escribir un libro sobre ello. a 3 http://testobsessed. que incluyen ejemplos sobre el framework FIT.com/wordpress/wp-content/uploads/2008/12/atddexample.3. en colaboraci´n con otros expertos de la talla de Brian Marick. no es exhaustiva. Elisabeth Hendrickson. El contexto es esencial Cap´ ıtulo 3 Desgraciadamente no podemos extendernos m´s con respecto a ATDa D/STDD.4.

Por aspecto quiero decir que.dhemery. por ejemplo. Cada vez que vamos a programar un test. Sin embargo. la Wikipedia aporta sificaciones de tests en un post reciente otro punto de vista complementario a los anteriores3 . y otra podr´ ser la de ıa ıa los que escriben tests para aplicaciones ya implementadas que todav´ no ıa los tienen. ni sus definiciones son extensas. Ha sido fueno te de discusi´n en los ultimos a˜os y sigue sin existir universalmente de mao ´ n nera categ´rica. Por su parte.objectmentor. para ser coherentes a la u hora de escribirlos. Emery hace una extensa recopilaci´n de los posibles o aspectos o puntos de vista y los tipos que alberga cada uno de ellos1 .org/wiki/Software testing 73 . No es la unica que existe. No existen reglas universales o a para escribir todos y cada uno de los tipos de tests ni sus posibles combinaciones. Cada equipo tiende a adoptar sus propias convenciones. es conveniente que tengamos una idea de c´mo es cada tipo de e o test. As´ se habla. Un mismo test puede ser de varios tipos. Definitivamente. del aspecto visibilidad (si se sabe ı lo que hay dentro del SUT o no). Dale H. por ejemplo. el test se puede clasificar de una mau o nera o de otra.Cap´tulo ı 4 Tipos de test y su importancia La nomenclatura sobre tests puede ser francamente ca´tica. Es una cuesti´n que llega a ser artesanal. Una coe munidad podr´ ser la de los que practicamos TDD. tenemos que 1 2 http://cwd. del aspecto potestad (a qui´n pertenece e el test). ya o que existen distintos aspectos a considerar para denominar tests. etc. incluso mir´ndolo desde la perspectiva de un solo equipo de desarrollo ya que su claa sificaci´n depender´ del aspecto a considerar. seg´n c´mo se mire. seg´n las convenciones que hayamos elegido. Michael Feathers tambi´n enumera varias clae 2 .com/articles/2009/04/13/x-tests-are-not-x-tests 3 http://en.wikipedia. cada comunidad usa unos t´rminos diferentes. m´s all´ de o a a los t´rminos.com/2004/04/dimensions/ http://blog. pero nos da una idea de ´ la complejidad del problema.

Terminolog´ en la comunidad TDD ıa Cap´ ıtulo 4 estar seguros de por qu´ lo escribimos y de qu´ estamos probando. dependiendo de o c´mo se haya escrito el test. o Cuando se escribe el c´digo que permite ejecutar este test. que los escribimos antes que nada. distinguimos entre tests escritos por desarrolladores y tests escritos por el Due˜o del Producto. Nosotros hablaremos de aceptaci´n porque se usa m´s o a Uno de los padres de NUnit. indique que no estamos seguros de e por qu´ lo escribimos. Los tests que pertenecen a u al Due˜o del Producto se llaman tests de cliente o de aceptaci´n. con m´s de 30 a˜os de experiencia: a n http://www. Usamos otros t´rminos. no tiene que ser as´ ı. Recordemos que el Due˜o del Producto n n es el analista de negocio o bien el propio cliente. Terminolog´ en la comunidad TDD ıa Desde el aspecto potestad.charliepoole.php 4 74 .4. En la siguiente secci´n veremos los t´rminos m´s comunes dentro de la o e a comunidad TDD y sus significados. Probablemente no a e tengamos claro el dise˜o que queremos. No es una regla exacta porque. o sea. Es una a a heur´ ıstica a tener en cuenta. Charlie n o Poole4 prefiere llamarles tests de cliente ya que por aceptaci´n se podr´ o ıa entender que se escriben al final cuando. pero e sabemos que un test de caja negra podr´ coincidir con un test unitario basado ıa en el estado.org/cp.1. realmente. es decir. Es extree e madamente importante tener claro qu´ queremos afirmar con cada test y por e qu´ lo hacemos de esa manera. se entiende que el cliente acepta el resultado. o puede que el test no est´ probando n e lo que debe. Y tambi´n por esto es un t´rmino provocador. De hecho. Por esto se habla de aceptaci´n. o que no estemos delimitando responsabilidades adecuadamente. Lo ideal es que el analista de negocio ayude al cliente a escribir los tests para asegurarse de que las afirmaciones est´n totalmente libres de ambig¨edad. 4. Y un test de caja blanca podr´ coincidir con un test unitario ıa basado en la interacci´n. mirando los tests seg´n a qui´n u e le pertenecen. si no conseguimos darle nombre e a lo que hacemos. en TDD partimos de tests de aceptaci´n (ATDD) para conectar o requerimientos con implementaci´n. y se ejecuta o positivamente. o quiz´s estemos escribiendo m´s tests de los que son necesarios. al haber o e e clientes que niegan que un test de aceptaci´n positivo signifique que aceptan o esa parte del producto. Podr´ ser que el hecho de no saber determie ıa nar qu´ tipo de test vamos a programar. puede que no sea unitario sino de integraci´n. En el ´mbito de TDD no hablamos de tests desde el aspecto visibilidad a (t´ ıpicamente tests de caja blanca y caja negra).1. o o Lo importante es ser capaces de entender la naturaleza de los tests. Dicho de otro modo. quiz´s no sepamos por qu´ lo hacemos.

los que pertenecen al Due˜o del Producto. se agrupan los tests que pertenecen a desarrolladores y.1. A la izquierda. aunque convendr´ recordar lo peligrosa a que puede llegar a ser esta denominaci´n. Desarrollo Dueño de Producto Tests Unitarios Tests de Aceptación/Cliente Tests Funcionales Tests de Integración Tests de Sistema 4. Como se vio en el cap´ ıtulo de ATDD. un test de aceptaci´n es un ejemplo escrito con el lenguaje del o cliente pero que puede ser ejecutado por la m´quina.Cap´ ıtulo 4 4. algunos tipos de tests contienen a otros.1. Terminolog´ en la comunidad TDD ıa en la literatura que test de cliente. Tests de Aceptaci´n o ¿C´mo es un test de aceptaci´n? Es un test que permite comprobar que o o el software cumple con un requisito de negocio. o En el siguiente diagrama se muestra la clasificaci´n de los tests t´ o ıpica de un entorno ATDD/TDD. a la derecha. n A su vez. Recordemos algunos a ejemplos: 75 .1.

ıa Los tests de carga y de rendimiento son de aceptaci´n cuando el cliente los o considera requisitos de negocio. Tests Funcionales Todos los tests son en realidad funcionales. etc. Terminolog´ en la comunidad TDD ıa Cap´ ıtulo 4 El producto X con precio 50etiene un precio final de 55edespu´s de aplicar el impuesto Z e Si el paciente naci´ el 1 de junio de 1981. el c´digo que estamos probando o 76 . se distingue entre test funcional y test no funcional. ya que integra varias partes del o sistema. ser´n tests de a desarrollo. Un test funcional es un test de aceptaci´n pero.2. Hasta ahora. 4.4. usando los mismos ıa puntos de entrada (aqu´ s´ es la interfaz gr´fica) y llegando a modificar la ı ı a base de datos o lo que haya en el otro extremo. un test del ıan ı sistema se ejercita tal cual lo har´ el usuario humano. cuestiones que van m´s all´ de la o a a funcionalidad. o e 4. Es decir. cuando se habla del aspecto funcional. uno de acepo taci´n. no o o es que estemos probando el servidor web o el servidor SMTP aunque. Si el cliente no los requiere.1. incluso.1. Se trata de un test que puede ir. comprueban alguna funcionalidad con o valor de negocio. de extremo a extremo de la aplicaci´n o del sistema. capacidad de carga de la aplicaci´n. todos los tests de aceptaci´n que hemos viso to son tests funcionales. Los tests de aceptaci´n tienen un ´mbito mayor o a porque hay requerimientos de negocio que hablan de tiempos de respuesta. pero no se refiere a administraci´n de sistemas.1. Tests de Sistema Es el mayor de los tests de integraci´n. No obstante. ¿C´mo se puede automao tizar el uso de la interfaz de usuario y validar que funciona? Hay software 5 Subject Under Test. aunque en el nivel m´s elemental sea un m´todo de o a e una clase. puesto que todos ejercitan alguna funci´n del SUT5 .3. Un test funcional es un subconjunto de los tests de aceptaci´n. tales servicios. As´ pues. podr´ ser una parte de nuestro sistema. Se habla de sistema porque es un t´rmino m´s o e a general que aplicaci´n. ıa ı. no tiene por qu´ ser funcional. o su edad es de 28 a~os en agosto de 2009 n ¿Los tests de aceptaci´n no usan la interfaz de usuario del programa? o Podr´ ser que s´ pero en la mayor´ de los casos la respuesta debe ser no.

como por ejemplo a los unitarios.org/es/firefox/ 8 http://wtr. si la interfaz de usuario es web.net/ 12 http://es. Sirva el ejemplo para recalcar que no hay una sola herramienta ni forma de escribir tests de sistema.org/projects/ide/ http://www. O sea. Si la u cobertura de otros tipos de tests de granularidad m´s fina.org/wiki/GNU Wget 13 http://es. Cada vez que quiera volver a comprobar ıe que el sitio web responde igual ante esa entrada. ya que esta redundancia se traduce en un aumento del costo http://seleniumhq. cada vez que alguien accede a una p´gina. es amplia. Para aplicaciones eso critas con el framework Django (Python). Podr´ ıamos hacer un script con alg´n comando que se conecte u a una URL del servidor. puede romperlos. El propio Selenium lo permite.com/documentation/models/test client/ 10 http://msdn.djangoproject. m´s bien depende de cada sistema. a Los tests de sistema son muy fr´giles en el sentido de que cualquier cama bio en cualquiera de las partes que componen el sistema.1 en el fichero de log con Grep13 . s´lo tengo que ejecutar el o test generado por Selenium.Cap´ ıtulo 4 4. al estilo de Wget12 desde la misma m´quina y despu´s a e buscar la ip 127. Para aplicaciones de escritorio.wikipedia. comprobando el estado de la ejecuci´n o o con sentencias condicionales o asertivas.0. la probabilidad de que los errores s´lo se detecten o con tests de sistema es muy baja. no es productivo escribir tests de sistema para todas las posibles formas de uso del sistema.sourceforge. o Existen muchas formas de probar un sistema. Terminolog´ en la comunidad TDD ıa que permite hacerlo. el plugin Selenium6 para el navegador Mozilla Firefox7 nos permite registrar nuestra actividad en una p´gina web como si estuvi´ramos grabando un v´ a e ıdeo para luego reproducir la secuencia autom´ticamente y detectar cambios en la a respuesta del sitio web. Pongamos que grabo la forma en que relleno un formulario con una direcci´n de correo electr´nico incorrecta para que el sitio o o web me env´ un mensaje de error. o a y accionarlos desde c´digo fuente. No es recomendable escribir un gran n´mero de ellos por su fragilidad.org/wiki/Grep 7 6 77 .microsoft.1.0.Net). Una de las herramientas m´s populares es Watir8 para Ruby y sus versiones para a otros lenguajes de programaci´n (Watin para .mozilla-europe.aspx 11 http://nunitforms. que si hemos ido haciendo TDD. hay frameworks espec´ ıficos como UIAutomation10 o NUnitForms11 que tambi´n permiten manipular la interfaz gr´fica desde e a c´digo.wikipedia. Hay herramientas que permiten hacer lo mismo mediante programaci´n: nos dan una API para seleccionar controles gr´ficos. Por ejemplo.rubyforge.com/en-us/library/ms747327. Supongamos que hemos implementado un servidor web ligero y queremos validar que.org/ 9 http://www. se registra su direcci´n ip en un fichero de a o registro (log). se utiliza el cliente web9 .

un requisito de negocio podr´ involucrar la GUI. Esto har´ que nuestros tests sean menos fr´giles y estaremos a a alcanzando el mismo nivel de cobertura de posibles errores. Todo test unitario debe ser: o At´mico o Independiente Inocuo R´pido a 78 . tan s´lo nos har´n falta unos pocos o a tests de sistema para comprobar que la GUI est´ bien conectada a la l´gica a o de negocio. En la documentaci´n de algunos frameworks.1.4. Un test funcional es una frase escrita o en lenguaje natural que utiliza el sistema para ejecutarse. e En casos puntuales. se podr´ ir desechando tests de sistema. Por el contrario.1. es un error. a Cada test unitario o test unidad (unit test en ingl´s) es un paso que andamos e en el camino de la implementaci´n del software. ıa e 4. ıan ¿Por qu´ se les llama tests de aceptaci´n y tests funcionales a los tests e o de sistema? En mi opini´n. Llamar test funcional a un test de sistema no es un problema siempre que adoptemos esa convenci´n en todo el equipo y todo el mundo o sepa para qu´ es cada test. el requisito de negocio no debe entrar en c´mo es el dise˜o de la interfaz de o n usuario. En a ese caso. Por tanto. si no tenemos escrito absolutamente ning´n tipo de test. los ineludibles. a tests que son o de sistema. Sin embargo. Si la mayor´ de los criterios de aceptaci´n ıa o se validan mediante tests funcionales. Terminolog´ en la comunidad TDD ıa Cap´ ıtulo 4 de mantenimiento de los tests.4. sino desde el que necesita para validar el requisito funcional. Luego. tal ıa como pasa con el cliente de Gmail del iPhone por ejemplo. el test funcional no entrar´ a ejecutar el sistema desde ıa el extremo de entrada que usa el usuario (la GUI). Tests Unitarios Son los tests m´s importantes para el practicante TDD. llaman tests funcionales. En el caso de probar que una direcci´n de email es incorrecta. el test utilizar´ la parte del o a sistema que valida emails y devuelve mensajes de respuesta. Est´ claro que el a negocio en ese proyecto est´ directamente relacionado con la propia GUI. blindar la aplicaci´n con tests de sistema u o ser´ el paso m´s recomendable antes de hacer modificaciones en el c´digo a a o fuente. cuando ya hubiesen tests unitarios para los nuevos cambios introducidos. llaman test unitarios a tests o que en verdad son de integraci´n y. el test de sistema ser´ tambi´n un test funcional.

Esto es. Debe funcionar siempre igual independientemente de que se ejecuten otros tests o no. En los siguientes cap´ ıtulos los veremos en detalle con ejemplos de c´digo. Al ejecutarlo una vez. la llamada al m´todo provoca que internamente se invoque a otros e m´todos. Hay principalmente dos formas de validar el resultado de la o ejecuci´n del test: validaci´n del estado y validaci´n de la interacci´n. Es ıa como si no se hubiera ejecutado. La raz´n es a o que un test at´mico nos evita tener que usar el depurador para encontrar un o defecto en el SUT.html https://code. cuando esto ocurre. para evitar abusar de los dobles de prueba. produce exactamente el mismo resultado que al ejecutarlo veinte veces. Lo ideal es que los tests unitarios ataquen a m´todos lo e m´s planos posibles.junitmax. ni env´ emails ni crea ficheros. o o Se llama JUnit Max14 . Olof Bjarnason ha escrito otra similar y libre para Python15 Para conseguir cumplir estos requisitos. e o que es menos fino. Terminolog´ en la comunidad TDD ıa Si no cumple estas premisas entonces no es un test unitario. o o o o o del comportamiento.com/junitmax/subscribe. A ´ o veces. aunque se ejecute con una herramienta tipo xUnit. es decir. resulta muy improductivo. R´pido tiene que ser porque ejecutamos un gran n´mero de tests caa u da pocos minutos y se ha demostrado que tener que esperar unos cuantos segundos cada rato.1. o 14 15 http://www. que prueben lo que es indivisible. Independiente significa que un test no puede depender de otros para producir un resultado satisfactorio. puesto que su causa ser´ muy evidente. El mismo m´todo puede presentar distintas respuestas ante distintas e entradas o distinto contexto.Cap´ ıtulo 4 4. ni los borra. de un unico camino de ejecuci´n. Como veremos a en la parte pr´ctica.launchpad. probar´ un solo comportamiento de un m´todo de una a e clase. hay veces que vale la pena ser menos estrictos con la a atomicidad del test. decimos que el test tiene menor granularidad. At´mico significa que el test prueba la m´ o ınima cantidad de funcionalidad posible. No altera la base de datos. Inocuo significa que no altera el estado del sistema. para evitar dejar o de trabajar en c´digo mientras esperamos por el resultado de la ejecuci´n. El test unitario se ocupar´ exclusivamente de a uno de esos comportamientos.net/ objarni/+junk/pytddmon 79 . No puede ser parte de una secuencia de tests que se deba ejecutar en un determinado orden. es decir. La rapidez es tan importante n o que Kent Beck ha desarrollado recientemente una herramienta que ejecuta los tests desde el IDE Eclipse mientras escribimos c´digo. un test unitario aisla la parte del SUT que necesita ejercitar de tal manera que el resto est´ inactivo durante a la ejecuci´n. Un s´lo test tendr´ que ejeo ıa cutarse en una peque˜a fracci´n de segundo.

I. fue bien. tienen la capacidad de crear una base de datos temporal antes de ejecutar la bater´ de tests. es conveniente que luego la deje como estaba. no es necesario repetir el o test para otra variante de la l´gica de negocio.T Como los acr´nimos no dejan de estar de moda. Una vez que se ha probado u que dos m´dulos. Repeatable. cabe destacar que las cao racter´ ısticas de los tests unitarios tambi´n se agrupan bajo las siglas F. Son. Como o su nombre indica. F. a Si tiene que acceder a base de datos. integraci´n significa que ayuda a unir distintas partes del o sistema. por tanto. por moo tivos de productividad es conveniente que traten de ser inocuos y r´pidos. Es o el complemento a los tests unitarios.1. de granularidad m´s gruesa y m´s fr´giles a a a que los tests unitarios. donde hab´ ıamos “falseado” el acceso a datos para limitarnos a trabajar con la l´gica de manera aislada.T e que vienen de: Fast.1. peque˜o caza con at´mico y transparente quiere decir que n o el test debe comunicar perfectamente la intenci´n del autor.5. para eso habr´n varios tests o a unitarios. los tests de integraci´n son la pieza del puzzle que nos fal´ o taba para cubrir el hueco entre los tests unitarios y los de sistema. s´lo que estos pueden romper las reglas. Terminolog´ en la comunidad TDD ıa Cap´ ıtulo 4 Los desarrolladores utilizamos los tests unitarios para asegurarnos de que el c´digo funciona como esperamos que funcione. que se destruye al terminar las pruebas. Tests de Integraci´n o Por ultimo. Independent. al igual que el cliente usa o los tests de cliente para asegurarse que los requisitos de negocio se alcancen como se espera que lo hagan.I.S. la l´gica de negocio entiende datos reales.4.R. tambi´n hay quien ejecuta los tests unitarios e creando y destruyendo bases de datos temporales pero es una pr´ctica que a debe evitarse porque los segundos extra que se necesitan para eso nos hacen 80 . T´ o n ıpicamente. Un test de o integraci´n podr´ ser aquel que ejecuta la capa de negocio y despu´s cono ıa e sulta la base de datos para afirmar que todo el proceso. Un test de integraci´n puede escribir y leer de base de datos para o comprobar que.S. Por eso. Small y Transparent. desde negocio hacia abajo. Los tests de integraci´n se pueden ver como tests de sistema peque˜os. Como se trata de ıa una herramienta incorporada. Repetible encaja con inocuo. tambi´n se escriben usando herramientas xUnit y tienen un aspecto paree cido a los tests unitarios. Aunque los tests de integraci´n pueden saltarse las reglas. algunos frameworks para Ruby y Python entre otros. o 4.R. con lo que el n´mero de tests de integraci´n tiende u o a ser menor que el n´mero de tests unitarios. objetos o capas se integran bien. efectivamente.

porque este no es un libro sobre c´mo hacer pruebas de software exclusivamente sino sobre c´mo o o construir software bas´ndonos en ejemplos que ilustran los requerimientos del a negocio sin ambig¨edad.Cap´ ıtulo 4 4. Los tests unitarios.1. Terminolog´ en la comunidad TDD ıa perder concentraci´n. Los tests unitarios deben pertenecer a suites16 difereno tes a los de integraci´n para poderlos ejecutar por separado. En los pr´ximos o o cap´ ıtulos tendremos ocasi´n de ver tests de integraci´n en detalle. a 16 Una suite es una agrupaci´n de tests o 81 . de integraci´n y de aceptaci´n u o o son los m´s importantes dentro del desarrollo dirigido por tests. o o Concluimos el cap´ ıtulo sin revisar otros tipos de tests.

1. Terminolog´ en la comunidad TDD ıa Cap´ ıtulo 4 82 .4.

se soportan las anotaciones por lo que o NUnit y JUnit se parecen todav´ m´s. los ejecuta a todos por igual. e igual que pasaba con la versi´n 3 de JUnit. Para etiquetar los m´todos como tests. ser´n tests unitarios o de integraci´n. e Si lo son. etc. El framework no distingue el tipo de test que es.Cap´tulo ı 5 Tests unitarios y frameworks xUnit En cap´ ıtulos previos hemos citado xUnit repetidamente pero xUnit como tal no es ninguna herramienta en s´ misma. En este cap´ ıtulo todos los tests son unitarios. Adem´s ıa a en Java. Quienes debemos hacer la distinci´n somos nosotros mismos.Net usamos anotacioo e nes y atributos respectivamente. ıa a Una clase que contiene tests se llama test case (conjunto de tests) y para definirla en c´digo heredamos de la clase base TestCase del framework o correspondiente (con JUnit 3 y Pyunit) o bien la marcamos con un atributo especial (JUnit 4 y NUnit). SUnit fue creado por Kent Beck para la plataforma SmallTalk y se ha portado a una gran variedad de lenguajes y plataformas como Java (JUnit). En Python se hace precediendo al nombre del m´todo con el prefijo test (ej: def test letter A isnot a number). en Java y . sabremos hacerlo con cualquier otro framework tipo xUnit porque la filosof´ es siempre la misma. aunque podr´ ser incluso de a o ıan sistema. . desde la versi´n 4 de JUnit. En pr´ximos cap´ o ıtulos veremos c´mo escribir tambi´n tests de integrao e ci´n. C++ (CppUnit). Perl (PerlUnit). una o vez que tenemos claro qu´ queremos probar y por qu´ lo hacemos con un e e framework xUnit. Los m´todos de dicha clase pueden ser tests o no. Los m´todos que no est´n marcao e a 83 . Ruby (Rubyunit). La letra x es tan s´lo un prefijo ı o a modo de comod´ para referirnos de manera gen´rica a los numerosos fraın e meworks basados en el original SUnit. Python (PyUnit). Si aprendemos a trabajar con NUnit y PyUnit como veremos en este libro.Net (NUnit).

Las tres partes del test: AAA Un test tiene tres partes.. o o o Se afirma que nuestras espectativas sobre el resultado se cumplen. entonces podemos definir otro m´todo o funci´n en la misma clase. namespace EjemplosNUnit { [ TestFixture ] public class NameNormalizerTests { 1 Librer´ de enlace din´mico.framework a las referencias para disponer de su funcionalidad. bien o o mediante validaci´n del estado o bien mediante validaci´n de la interacci´n. MonoDevelop. a Veamos varios ejemplos en lenguaje C# con el framework NUnit. Equivalente en . que a´ne tal c´digo. por lo que xUnit provee una manera sencilla de organizar y compartir o este c´digo: los m´todos especiales SetUp y TearDown. Recordemos que un test unitario no puede modificar el estado del sistema y que todos los tests de este cap´ ıtulo son unitarios. Si la estapa de preparaci´n es com´n u o u a varios tests de la clase pero no a todos. a definir el escenario adecuado para despu´s llamar e al SUT y TearDown a eliminar posibles objetos que no elimine el recolector de basura. El acto consiste en hacer la llamada al c´digo que queremos probar (SUT) o y la afirmaci´n o afirmaciones se hacen sobre el resultado de la ejecuci´n.Net a los .. Tendremos que crear un nuevo proyecto de tipo librer´ con el IDE (VisualStudio.jar de Java ıa a 84 . using NUnit .1. Una parte de la preparaci´n puede estar contenida en el m´todo SetUp. No le pondremos e o u o la etiqueta de test sino que lo invocaremos desde cada punto en que lo necesitemos. Las tres partes del test: AAA Cap´ ıtulo 5 dos como tests. Estos ejemplos realizan la validaci´n a trav´s del estado. El estado o e del que hablamos es el de alg´n tipo de variable. adem´s de a a NUnit. § 1 2 3 4 5 6 7 8 using System . Si no se cumplen el framework marcar´ en rojo cada falsa expectativa. que se identifican con las siglas AAA en ingl´s: e Arrange (Preparar). Es normal o que haya c´digo com´n para preparar los tests o para limpiar los restos su ejeo u cuci´n. no hablamos del estado del u sistema. Act (Actuar). ıa SharpDevelop. SetUp suele destio e narse a crear variables. si o e es com´n a todos los tests de la clase. 5.1. Assert (Afirmar). se utilizan para unificar c´digo requerido por ellos. Framework .) e incorporar la DLL1 nunit. tambi´n ı e se incluye la referencia a la DLL o ejecutable (. As´ en el proyecto que contiene los tests.5.exe) del SUT. Lo ideal es tener el SUT en una DLL y sus tests en otra.

Charlie Poole comenta que es una buena idea agrupar tests dentro de un mismo conjunto cuando sus datos de contexto son comunes. Es com´n que los tests u de integraci´n dependan de datos que tienen que existir en la base o de datos. F i r s t L e t t e r U p p e r C a s e ( name ). Estos datos que son un requisito previo. Algunos frameworks de tests como el de Django (que se basa en PyUnit) permiten definir conjuntos de datos de contexto mediante JSONa que son autom´ticamente insertados en la base de datos antes de cada a test y borrados al terminar. Los datos de contexto o fixtures son aquellos que se necesitan para construir el escenario que require el test. no debemos confundirnos con los datos de contexto. por eso optaron por llamarle TestFixture en lugar de TestCase. En el ejemplo de arriba la variable name es una variable de contexto o fixture. ' El t´rmino Fixture se utiliza en realidad para hablar de los datos e de contexto de los tests. AreEqual ( " Pablo Rodriguez " . Los datos de contexto no son exclusivamente variables sino que tambi´n pueden ser datos obtenidos de alg´n e u sistema de almacenamiento persistente. ıa a TestCase. // Assert Assert . De hecho se habla m´s de fixtures a cuando son datos que cuando son variables. son igualmente datos de contexto o fixtures. a $ & Un diccionario con sintaxis javascript El nombre que le hemos puesto a la clase describe el conjunto de los tests 85 % . result ).Cap´ ıtulo 5 5. NUnit decidieran llamar TestFixture al atributo que etiqueta un conjunto de tests. ser´ m´s claro si el atributo se llamase ı. } } } ¦ ¥ Hemos indicado a NUnit que la clase es un conjunto de tests (test case). utilizando para ello el atributo TestFixture.1. As´ aunque los desarrolladores de ı. // Act string result = normalizer . Las tres partes del test: AAA 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 [ Test ] public void F i r s t L e t t e r U p p e r C a s e () { // Arrange string name = " pablo rodriguez " . El que la palabra fixture aparezca aqu´ puede ser desconcertante. NameNormalizer normalizer = new NameNormalizer ().

Es una forma de detectar un mal dise˜o. qu´ misi´n cumple. 86 . Si no sabemos c´mo resumir lo que hace el test en menos de setenta o letras. En nuestro ejemplo la clase contiene un unico test que est´ marcado ´ a con el atributo Test. entonces lo m´s probable es que tampoco sepamos qu´ test vamos a a e escribir. NameNormalizer normalizer = new NameNormalizer (). Es la mejor forma de documentarlo. Las tres partes del test: AAA Cap´ ıtulo 5 que va a contener. Cuando NUnit ejecute el m´todo. Debemos utilizar conjuntos de tests distintos para probar grupos de funcionalidad distinta o lo que es lo mismo: no se deben incluir todos los tests de toda la aplicaci´n en un solo conjunto de tests (una sola o clase). A veces cuando un c´digo requiere documentaci´n o o es porque no est´ lo suficientemente claro. La finalidad del test del ejemplo es comprobar que el m´todo FirstLetterUpperCase de la clase NameNormalizer es e capaz de poner en may´scula la primera letra de cada palabra en una frase. Al positivo le llamamos luz verde o porque es el color que emplea la interfaz gr´fica de NUnit o s´ a ımplemente decimos que el test pasa. aqu´ est´ escrito a ı a meramente con fines docentes. No o e importa que el nombre del m´todo tenga cincuenta letras. Imaginemos que el c´digo del SUT ya est´ implementado y el test da o a luz verde. § 1 2 3 4 5 6 7 8 [ Test ] public void SurnameFirst () { string name = " gonzalo aller " . SurnameFirst ( name ).5. sino s´ ımplemente explicando el funcionamiento de un framework xUnit. En la pr´ctica nunca delimitamos con comentarios. es un antipatr´n porque vamos a terminar con un gran n´mero de tests y el o u esfuerzo de mantener todos sus comentarios es muy elevado. De hecho es un error que el comentario no coincida con lo que hace el c´digo y eso pasa o cuando modificamos el c´digo despu´s de haber escrito el comentario. Assert en ingl´s viene a a e significar afirmar. Poner un comentario de cabecera al test. Al resultado negativo le llamamos luz roja o bien decimos que el test no pasa. string result = normalizer . La ultima l´ ´ ınea dice: Afirma que la variable result es igual a ”Pablo Rodriguez”. u Es un test de validaci´n de estado porque hacemos la afirmaci´n de que o o funciona bas´ndonos en el estado de una variable. Los tests siempre son de tipo void y sin par´metros a de entrada. dar´ positivo si la e a afirmaci´n es cierta o negativo si no lo es. Pasemos al siguiente ejemplo recalcando que todav´ no estamos ıa practicando TDD. no le hace da˜o a e n nadie. bien e o n del test o bien del SUT. El nombre del test es largo porque es autoexplicativo.1. a En el cuerpo del test aparecen sus tres partes delimitadas con comentarios.

AreEqual ( " aller . } } } ¦ ¥ Antes de cada uno de los dos tests el framework invocar´ al m´todo SetUp a e record´ndonos que cada prueba es independiente de las dem´s. o es una regla de estilo que nos ayuda a identificarla r´pidamente como variable a 87 . Assert . Lo que e el test comprueba es que el m´todo SurnameFirst es capaz de recibir un e nombre completo y devolver el apellido por delante. A fin de eliminar c´digo duplicado la movemos hacia e o el SetUp. string result = _normalizer . } [ Test ] public void F i r s t L e t t e r U p p e r C a s e () { string name = " pablo rodriguez " .Cap´ ıtulo 5 5. gonzalo " . string result = _normalizer . Si nos fijamos bien vemos que la declaraci´n de la variable normalizer es o id´ntica en ambos tests. Las tres partes del test: AAA 9 10 Assert . El conjunto queda de la siguiente manera: § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 namespace EjemplosNUnit { [ TestFixture ] public class NameNormalizerTests { NameNormalizer _normalizer . Assert . a a Hemos definido normalizer como un miembro privado del conjunto de tests. SurnameFirst ( name ). result ). separado por una coma. } [ Test ] public void SurnameFirst () { string name = " gonzalo aller " . result ). El gui´n bajo (underscore) que da comienzo al nombre de la variable. } ¦ ¥ Es otro test de validaci´n de estado que creamos dentro del mismo conjunto o de tests porque el SUT es un m´todo de la misma clase que antes. result ). AreEqual ( " aller .1. [ SetUp ] public void SetUp () { _normalizer = new NameNormalizer (). AreEqual ( " Pablo Rodriguez " . F i r s t L e t t e r U p p e r C a s e ( name ). gonzalo " .

result ).1. SurnameFirst ( " gonzalo aller " ). F i r s t L e t t e r U p p e r C a s e ( " pablo rodriguez " ). lo mejor es liquidarla: o § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 namespace EjemplosNUnit { [ TestFixture ] public class NameNormalizerTests { NameNormalizer _normalizer . No aconsejo utilizarla si disponemos de un IDE (pero el libro a no lo es) 88 . result ). Assert . Por a u tanto lo que un test haga con la variable normalizer no afecta a ning´n otro. El recolector de basura es capaz de liberar la memoria que hemos reservado. AreEqual ( " aller . [ SetUp ] public void SetUp () { _normalizer = new NameNormalizer (). } [ Test ] public void SurnameFirst () { string result = _normalizer . } [ Test ] public void F i r s t L e t t e r U p p e r C a s e () { string result = _normalizer . se destruye y se vuelve a crear. Las tres partes del test: AAA Cap´ ıtulo 5 de la clase en cualquier parte del c´digo2 . evit´ndo efectos colaterales. salvo o o 2 Uncle Bob en Clean Code[11] y Xavier Gost en el Agile Open 2009 me han convencido definitivamente para que deje de utilizar esta regla en mi c´digo pero encuentro o que en el papel ayudar´. no hemos dejado ninguna referencia muerta por el camino. El m´todo SetUp crea una nueva o e instancia de dicha variable asegur´ndonos que entre la ejecuci´n de un test a o y la de otro. } } } ¦ ¥ Nos est´ quedando un conjunto de tests tan bonito como los muebles que haa cen en el programa de Bricoman´ de la televisi´n. Podr´ ıamos haber extraido tambi´n la variable name de los tests pero e como no se usa nada m´s que para alimentar al SUT y no interviene en la a fase de afirmaci´n. No hemos definido m´todo ıa o e tearDown porque no hay nada que limpiar expl´ ıcitamente. gonzalo " .5. Assert . AreEqual ( " Pablo Rodriguez " . La validaci´n de estado generalmente no tiene mayor complicaci´n.

Si por el contrario la ejecuci´n del SUT termina y no se ha lanzado la excepo ci´n esperada. Entonces se considera que el SUT se comporta como deseamos. SUT lanze una excepci´n de tipo EmptyNameException. Cuando se quiere probar que se lanza una excepci´n. Fail ( " Exception should be thrown " ). Assert . el SUT lanze una excepci´n de o tipo concreto. excepciones inespee radas. Adem´s. o esperamos que al invocar a SurnameFirst. pues no se valida estado ni tampoco interacci´n entre o colaboradores.Cap´ ıtulo 5 5. definida por nuestra o propia aplicaci´n. Se puede escribir el mismo test ayud´ndonos de atribun a tos especiales que tanto NUnit como JUnit (en este caso son anotaciones) incluyen. en el caso de PyUnit la llamada a fail no s´lo detiene el a o 89 . cuando no hay afirmaciones y ninguo na excepci´n interrumpe el test. Las tres partes del test: AAA § 1 2 3 4 5 6 7 8 9 10 11 que la ejecuci´n del SUT implique cambios en el sistema y tengamos que o evitarlos para respetar las cl´usulas que definen un test unitario. se considera que funciona. } ¦ ¥ El funcionamiento y significado de los dos ultimos tests es exactamente el ´ mismo. la llamada a Assert. Se considera validaci´n o o de comportamiento. Por eso colocamos un bloque catch.Exception en .Net). ExpectedMessage = " The name can not be empty " )] public void ThrowOnEmptyName () { _normalizer . Si usamos la excepci´n o e o gen´rica. se debe exo presar exactamente cu´l es el tipo de la excepci´n esperada y no capturar a o la excepci´n gen´rica (System. pasamos como par´metro una cadena vac´ Para tal entrada queremos que el a ıa. SurnameFirst ( " " ). [ Test ] [ ExpectedException ( " EmptyNameException " . Veremos a ejemplos en los pr´ximos cap´ o ıtulos.Fail abortar´ el test expl´ o ıa ıcitamente se˜alando luz roja. aunque o e no haya ninguna afirmaci´n. } catch ( EmptyNameException ){} } ¦ ¥ § 1 2 3 4 5 6 7 Cuando un test se ejecuta sin que una excepci´n lo aborte. estaremos escondiendo posibles fallos del SUT. Continuemos con la validaci´n de excepciones. Dentro de ese bloque no hay nada.1. as´ que la ejecuci´n del test ı o termina. Supongamos que a cualquiera de las funciones anteriores. Es decir. En el ejemplo. para que el test no se interrumpa. ´ste pasa. SurnameFirst ( " " ). ¿C´mo escribimos esta prueba con NUnit? o o [ Test ] public void ThrowOnEmptyName () { try { _normalizer .

El test unitario pretende probar exclusivamente el SUT. con lo cual a a o el bloque catch la captura y obtenemos un resultado totalmente confuso.5. Llega el turno de la validaci´n de interacci´n. dif´ ıcilmente podremos conseguir respetar las reglas de los tests unitarios. Si los o miembros de la colaboraci´n no se han definido con la posibilidad de inyectar o uno en el otro. El caso de validaci´n de interacci´n m´s com´n es el de una colaboraci´n o o a u o que implica alteraciones en el sistema. o a a La mayor´ de las veces. tenemos que validar que la interacci´n entre ambas partes se produce o y al mismo tiempo evitar que realmente se acceda a la base de datos. as´ que imaginemos un sistema que a ı gestiona el expediente acad´mico de los alumnos de un centro de formaci´n. e o Hay una funci´n que dado el identificador de un alumno (su n´mero de o u expediente) y la nota de un examen. Tratamos de aislarlo todo lo posible. Las tres partes del test: AAA Cap´ ıtulo 5 test marc´ndolo en rojo sino que adem´s lanza una excepci´n. Para llevar a cabo este tipo de validaciones es fundamental la inyecci´n de dependencias3 . actualiza su perfil en base de datos. es posible pero el c´digo del test termina siendo complejo de entender y mantener. Si el o m´todo a prueba es de tipo void. es s´lo una o o opci´n temporal. Creer´ ıamos que el SUT est´ lanzando la excepci´n cuando en realidad no lo a o hace pero no lo advertir´ ıamos. Luego ya habr´ un test de integraci´n a o que se encargue de verificar el acceso a base de datos. Es o un tipo de validaci´n de comportamiento. La validaci´n de interacci´n o o o o es el recurso que usamos cuando no es posible hacer validaci´n de estado. no se puede afirmar sobre el resultado e pero es posible que alg´n otro miembro de la clase refleje un cambio de u estado. Cuando el SUT debe colaborar con una clase que guarda en base de datos. o Un ejemplo vale m´s que las palabras. Supongamos que existen los objetos relacionales Student y Score y una clase DataManager capaz de recuperarlos y guardarlos en base de datos. porque los tests que validan interacci´n necesitan e o conocer c´mo funciona por dentro el SUT y por tanto son m´s fr´giles. que a 3 Ver Cap´ ıtulo 7 en la p´gina 111 a 90 . El SUT se llama ScoreManager y su colaborador ser´ DataManager. Elementos que modifican el estado del sistema son por ejemplo las clases que acceden a la base de datos o que env´ mensajes a trav´s de un servicio web (u otra comunicaci´n que salga ıan e o fuera del dominio de nuestra aplicaci´n) o que crean datos en un sistema de o ficheros. Es recomendable recurrir a esta o t´cnica lo menos posible. Con lenguajes interpretados como Python. se puede validar estado aunque no sea evidente a ıa simple vista.1. Quiz´s tengamos que consultarlo a trav´s de alguna propiedad a e del SUT en vez de limitarnos a un valor devuelto por una funci´n. No queremos probar toda la cadena desde el SUT hacia abajo hasta el sistema de almacenamiento.

5). uno para obtener un objeto e relacional dada su clave primaria.5). } public void Save ( IRelationalObject robject ) {} public void Create ( IRelationalObject robject ) {} } ¦ ¥ La interfaz IDataManager tiene tres m´todos. } ¦ ¥ Pero el m´todo AddScore no devuelve nada y adem´s estar´ actualizando la e a a base de datos. } ¦ ¥ La validaci´n de la interacci´n se hace con frameworks de objetos mock. vamos a implementar la soluci´n manualmente. 8. Actualizamos ´ el test: § 1 2 3 [ Test ] public void AddStudentScore () { 91 . Las fases de preparaci´n y acci´n las o o sabemos escribir ya: § 1 2 3 4 5 6 [ Test ] public void AddStudentScore () { ScoreManager smanager = new ScoreManager (). smanager .1. podemos inyectar una instancia que se haga pasar por buena pero que en verdad no acceda a tal base de datos. Las tres partes del test: AAA implementa la interfaz IDataManager.Cap´ ıtulo 5 5. Si el SUT va a o invocar a su colaborador para leer de la base de datos. smanager . 8. como o o muestra el siguiente cap´ ıtulo pero para comprender parte de lo que hacen internamente los mocks y resolver este test sin ninguna otra herramienta externa. ¿C´mo validamos? Lo primero es hacer que el colaborador de o ScoreManager se pueda inyectar: § 1 2 3 4 5 6 7 8 [ Test ] public void AddStudentScore () { IDataManager dataManager = new DataManager (). ScoreManager smanager = new ScoreManager ( dataManager ). § 1 2 3 4 5 6 7 8 9 10 11 12 13 public class MockDataManager : IDataManager { public IRelationalObject GetByKey ( string key ) { return new Student (). AddScore ( " 23145 " . operar y guardar. otro para guardarlo cuando se ha modificado y el ultimo para crearlo en base de datos por primera vez. AddScore ( " 23145 " .

la soluci´n propuesta es costosa o 92 .5). ScoreManager smanager = new ScoreManager ( dataManager ). dataManager .5. smanager . Tendremos que a˜adir algunas variables de n estado internas para controlarlo: public class MockDataManager : IDataManager { private bool _getKeyCalled = false . } public void VerifyCalls () { if (! _saveCalled ) throw Exception ( " Save method was not called " ). } ¦ ¥ No hemos tocado la base de datos y estamos validando que el SUT hace esas dos llamadas a su colaborador. 8. return new Student (). } public void Create ( IRelationalObject robject ) {} } ¦ ¥ § 1 2 3 4 5 6 7 8 9 10 Ya podemos hacer afirmaciones (en este caso verificaciones) sobre el resultado de la ejecuci´n: o [ Test ] public void AddStudentScore () { MockDataManager dataManager = new MockDataManager (). public IRelationalObject GetByKey ( string key ) { _getKeyCalled = true . ScoreManager smanager = new ScoreManager ( dataManager ). AddScore ( " 23145 " . smanager . if (! _getKeyCalled ) throw Exception ( " GetByKey method was not called " ). } ¦ ¥ § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 Vale. VerifyCalls (). AddScore ( " 23145 " . } public void Save ( IRelationalObject robject ) { _saveCalled = true . ya no acceder´ al sistema de almacenamiento porque la clase no ima plementa nada ¿pero c´mo validamos que el SUT intenta hacerlo? Al fin o y al cabo la misi´n de nuestro SUT no es meramente operar sino tambi´n o e coordinar el registro de datos. Las tres partes del test: AAA Cap´ ıtulo 5 4 5 6 7 8 MockDataManager dataManager = new MockDataManager (). private bool _saveCalled = false .5).1. Sin embargo. 8.

Cap´ ıtulo 5 5. los mocks generador por frameworks. En el siguiente cap´ o ıtulo veremos una soluci´n alternativa. Las tres partes del test: AAA de implementar y no contiene toda la informaci´n que necesitamos (¿c´mo o o sabemos que el dato que se salv´ era el correcto?). que o nos permitir´ definir afirmaciones certeras basadas en expectativas con todo a lujo de detalles.B Rainsberg[16].1. Como lectura adicional recomiendo el libro de J. 93 . Adem´s a el blog y los videos de las conferencias de este autor son una joya.

1.5. Las tres partes del test: AAA Cap´ ıtulo 5 94 .

igualmente importante. En o n los tests de validaci´n de estado. cu´ndo debemos evitarlos. ya o que se hace pasar por un colaborador del SUT cuando en realidad no es la entidad que dice ser. a Un mock es un tipo concreto de doble de test. adem´s.Cap´tulo ı 6 Mocks y otros dobles de prueba Antes de decidirnos a usar objetos mock (en adelante mocks) hay que contar hasta diez y pensarlo dos veces. a Martin Fowler public´ un art´ o ıculo que se ha hecho muy popular basado en 95 . o ıcil El test corre el riesgo de volverse fr´gil si conoce demasiado bien el a interior del SUT. tambi´n se usan mocks o stubs cuando o e hay que acceder a datos procedentes de un colaborador. sienta las bases de la nomenclatura. los mocks (y otros dobles) son imprescindibles para un desarrollo dirigido por tests completo pero. La gran ventaja de los mocks es que reducen a dr´sticamente el n´mero de l´ a u ıneas de c´digo de los tests de validaci´n de o o interacci´n y evitan que el SUT contenga hacks (apa˜os) para validar. En las listas de correo a menudo e e la gente pregunta c´mo deben usar mocks para un problema determinado y o buena parte de las respuestas concluyen que no necesitan mocks. romper´ el a n a test forz´ndonos a reescribirlo. Los mocks presentan dos inconvenientes fundamentales: El c´digo del test puede llegar a ser dif´ de leer. por peque˜o que sea. Lo primero. La expresi´n “doble” se o usa en el mismo sentido de los actores “dobles” en las pel´ ıculas de acci´n. o sea. Por lo tanto. sino partir su test en varios y/o reescribir una parte del SUT. es saber cu´ndo van a poner en a jaque al test. Fr´gil significa que un cambio en el SUT. es saber en todo momento qu´ es lo que vamos a probar y por qu´. Gerard Meszaros describe los distintos tipos de dobles de test en su libro[12] donde.

5.html 2 La traducci´n de stub ser´ sobra o colilla. o mientras que el stub simplemente simula respuestas a consultas.Net. aunque a d´ de hoy la traducci´n o ıa o necesita ser mejorada: http://www. Para poder discernir entre usar un mock o o un stub. a Los frameworks que generan mocks y stubs son muy ingeniosos. o Stub: proporciona respuestas predefinidas a llamadas hechas durante los tests. Un mock valida comportamiento en la colaboraci´n. tal como una pasarela e o de email que recuerda cu´ntos mensajes envi´. toma alg´n atajo o cortocircuito que le hace inapropiado para u producci´n (como una base de datos en memoria por ejemplo). Son capaces de crear una clase en tiempo de ejecuci´n. Normalmente. nos aporta menos informaci´n a o sobre la colaboraci´n entre objetos. a o Mock: objeto preprogramado con expectativas que conforman la especificaci´n de c´mo se espera que se reciban las llamadas.Mocks versi´n 3.com/traducciones/mocksArentStubs.2 para Java. Son m´s o o a complejos que los stubs aunque sus diferencias son sutiles. Los mocks tienen ventajas e inconvenientes sobre los stubs. El stub hace el test menos fr´gil pero. que hereda de una clase o X o que implementa una interfaz Y.6 para . Tanto X como Y se pueden pasar como par´metro para que el framework genere una instancia de un mock o un a stub que sea de ese tipo pero cuya implementaci´n simplemente se limita a o reaccionar tal como le indiquemos que debe hacerlo. Lo e mejor ser´ mostrarlas con ejemplos. De ah´ extraemos el siguiente listado de tipos de doble: ı Dummy: se pasa como argumento pero nunca se usa realmente. sin responder en absoluto a cualquier otra cosa fuera de aquello para lo que ha sido programado. un subconjunto de su funcionalidad. Fake: tiene una implementaci´n que realmente funciona pero. donde habla de los distintos dobles1 .carlosble. o 1 Mi buen amigo Eladio L´pez y yo lo tradujimos. Los stubs pueden tambi´n grabar informaci´n sobre las llamadas. al mismo tiempo. volvemos a recalcar que primero hay que saber qu´ estamos probando e y por qu´. los objetos dummy se usan s´lo para rellenar listas de par´meo a tros. EasyMock 2. o El stub2 es como un mock con menor potencia. por lo o general. “Los mocks no son stubs”. Mientras que en el mock podemos definir expectativas con todo lujo de detalles. Las veremos a continuaci´n.Cap´ ıtulo 6 esta nomenclatura. as´ que todo el mundo ha optado por o ıa ı dejarlo en stub 96 . el stub tan s´lo devuelve respuestas preprogramadas o a posibles llamadas. En este libro usaremos el framework Rhino. frecuentemente.

de nuestra aplicaci´n cliente. no vamos a utilizar el servicio real porque el test no ser´ unitario.Mocks.5.Mocks para los ejemplos. Todos son software libre y se pueden descargar gratuitamente de la red. Vamos a estudiar las dos posibilidades resaltano do las diferencias de cada una.1. Cu´ndo usar un objeto real. Lo que queremos dise˜ar es el trozo de e n c´digo. que obtiene los d´ festivos del servidor o o ıas remoto. El SUT es el calendario cliente y su colaborador el servicio remoto. Nuestro calendario cliente tiene tres propiedades que son CurrentYear CurrentMonth.1. para lo cual tenemos que consultar un servicio remoto que lee. } ¦ ¥ El m´todo es de lectura. El servicio ıas necesita conocer qu´ a˜o. Cu´ndo usar un objeto real. n Castle. no o ıa se ejecutar´ de manera veloz ni con independencia del entorno. qu´ mes y qu´ municipio nos ocupa.1 para Python. Este cap´ ıtulo posiblemente sea de los m´s dif´ a ıciles de entender de todo el libro. Lo primero es a˜adir al proyecto las referencias a las DLL3 Rhino. Estamos hablando de una aplicaci´n cliente/servidor.Core y Castle. string townCode ). La interfaz del servicio remoto es la ıas siguiente: § 1 2 3 4 5 6 public interface ICalendarService { int [] GetHolidays ( int year . Est´ claro que ıa a necesitamos un doble.8. de una base de datos. Para dise˜ar la colaboo n raci´n. 6. los d´ festivos. para devolver e n e e un vector con los d´ festivos del mes.2 para Java y Pymox 0. CurrentTown que sirven para configurarlo. 3 incluidas en el c´digo fuente que acompa˜a al libro o n 97 . un stub o un mock a Mockito 1. ¿Qu´ tipo de doble de prueba utilizaremos? Generale mente. Utilizaremos Rhino. de consulta. Para una correcta comprensi´n del mismo es recomendable leerlo con o el ordenador delante para ir ejecutando los ejemplos. int month .Cap´ ıtulo 6 6. Ahora nos piden que dibujemos los d´ festivos de determinaıas dos municipios en un color distinto.DynamicProxy2. un stub o un a mock Vamos a por el primer ejemplo para sacar a relucir los pros y los contras de las distintas alternativas que tenemos para dise˜ar c´digo que trata n o de colaboraciones entre objetos. para los m´todos de consulta se usan stubs pero el factor determinante e para decantarse por un mock o un stub es el nivel de especificidad que se requiere en la colaboraci´n. Supongamos que hemos escrito un control gr´fico que muestra un calendario en pantalla para permitirnos seleccionar a una fecha.

Con la llamada a GenerateStrictMock. NUnit . Si no se o cumpli´. donde se invoca al SUT. [ TestFixture ] public class CalendarTests { [ Test ] public void C l i e n t A s k s C a l e n d a r S e r v i c e () { int year = 2009. V e r i f y A l l E x p e c t a t i o n s (). Calendar calendar = new Calendar ( serviceMock ). } } ¦ ¥ El c´digo asusta un poco al principio pero. Assert 98 . Collections . en alg´n momento. queremos que se comporte tal cual. calendar . ser´ el siguiente: ıa § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 using using using using System . CurrentTown = townCode .6. CurrentYear = year . calendar . Y adem´s. int month = 2. o o Que no se cumpli´ significa que la llamada nunca se hizo o que se hizo con o 4 Las tres partes de un test: Arrange. 1 y 5. cuando ocurra. CurrentMonth = month . ICalendarService serviceMock = MockRepository . GetHolidays ( year . serviceMock . la u que le dice al mock que compruebe que la expectativa se cumpli´. Expect ( x = > x . string townCode = " b002 " .1. cuando esa invocaci´n se haga. o el framework genera una instancia de una clase que implementa la interfaz ICalendarService. month . DrawMonth (). El siguiente bloque ya es el de acto (Act). Act. El primer bloque consiste en la generaci´n del mock en blanco (AAA o y la definici´n de expectativas. La siguiente l´ ınea define la primera expectativa (Expect) y dice que. GenerateStrictMock < ICalendarService >(). La ultima l´ ´ ınea es la que verifica que todo fue seg´n lo esperado (Assert). un stub o un mock a Cap´ ıtulo 6 El test. Nos ahorramos crear una clase impostora a mano como hicimos en el cap´ ıtulo anterior. En ´l. 5 }). dice a e a a que. se u invocar´ al m´todo GetHolidays con sus tres par´metros. System . // the SUT serviceMock . Cu´ndo usar un objeto real. el mock devolver´ un array de dos o a elementos. Mocks . estamos dici´ndole al mock que le van a invocar de e esa manera y que. calendar . veremos que o no es tan complejo. Return ( new int [] { 1 . se distinguen tres bloques separados por l´ e ıneas 4 ). O sea. calendar . Generic . si lo estudiamos. usando un mock. townCode )). entonces el test no pasa porque el framework lanza una excepci´n. sobre el propio objeto mock. Framework . Rhino .

siendo adem´s de la ´ a forma que dicta el test. Si el colaborador fuese un stub. ıa 5 99 . con lo que podr´ optar por eliminarla en futuras versiones.. Digo a rendimiento porque quiz´s queremos cuidarnos del caso en que el calendaa rio hiciese varias llamadas al servicio por despiste del programador o por cualquier otro motivo. un stub o un mock a otros par´metros distintos a los que expl´ a ıcitamente se pusieron en la expectativa o bien que se hizo m´s de una vez. podemos validar el estado. Esta es una restricci´n o una validaci´n imporo o tante. Existe la posibilidad de llamar a AssertWasCalled pero el propio Ayende.. siempre da un ı resultado positivo. _currentMonth . no verifica nada. entonces un mock est´ bien como colaborador. GetHolidays ( _currentYear . a El stub. el framework hace fallar el test. El stub no e dispone de verificaci´n de expectativas5 sino que hay que usar el Assert o de NUnit para validar el estado. o Si por motivos de rendimiento. simular´ al servicio devolviendo unos valores: a § 1 2 [ Test ] public void D r a w H o l i d a y s W i t h S t u b () Es decir. no se aplicar´ como veremos a continuaci´n.. al igual que el mock.. al margen o de la descrita expl´ ıcitamente. queremos obligar a que el SUT se comunique una unica vez con el colaborador. ıa. nos aseguramos que el SUT es capaz de e funcionar puesto que. En el presente ejemplo. La ausencia de expectativa supone fallo. autor de Rhino.1. Cu´ndo usar un objeto real. El c´digo del SUT que hace pasar el test ser´ algo como: o ıa § 1 2 3 4 5 6 7 8 public void DrawMonth () { // . seg´n c´mo se mire.Cap´ ıtulo 6 6.. si se produce alguna interacci´n entre SUT y mock. ahorr´ndonos una buena suma de l´ a ıneas de c´digo y evit´ndonos o a c´digo espec´ o ıfico de validaci´n dentro del SUT.Mocks. _currentTown ). Al recurrir al stub.. se traducir´ en luz roja. Adem´s. por ejemplo.Mocks no est´ seguro de que sea correcto seg´n la definici´n a u o de stub. definiendo en el calendario cliente alguna propiedad Holidays de tipo array de enteros que almacenase la respuesta del servidor para poder afirmar sobre ´l. la verificaci´n (y u o o por tanto sus restricciones). el framework est´ haciendo una gran cantidad de trabajo por a nosotros. // . rest of b u s i n e s s logic here . int [] holidays = _calendarService . cualquier otra llamada al servicio ser´ noıa contemplada). si en el acto se hacen a a llamadas al mock que no estaban contempladas en las expectativas (puesto que solo hay una expectativa. Aunque en realidad dicha funci´n o s´ forma parte de la API para stubs en Rhino. some b u s i n e s s code here .. o Desde luego. } ¦ ¥ ¿C´mo lo har´ o ıamos con un stub? ¿qu´ implicaciones tiene?. Cuala quier cosa que se salga de lo que pone el test. obtendr´ respuesta. la llamada a VerifyAllExpectations.. cuando invoque a su colaborador.

6.. “si se produce esta llamada. un stub o un mock a Cap´ ıtulo 6 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 { int year = 2009. CurrentMonth = month . string townCode = " b002 " . Invoice (factura). hay que plantearse cu´l es el verdadero objetivo del test. int month = 2. Es decir. Se trata de un software de facturaci´n. Al fin y al cabo.. Cu´ndo usar un objeto real. Nos interesa inyectar el gestor de impuestos en el constructor 100 . month . townCode )). Holidays [0]). Cuando no estamos interesados en controlar ıa con total exactitud la forma y el n´mero de llamadas que se van a hacer al u colaborador. el test es menos fr´gil e a que su versi´n con un mock. Tenemos los objetos. AreEqual (1 . usar´ un stub. CurrentTown = townCode . tambi´n podemos utilizar un stub. calendar . nos queda sensaci´n de que no o o sabemos si la llamada al colaborador se est´ haciendo o no. 5 }). ICalendarService serviceStub = MockRepository . entonces devuelve X”. Line y TaxManager o (gestor de impuestos). calendar . Return ( new int [] { 1 . o Una factura se compone de una o m´s l´ a ıneas y cada l´ ınea contiene el art´ ıculo y la cantidad. } ¦ ¥ La diferencia es que este test mantendr´ la luz verde incluso aunque no ıa se llamase a GetHolidays. AreEqual (5 . DrawMonth (). Assert . siempre podemos cubrir el c´digo con un test de integraci´n posterior que nos asegure que todas las o o partes trabajan bien juntas. independientemente de que la llamada se produzca una o mil veces. calendar . o o usar´ un mock. Holidays [1]). serviceStub . si conviene un doble o no. Sin embargo. para todos aquellos e casos en los que le pedimos al framework. Tambi´n pasar´ e ıa aunque la llamada se hiciese cien veces y aunque se hicieran llamadas a otros m´todos del servicio. Stub ( x = > x . CurrentYear = year . Para salir de a dudas. Si me basta con que el calendario obtenga los d´ festivos y ıa ıas trabaje con ellos.1. vamos a por un ejemplo que nos har´ dudar sobre el tipo o a de doble a usar o. calendar . calendar . siempre que la propiedad Holidays de calendar tuviese los valores indicados en las afirmaciones del final. GetHolidays ( year . A continuaci´n. Si se trata a de describir la comunicaci´n entre calendario y servicio con total precisi´n. ya que el porcentaje de los mismos puede variar dependiendo de los art´ ıculos y dependiendo de la regi´n. El objeto factura necesita colaborar con el gestor de impuestos para calcular el importe a sumar al total. Al ser menos restrictivo. calendar . Assert . GenerateStub < ICalendarService >(). incluso. Digamos que son atajos para simular el entorno y que se den las condiciones oportunas. Calendar calendar = new Calendar ( serviceStub ).

¿C´mo vamos a probar esta colaboraci´n? ¿utilizaremos un objeto real? o o ¿un stub? ¿un mock tal vez?. Visto as´ resulta que si usaı. un stub o un mock a de la factura para que podamos tener distintos gestores correspondientes a distintos impuestos. nos limitamos a decir que el total debe ser mayor que la simple o multiplicaci´n del precio del producto por la cantidad de productos. int quantity = 10. no tenemos garant´ de que el c´lculo del impuesto fuese realizado por el gestor ıas a 6 En Canarias no se aplica el IVA sino un impuesto llamado IGIC 101 . float total = invoice . el objetivo del test no es probar TaxManager (el colaborador) sino probar Invoice (el SUT). Partimos de la base de que el gestor de impuestos ya ha sido implementado. Line line = new Line (). La elecci´n no es f´cil. Cu´ndo usar un objeto real. la alerta roja se o va a activar. } } ¦ ¥ Las tres partes del test est´n separadas por l´ a ıneas en blanco. ir haciendo TDD me garantiza que. As´ si estoy en Madrid inyectar´ el IvaManager y si ı. invoice . Usar el o colaborador real (TaxManager) tiene la ventaja de que el c´digo del test es o sencillo y de que. al no haber usado un mock. Price . con lo que detectar y corregir el error ser´ cuesti´n de seguna o dos. entonces el SUT queda perfectamente aislado y este test no se rompe aunque se introduzcan defectos en el colaborador. AddProducts ( product . prefiero usar el colaborador real en o a estos casos en que no altera el sistema y se ejecuta r´pido. Greater ( quantity * product . line . yo utilizar´ el objeto real: a ıa § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 [ TestFixture ] public class InvoiceTests { [ Test ] public void CalculateTaxes () { Stock stock = new Stock (). Sin embargo. AddLine ( line ). aunque el aislamiento es un aspecto secundario que suele resultar beneficioso. mos un doble para TaxManager. los posibles defectos que tenga. en el instante siguiente a la generaci´n del defecto. Product product = stock . quantity ). Assert . total ). En la afirmaci´n.Cap´ ıtulo 6 6. Los mocks no se inventaron para aislar dependencias sino para dise˜ar n colaboraciones. Invoice invoice = new Invoice ( new TaxManager ()). GetProductWithCode ( " x1abc3t3c " ). Se puede argumentar que. e estoy en Canarias6 el IgicManager. GetTotal ().1. probablemente sean detectados en este test. A pesar de que el a test se puede romper por causas ajenas al SUT. Puesto que no altera el sistema y produce una respuesta r´pida. Personalmente.

Generic .stubs para consultas y mocks para acciones”. System . Si el gestor de impuestos accediese a la base de datos. hemos ıa producido c´digo duplicado. Text . un stub o un mock a Cap´ ıtulo 6 de impuestos. Return ( dummyStudent ). Rhino . En palabras de Steve Freeman[14]: “utilizamos mocks cuando el servicio cambia el mundo exterior. funciona.6. Expect ( x = > x . [ Test ] public void AddStudentScore () { string studentId = " 23145 " . Las consideraciones difieren. son suficientes para afirmar que el c´digo va por o buen camino. junto con o la ausencia de duplicidad. si lo estamos aplicando. forzosamente. a Aplicada al ejemplo anterior. GetByKey ( studentId )). El tercer paso o del algoritmo TDD consiste en eliminar la duplicidad. Podr´ haberse calculado dentro del mismo objeto factura sin ıa hacer uso de su colaborador. replicado del gestor de impuestos. Expect ( x = > x . usar´ ıamos un stub si el TaxManager no estuviese implementado. GenerateStrictMock < IDataManager >(). dataManagerMock . Save ( dummyStudent )). float score = 8. ya que le hacemos una consulta.Mocks: § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 using using using using using System . NUnit . Si no estuviese implementado todav´ y me viese obligado a dise˜ar ıa n primero esta funcionalidad de la factura. IDataManager dataManagerMock = MockRepository . 102 . Pero eso supondr´ que. System . la t´cnica no es la misma si estamos haciendo TDD ı e que si estamos escribiendo pruebas de software a secas. Escribimos el mismo test con la ayuda de Rhino. La validaci´n de estado que hemos hecho.1. entonces seguro que hubiese utilizado un doble.5 f . ya que es com´n que un SUT tenga varios colaboradores. por lo que. Es una regla que nos orientar´ en muchos casos. Mocks . entonces seguro usar´ un stub para ıa simular que la funcionalidad del gestor de impuestos est´ hecha y produce el a resultado que quiero. no es obligatorio que un mock nos garantice que se hizo la llamada al colaborador. Collections . stubs cuando no lo hace . Hay veces en las que un mismo test requiere de mocks y stubs a la vez. dependiendo de la intenci´n del test. Se trata del ejemplo de los estudiantes del cap´ a ıtulo anterior. dataManagerMock . As´ pues. escribiese en un fichero en disco o enviase un email. Framework . siendo algunos stubs u y otros mocks. Student dummyStudent = new Student (). Cu´ndo usar un objeto real. o Antes de pasar a estudiar las peculiaridades de EasyMock veamos un ejemplo m´s complejo. “Usa un stub para m´todos de consulta y un mock e para suplantar acciones”.

score ). 103 . Podemos identificar entonces la responsabilidad de actualizar el marcador y separarla. Score . AddScore ( studentId . conoce el orden y ni si quiera estamos comprobando que a los puntos han subido al marcador del alumno. Assert . ya que conoce c´mo funciona el SUT o m´s de lo que deber´ No s´lo sabe que hace dos llamadas a su colaborador a ıa. tiene un problema importante y es su fragilidad. 5 f ). Score = student . return student . 5 f ).5 f . que ya podemos probarlo todo. El ScoreManager est´ encargado de coordinar la acci´n de actualizar el marcaa o dor y guardar los datos. un stub o un mock a 21 22 23 24 25 ScoreManager smanager = new ScoreManager ( dataManagerMoc k ). La delegamos en una clase que se encarga exclusivamente de ello.Cap´ ıtulo 6 6. el colaborador es un mock con dos expectativas. AreEqual ( student .1. Vamos a dise˜arla utilizando un test unitario: n § 1 2 3 4 5 6 7 8 [ Test ] public void ScoreUpdaterWorks () { ScoreUpdater updater = new ScoreUpdater (). Student dummyStudent = new Student (). Reflexionemos sobre las responsabilidades. float score ) { student . Score + score . o sino que. smanager . dataManagerMock . El orden de las expectativas tambi´n es decisivo en la fase de verificaci´n: si la que aparece e o segunda se diese antes que la primera. UpdateScore ( new Student () . float score = 8. V e r i f y A l l E x p e c t a t i o n s (). Cu´ndo usar un objeto real. reescribamos el test que nos preocupaba: § 1 2 3 4 5 6 7 [ Test ] public void AddStudentScore () { string studentId = " 23145 " . A pesar del esfuerzo que hemos hecho por escribir este test. Student student = updater . } ¦ ¥ El c´digo del SUT: o § 1 2 3 4 5 6 7 8 9 public class ScoreUpdater : IScoreUpdater { public Student UpdateScore ( Student student . el framework marcar´ el test como ıa fallido. ¿Ser´ que el SUT tiene m´s a a de una responsabilidad? Un c´digo que se hace muy dif´ de probar expreo ıcil sa que necesita ser reescrito. } } ¦ ¥ Ahora. adem´s. } ¦ ¥ En este caso.

V e r i f y A l l E x p e c t a t i o n s (). GenerateStrictMock < IDataManager >(). score ). Cu´ndo usar un objeto real. Save ( dummyStudent )). Expect ( y = > y . nos quitar´ ıamos una expectativa del test. Student dummyStudent = new Student (). Return ( dummyStudent ). dataManagerMock . GetByKey ( studentId )). AddScore ( studentId . IScoreUpdater scoreUpdaterMock = MockRepository . Expect ( x = > x . } ¦ ¥ Hubo que modificar el constructor de ScoreManager para que aceptase otro colaborador. Save ( studentUpdated ). ¿C´mo lo o o enmendamos? Lo primero que hay que preguntarse es si realmente estamos obligados a obtener el objeto Student a partir de su clave primaria o si tiene m´s sentido recibirlo de alguna otra funci´n. UpdateScore ( dummyStudent . Expect ( x = > x . Si toda la aplicaci´n tiene un a o o buen dise˜o orientado a objetos. Return ( dummyS tudent ). scoreUpdaterMock ). UpdateScore (( Student ) student . Ahora estamos seguros que se est´ probando todo. score )). Vamos a suponer que podemos modificar el SUT para aceptar este cambio: § 1 2 3 4 5 6 7 8 9 10 11 12 [ Test ] public void AddStudentScore () { float score = 8. UpdateScore ( dummyStudent . Expect ( y = > y . dataManagerMock . quiz´s el m´todo AddScore puede recibir ya n a e un objeto Student en lugar de su clave primaria. Imita demasiado lo que hace el SUT. scoreUpdaterMock . GetByKey ( studentId ). Return ( dummyStudent ). score ). Student studentUpdated = _updater . V e r i f y A l l E x p e c t a t i o n s (). 104 . float score ) { IRelationalObject student = _dataManager . smanager . ScoreManager smanager = new ScoreManager ( dataManagerMock .6.1. } ¦ ¥ Desde luego este test parece m´s un SUT en s´ mismo que un ejemplo de a ı c´mo debe funcionar el SUT. GenerateStrictMock < IScoreUpdater >(). scoreUpdaterMock . GenerateStrictMock < IScoreUpdater >().5 f . ¡Pero el test a es id´ntico a la implementaci´n del SUT! e o § 1 2 3 4 5 6 7 8 public void AddScore ( string studentId . GenerateStrictMock < IDataManager >(). _dataManager . scoreUpdaterMock . IScoreUpdater scoreUpdaterMock = MockRepository . En ese caso. dataManagerMock . score )). IDataManager dataManagerMock = MockRepository . un stub o un mock a Cap´ ıtulo 6 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 IDataManager dataManagerMock = MockRepository .

a continuaci´n. La idea es probar un solo aspecto de la colaboraci´n en cada test mediante mocks. V e r i f y A l l E x p e c t a t i o n s (). o e ignorar lo dem´s con stubs. De forma abreviada el SUT ser´ ıa: public class Orchestrator { private IServices _services .Cap´ ıtulo 6 6. UpdateScore ( student . public Orchestrator ( IServices services ) { _services = services . Save ( dummyStudent )). _services . } ¦ ¥ § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Ahora el test nos ha quedado con dos expectativas pero pertenecientes a distintos colaboradores. float score ) { Student studentUpdated = _updater .1. El SUT se encarga de orquestar (cooro dinar) la forma en que se realizan llamadas a distintos servicios. _dataManager . El orden de las llamadas no importa cuando son sobre colaboradores distintos. no se romper´ si el SUT los ıa invocase en orden inverso. ScoreManager smanager = new ScoreManager ( dataManagerMock . MethodA (). Si escribimos un test con tales expectativas. smanager . Vamos a partirlo en dos para probar cada colaboraci´n por separado: o 105 . s´lo nos queda partir el test en varios para eliminar las restricciones impuestas o por el framework con respecto al orden en las llamadas a los mocks. AddScore ( dummyStudent . V e r i f y A l l E x p e c t a t i o n s (). } ¦ § 1 2 3 4 5 6 ¥ public void AddScore ( Student student . } } ¦ ¥ La orquestaci´n consiste en invocar al servicio A y. Cu´ndo usar un objeto real. scoreUpdaterMock . dataManagerMock . a En caso de que no podamos cambiar la API para recibir el objeto Student. Pensemos en a la orquestaci´n de unos servicios web. score ). score ). Expect ( x = > x . } public void Orchestrate () { _services . al servicio o o B. scoreUpdaterMock ). Save ( studentUpdated ). que aunque en el test hayamos definido la expectativa UpdateScore antes que Save. queda un test id´ntico al e SUT como nos estaba pasando. es decir. un stub o un mock a 13 14 15 16 17 18 19 20 21 22 dataManagerMock . MethodB (). Veamos un ejemplo simplificado. Entonces el test no queda tan fr´gil.

GenerateStrictMock < IServices >(). } } ¦ ¥ El primer test tan s´lo se encarga de probar que el servicio A se llama. Orchestrate ().Mocks permite crear h´ a ıbridos7 entre mocks y stubs mediante Leyendo a Fowler y Meszaros entiendo que en realidad no son h´ ıbridos sino stubs. As´ el test no es tan fr´gil y la posibilidad de romper los tests por ı a cambios en el SUT disminuye. orchestrator . V e r i f y A l l E x p e c t a t i o n s (). Stub ( b = > b . Orchestrator orchestrator = new Orchestrator ( servicesMock ). MethodA ()). si resulta que el orden de las llamadas es algo tan cr´ ıtico que se decide escribir todas las expectativas en un solo test. Rhino. Sin embargo la mayor´ de ıa los frameworks consideran que un stub s´ ımplemente devuelve valores. servicesMock . Expect ( b = > b . V e r i f y A l l E x p e c t a t i o n s (). Stub ( a = > a . o mientras que se le dice al framework que da igual lo que se haga con el servicio B. Orchestrate (). No obstante. MethodB ()). ya que se les atribuye la posibilidad de recordar expectativas. orchestrator . Cu´ndo usar un objeto real. no un test. Es programar una alerta. servicesMock . se puede hacer siempre que tengamos conciencia de lo que ello significa: lanzar una alerta cada vez que alguien modifique algo del SUT. De esta forma. servicesMock .1. servicesMock . GenerateStrictMock < IServices >().6. El segundo test trabaja a la inversa. servicesMock . entonces est´ bien. Expect ( a = > a . Si de verdad es lo que necesitamos. MethodA ()). un stub o un mock a § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 Cap´ ıtulo 6 [ TestFixture ] public class ServicesTests { [ Test ] public void OrchestratorCallsA () { IServices servicesMock = MockRepository . estamos dise˜ando qu´ elementos toman parte en la orquestaci´n y no tanto el orden n e o mismo. MethodB ()). Orchestrator orchestrator = new Orchestrator ( servicesMock ). } [ Test ] public void OrchestratorCallsB () { IServices servicesMock = MockRepository . que no recuerda 7 106 . servicesMock .

Nos ahorra declarar la llamada stub en el test pero. por contra. V e r i f y A l l E x p e c t a t i o n s (). V e r i f y A l l E x p e c t a t i o n s (). Quiz´s haya observado que en todo el cap´ a ıtulo no hemos creado ning´n u mock basado en una implementaci´n concreta de una base de datos ni de nino guna librer´ del sistema (. servicesMock .1. Orchestrator orchestrator = new Orchestrator ( servicesMock ). } } ¦ ¥ La diferencia es que el framework s´lo falla si la llamada no se produce pero o admite que se hagan otras llamadas sobre el SUT. MethodB ()). orchestrator . un stub o un mock a la llamada GenerateMock en lugar de GenerateStrictMock. queda como ejercicio propuesto escribir tests para el ejemplo de los estudiantes que no hemos terminado de cerrar. GenerateMock < IServices >(). No es recomendable crear mocks ıa basados en dependencias externas sino s´lo en nuestras propias interfaces. Orchestrate (). Expect ( b = > b . orchestrator . Aunque es posible hacer ı nada. servicesMock . 107 . Expect ( a = > a . incluso que se repita la llamada que definimos en la expectativa. servicesMock .Net en este caso). Cu´ndo usar un objeto real. servicesMock . Orchestrate (). Orchestrator orchestrator = new Orchestrator ( servicesMock ). } [ Test ] public void OrchestratorCallsB () { IServices servicesMock = MockRepository . o De ah´ el uso de interfaces como IDataManager. se calla la posible repetici´n de la expectativa. MethodA ()). lo o cual seguramente no nos conviene. Visto el ejemplo de los servicios. anteriores se podr´ reescribir con un resultado similar y menos l´ ıan ıneas de c´digo: o § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 [ TestFixture ] public class S e r v i c e s H y b r i d M o c k s T e s t s { [ Test ] public void OrchestratorCallsA () { IServices servicesMock = MockRepository . As´ los tests ı. GenerateMock < IServices >().Cap´ ıtulo 6 6.

class ). Veamos la versi´n Java del ejemplo del o calendario: § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 8 6. junit . podemos hacer mock e de la misma directamente. a es una l´ ınea de c´digo pero a la gente le choca esto de record y replay. Si heo mos comprendido todos los tests de este cap´ ıtulo. junit . e a Necesitan que les indiquemos expl´ ıcitamente cu´ndo hemos terminado de dea finir expectativas para comenzar el acto. siempre usamos interfaces. la met´fora no ser´ ning´n a a u problema. Ver Cap´ ıtulo 7 en la p´gina 111 a 108 . Una vez escritos los tests unitarios. static org . org . es f´cil hacerlo.2. 5 }). puesto que la inyecci´n de deo pendencias8 es la mejor forma en que podemos gestionar tales dependencias entre SUT y colaboradores. lo hacen bien. o 6. EasyMock . En el caso de Python. String townCode = " b002 " . townCode ) ). java . getHolidays ( year . a˜adiremos n tests de integraci´n que se encargan de probar que aquellas de nuestras clao ses que hablan con el sistema externo. dado que en este lenguaje no necesitamos definir interfaces al ser d´bilmente tipado.*.Mocks) usan la met´fora conocida como Record/Replay. util . Es un claro ejemplo en el que Python ahorra a e unas cuantas l´ ıneas de c´digo. Escribir´ ıamos tests de integraci´n para la clase DataManager. andReturn ( new int [] { 1 .1: EasyMock static org . tambi´n Rhino. expect ( serviceMock .2. month . Afortunadamente. EasyMock es para Java.*.6. Por eso. ICalendarService serviceMock = createMock ( ICalendarService . La met´fora Record/Replay a Algunos frameworks de mocks como EasyMock (y en versiones anteriores. int month = 2. Calendar . si la API de o la clase externa nos resulta suficientemente gen´rica. en Python. La met´fora Record/Replay a Cap´ ıtulo 6 mocks de clases. s´lo crear´ una clase e o ıa tipo DataManager si viese que las distintas entidades externas con las que hablar´ son muy heterog´neas. replay ( serviceMock ). easymock . import import import import public class CalendarTests { @Test public void drawHolidays () { int year = 2009. Test . Assert .

Persistor < EmailAddress > persistor = new Persistor < EmailAddress >(). es Mockito9 ya que ıa es m´s sencillo que EasyMock. La documentaci´n o de EasyMock es concisa. drawMonth (). persistor . calendar . save ().Cap´ ıtulo 6 6. que es de tipo EmailAddress. para definir ı un mock con una expectativa. que es una limitaci´n en lugar de una ventaja. a de hecho. en Rhino. u e 9 http://mockito.2: Mockito @Test public void persistorSaves () throws Exception { EmailAddress emailMock = mock ( EmailAddress . class ). aunque luego sea un stub. verify ( serviceMock ). Para mockito no hay que utilizar distintos m´todos a la hora de crear un stub y un mock. calendar . set_currentTown ( townCode ). verify ( emailMock ). } } ¦ ¥ Pr´cticamente todo igual. o Mi framework de mocks favorito en Java. contiene ejemplos que reforzar´n los conceptos expresados en estas l´ a ıneas. es decir. As´ por ejemplo. calendar .Mocks.Mocks ya se hace impl´ ıcita. La met´fora Record/Replay a 22 23 24 25 26 27 28 29 30 31 Calendar calendar = new Calendar ( serviceMock ). Si se nos olvida activar el replay.org 109 . La met´fora que acabamos de ver no aporta ninguna ventaja al desarrollo y. En principio todos los dobles e se crean con el m´todo mock. el resultado del test es bastante raro puesto que. La diferencia queda e impl´ ıtica en la forma en que utilicemos el doble. Lo unico es que hemos tenido que decir expl´ a ´ ıcitamente replay(serviceMock) para cambiar de estado al mock. siendo recomendable dedicar unos minutos a revisarla para descubrir toda su funcionalidad. Save ( emailMock ). set_currentMonth ( month ). calendar . El motivo por el que algunos frameworks la mantienen es para hacer m´s sencilla la implementaci´n de los a o mocks internamente. set_currentYear ( year ). Produce un c´digo m´s claro y la met´fora de a o a a record/replay ya se ha superado. } ¦ ¥ El test dice que nuestro SUT (Persistor) tiene que invocar forzosamente en alg´n momento al m´todo save del colaborador. Al igual que la de Rhino.2. a d´ de hoy. lo que deber´ ser el acto se sigue considerando la preparaci´n y es desconcerıa o tante. Suele pasar al principio con este tipo de frameworks. podemos hacer lo siguiente: § 1 2 3 4 5 6 7 8 9 10 11 6.

La nomenclatura de facto.Mocks. por defecto. porque tanto con Python Mocker11 como con Pya Mox12 . Es similar al GenerateMock de Rhino. una especie de mock h´ ıbrido o relajado10 que no tiene en cuenta el orden de las expectativas u otras posibles llamadas. nos queda ver c´mo se trabaja en Python pero lo haremos en ´ o la parte pr´ctica del libro. La met´fora Record/Replay a Cap´ ıtulo 6 Los dobles de prueba en mockito son. Mocker funciona francamente bien pero su desarrollo lleva tiempo parado.com/p/pymox/ 10 110 . se trabaja practicamente igual a como acabamos de estudiar.6.2. Algunas de las preguntas que quedan abiertas en el presente cap´ ıtulo.Mocks.google. a Como dijimos antes. salvo que se especifique con c´digo. hay quien les llama stubs pero resulta confuso.org/mocker 12 http://code. ıa Por ultimo. mientras que el de Pymox sigue activo y su sint´xis se parece a´n m´s a EasyMock o a u a Rhino. lo cual se agradece la o mayor´ de las veces. dice que los stubs se limitan a devolver valores cuando se les pregunta 11 http://labix. se resolver´n en los siguientes.

Dise˜ o Orientado a Objetos (OOD) n Todos los lenguajes y plataformas actuales se basan en el paradigma de la programaci´n orientada a objetos (OOP por sus siglas en ingl´s). 7.O. en otros es la forma de hacer el c´digo m´s fr´gil.I. El u s´ no est´ mal en s´ mismo. Lo que en unos casos es una buena manera de resolver un problema. de la que a su vez hereda la clase Cuadr´pedo. lo que sucede es que las clases en el software ımil a ı 111 . por poner un ejemplo. Aunque a o e diario trabajamos con objetos. de la que hereda la clase Mam´ ıfero.Cap´tulo ı 7 Dise˜o Orientado a Objetos n TDD tiene una estrecha relaci´n con el buen dise˜o orientado a objetos y o n por tanto. e La potencia de la orientaci´n a objetos lleva impl´ o ıcita mucha complejidad y una larga curva de aprendizaje. Desafortunadamente no hay reglas universales que sirvan para toda la gama de problemas que nos podamos encontrar pero hay ciertos principios y patrones que nos pueden dar pistas sobre cual es el dise˜o m´s n a conveniente en un momento dado. no siempre conviene crear una jerarqu´ de clases..1.L. con los principios S. o a a Es decir. no todo el mundo comprende realmente lo que es el polimorfismo o para qu´ sirve una clase abstracta. como no. entra en juego ´ nuestra pericia dise˜ando clases y m´todos. de la que a su vez heredan las clases Perro y Gato. el de refactorizar. Durante los cap´ n e ıtulos pr´cticos a haremos uso de todos estos principios y los nombraremos donde corresponda.. o En el ultimo paso del algoritmo TDD. dependiendo ıa del caso puede ser m´s conveniente crear una asociaci´n entre objetos que a o colaboran.D que veremos a continuaci´n. Con fines docentes se suele explicar la OOP mediante ejemplos relacionados con el mundo que conocemos: v´ase el t´ e ıpico ejemplo de la clase Animal.

uno por cada letra. e no tienen por qu´ ser clasificaciones adecuadas en el software. lo que le da in giro completo a la forma en que creamos nuestras clases. muy dif´ de reutilizar. o ıcil Veamos el ejemplo de la clase Rectangulo.2. Para este caso particular.O.L. Principios S.L.I.. es usarla. Ahora damos la clase Cuadrado a alguien que tiene que trabajar con ella y se encuentra con los atributos heredados.7. Martin.I. Qui´n decidi´ resaltar estos principios y darles nombre o e o a algunos de ellos fue Robert C. a Una de las mejores formas que hay.. Este dise˜o n no tienen sentido. all´ por el a˜o 1995.O. ıa a por tanto. entonces definimos Cuadrado extendiendo de Rectangulo. podemos crear ese m´todo en e a e una tercera clase que colabora con Rectangulo y Cuadrado para calcular el ´rea. el beneficio de dise˜ar adecuadamente n cambia radicalmente la calidad del software. As´ Rect´ngulo sabe que cuando necesite calcular el ´rea invocar´ al a ı a a a m´todo de esta clase colaboradora pasando Ancho y Alto como par´metros e a y Cuadrado pasar´ dos veces la longitud de uno de sus lados. si copiamos esta clasificaci´n dir´ o ıamos que Cuadrado hereda de Rectangulo. El Rectangulo tiene dos atributos. pues como vamos a o mostrar. Ancho y Alto. Las n e o dependencias entre unas clases y otras son las que hacen al c´digo m´s fr´gil o a a o m´s robusto y reutilizable. Principios S. Lo m´s probable a es que se pregunte.2. a n 112 . de ver si la API que estamos dise˜ando n es intuitiva o no. porque el software difiere mucho de ´ste. si lo que queremos es ahorrarnos reescribir el m´todo que calcula el ´rea. que hablan del dise˜o orientado a objetos en t´rminos de la gesti´n de dependencias. ¿Qu´ significan el ancho y el alto en un cuadrado? Un e cuadrado tiene todos sus lados iguales. Las clasificaciones naturales de los objetos. TDD propone usarla antes de implementarla.D Son cinco principios fundamentales. El problema con el modelado tradicional es que a no se ocupa en profundidad de la gesti´n de dependencias entre clases sino de o la conceptualizaci´n. la realidad es demasiado compleja de modelar mediante OOP y el resultado puede ser un c´digo muy acoplado. La concepe tualizaci´n y el modelado son una espada de doble filo. Puesto que todo lo hacemos con objetos. En geometr´ el cuadrado es un rect´ngulo. Vale. Ahora e a necesitamos una clase Cuadrado.D Cap´ ıtulo 7 no siempre casan directamente con los objetos del mundo real. no tiene ancho y alto. Ancho y Alto y un m´todo que es capaz de calcular el ´rea. 7.

Desde que disponemos de herramientas que nos permiten el desarrollo dirigido por tests. Collaboration) como ayuda para detectar responsabilidades y colaboraciones entre clases.pdf http://c2. cada uno tambi´n e e con nombres que sirven perfectamente de documentaci´n. El e e ıa efecto que produce este principio son clases con nombres muy descriptivos y por tanto largos.O. Principios S. All´ por el a˜o 1989. podemos afirmar que dicha clase tiene m´s de una responsabilidad o m´s de un motivo para cambiar.I. ante o un cambio en el proceso de negocio. el principio dice que el comportamiento de una entidad debe poder ser alterado sin tener que modificar su propio c´digo fuente. Este principio es quiz´s el m´s importante de todos.D 7. no necesariamente una clase. En la pr´ctica la mayor´ de mis clases tienen uno o dos m´todos o a ıa e nada m´s. Para evitarlo.2. Cada tarjeta es para una entidad. Open-Closed Principle (OCP) Una entidad software (una clase.2.L.D es el de una unica res´ ponsabilidad y dice que cada clase debe ocuparse de un solo menester. Se aplica tanto a la clase como a cada uno de sus m´todos. 7. Kent Beck y Ward Cunningham usaban a n tarjetas CRC2 (Class. Martin o a tambi´n habla en profundidad sobre SRP en su libro[10]. TDD es una excelente manera de hacer RDD o de seguir el SRP. Hay una antigua e t´cnica llamada Responsibility Driven Design (RDD). o 1 2 http://www.com/resources/articles/srp. m´dulo o funci´n) debe estar abierta o o a extensiones pero cerrada a modificaciones.O.objectmentor. Si estamos delante de una clase que se podr´ ver obliıa gada a cambiar ante una modificaci´n en la base de datos y a la vez.Cap´ ıtulo 7 7. R. que tienen menos de cinco m´todos. el m´s sencillo a a a a y a la vez el m´s complicado de llevar a cabo. Single Responsibility Principle (SRP) El principio que da origen a la S de S. Existen ejemplos de c´digo y a o una explicaci´n m´s detallada del mismo en la propia web del autor1 .2.L.2. Puesto que el software requiere cambios y que unas entidades dependen de otras. es decir. Visto de otro modo.com/doc/oopsla89/paper. las tarjetas CRC han pasado a un segundo plano pero puede ser buena idea usarlas parcialmente para casos donde no terminamos de ver claras las responsabilidades. las modificaciones en el c´digo de una de ellas puede generar indeseables efectos colaterales en o cascada. aunque es anterior a la aparici´n de SRP como o tal. Responsibility. que viene a decir lo e mismo que este principio.1. Martin dice que cada clase deber´ tener un unico motivo ıa ´ para ser modificada. por poner un a a ejemplo. de varias o palabras: CalcularAreaRectangulo y que no contienen m´s de 15 l´ a ıneas de c´digo. con e lo que cada m´todo tambi´n deber´ tener un solo motivo para cambiar.html 113 .I. como se quiera ver.

Para ejemplos de c´digo l´ase el art´ o e ıculo original de R.3. La otra podr´ ser inyectando ıa ıa dependencias que cumplen el mismo contrato (que tienen la misma interfaz) pero que implementan diferente funcionamiento.2. Interface Segregation Principle (ISP) Cuando empleamos el SRP tambi´n empleamos el ISP como efecto colae teral. modificarla. Hay varias t´cnicas dependiendo del dise˜o. El dise˜o por contrato (Design by Contract) es otra forma n de llamar al LSP.pdf http://www. sino Y. Cerrar en exceso obliga a escribir demasiadas l´ ıneas de c´digo a la hora de o reutilizar la entidad en cuesti´n. Principios S.4.D Cap´ ıtulo 7 ¿C´mo se hace esto?. En espa˜ol podemos denominarlo el n n principio Abierto-Cerrado. donde o e dicha clase padre podr´ incluso ser abstracta. los compiladores e int´rpretes admiten e este paso de par´metros. El ISP defiende que no obliguemos a los clientes a depender de clases o interfaces que no necesitan usar. Por el propio polimorfismo. Martin3 7.2.I. L´ase el art´ e ıculo de R.L. El nombre de Open-Closed se lo debemos o a Bertrand Meyer y data del a˜o 1988. no s´lo por la dificultad de crear entidades de comportamiento extensible o sino por el peligro que conlleva cerrar determinadas entidades o parte de ellas. Martin sobre este principio4 . Como la totalidad del c´digo no se o o puede ni se debe cerrar a cambios.com/resources/articles/ocp. Tal imposici´n ocurre cuando una clao se o interfaz tiene m´s m´todos de los que un cliente (otra clase o entidad) a e 3 4 http://www. lo que viene diciendo es que si una funci´n recibe un objeto como par´metro. aunque quien recibe como par´metro no es exaca tamente X. una podr´ o e n ıa ser mediante herencia y redefinici´n de los m´todos de la clase padre.pdf 114 .objectmentor. El principio de sustituci´n de Liskov est´ estrechamente o a relacionado con el anterior en cuanto a la extensibilidad de las clases cuando ´sta se realiza mediante herencia o subtipos. el dise˜ador debe decidir contra cu´les n a protegerse mediante este principio. Liskov Substitution Principle (LSP) Introducido por Barbara Liskov en 1987. de tipo X y en su lugar le o a pasamos otro de tipo Y. Si una funci´n no cumple el e o LSP entonces rompe el OCP puesto que para ser capaz de funcionar con subtipos (clases hijas) necesita saber demasiado de la clase padre y por tanto. 7. Su aplicaci´n requiere bastante experieno cia. dicha funci´n debe proceder correco tamente.com/resources/articles/lsp.7.O.objectmentor.2. que hereda de X. En pr´ximos p´rrafos estuo a diaremos la inyecci´n de dependencias. la cuesti´n es si la funci´n de verdad est´ dise˜ada a o o a n para hacer lo que debe.

ıa En los lenguajes como Java y C# hablamos de interfaces pero en lenguajes interpretados como Python. ni siquiera jerarqu´ ıas pero el concepto se aplica igualmente. Dependency Inversi´n Principle (DIP) o La inversi´n de dependencias da origen a la conocida inyecci´n de depeno o dencias. o ´ o que vamos a ver dentro del siguiente apartado.Cap´ ıtulo 7 7. L´ase el a e 5 art´ ıculo de R. Cuando los requerimientos exigiesen un o o cambio de base de datos por ficheros en disco o viceversa. En el caso de un lenguaje interpretado no necesitamos definir interfaces.objectmentor. No s´lo es por motivos de robustez del software. Ve´moslo con un ejemplo sencillo: La a clase Logica necesita de un colaborador para guardar el dato Dato en alg´n u lugar persistente. Inversi´n del Control. Cuando un cliente depende de una interfaz con funcionalidad que no utiliza. sino de una o abstracci´n de B. No se preocupe si el ejemplo no le queo 5 http://www.L. ¿C´mo resolvemos esta ultima parte?. DIP explica que un m´dulo concreto o A.com/resources/articles/isp. se convierte en dependiente de otro cliente y la posibilidad de cat´strofe frente a cambios en la interfaz o clase base se multiplica. sino tambi´n por motivos o e de despliegue. sobrio y preparado para cambiar o sin producir efectos “bola de nieve”. no debe depender directamente de otro m´dulo concreto B. que no requieren interfaces. No quedar´ otro a remedio que modificar el c´digo de Logica. ¿C´mo haremos cuando necesitamos o cambiar la base de datos por ficheros binarios en disco?. podriamos limitarnos a usar IPersistor (que es una abstracci´n) en el c´digo de Logica. Tal abstracci´n es una interfaz o una clase (que podr´ ser o o ıa abstracta) que sirve de base para un conjunto de clases hijas.2. Con la inyecci´n de dependencias. Si en el c´digo de Logica escribimos literalmente el nombre de la clase MyBD o como colaborador para persistir datos. hablamos de clases. Principios S.2.I. Seguramente sirve a varios objetos cliente con responı sabilidades diferentes. s´lo tendr´ o ıamos que preocuparnos de que el atributo myPersistor de la clase Logica.pdf 115 . o Si las clases MyDB y FS implementasen una misma interfaz IPersistor para salvar Dato. que es de tipo IPersistor contuviese una instancia de MyDB o bien de FS.D necesita para s´ mismo. con lo que deber´ estar dividida en varias entidades. Disponemos de una clase MyBD que es capaz de almacenar Dato en una base de datos MySQL y de una clase FS que es capaz de almacenar Dato en un fichero binario sobre un sistema de ficheros NTFS.O.5. En los o pr´ximos cap´ o ıtulos haremos mucho uso de la inyecci´n de dependencias con o gran cantidad de listados de c´digo. produciendo un c´digo reutilizable. Martin 7. una de las mejores t´cnicas para lidiar con las colaboraciones entre e clases.

La soluci´n es invertir la forma en que se generan las o instancias. Si la aplicaci´n es o o peque˜a no necesitamos ning´n contenedor de terceros sino que en nuesn u 6 http://www.D. podeo mos especificar al contenedor que inyecte MyDB o FS mediante un fichero de configuraci´n que lee la aplicaci´n al arrancar y conseguir diferente comporo o tamiento sin tocar una sola l´ ınea de c´digo. Needle y Copland para Ruby y Castle. Inversi´n del Control (IoC) o Cap´ ıtulo 7 da demasiado claro.pdf 116 .com/resources/articles/dip.Net. Pico y Nano para Java. Los contenedores necesitan de un fichero de configuraci´n o bien de un fragmento de c´digo donde se o o les indica qu´ entidades tienen dependencias. Algunos de los m´s populares son a Pinsor para Python. funcione con instancias concretas. Habiendo preparado las clases de nuestro ejemplo para la inversi´n del control. Demoledor. cu´les son y qu´ entidades son e a e independientes. Tradicionalmente la clase Logica tendr´ una ıa sentencia de tipo “ myPersistor = new MyDB()” dentro de su constructor o de alg´n otro m´todo interno para crear una instancia concreta. El o o o nombre fue popularizado por el c´lebre Martin Fowler pero el concepto es e de finales de los a˜os ochenta. Si lo hacemos as´ volvemos al problema de tener que modificar la clase Logica para salvar ı en ficheros binarios. La entidad externa puede ser otra clase o bien puede ser un contenedor de inyecci´n de dependencias.I. Dado el principio de la inversi´n de depenn o dencias.O. Spring Container para Java y . Inversi´n del Control (IoC) o Inversi´n del Control es sin´nimo de Inyecci´n de Dependencias (DI). como par´metro en el constructor de Logica o bien mediante un a setter o m´todo que unicamente sirve para recibir el par´metro y asignarlo e ´ a al atributo interno.Windsor para . nos queda la duda de c´mo hacer para que la clase que requiere o colaboradores de tipo abstracto. Afortunadamente hay una gran variedad de contenedores libres para todos los lenguajes modernos. Habr´ una entidad externa que toma la decisi´n de salvar en base a o de datos o en ficheros y en funci´n de eso crea la instancia adecuada y se o la pasa a Logica para que la asigne a su miembro myPersistor.3. Martin sobre DIP6 es uno de los m´s a amenos y divertidos sobre los principios S. Dicho de otro modo. ¿Qui´n crea las instancias de los colaboradores? Retomemos el e ejemplo de las clases de antes. ya que no u e podemos crear instancias de interfaces ni de clases abstractas. Hay dos formas.Net. En tiempo de compilaci´n nos vale con tener el contrato pero en tiempo de ejecuci´n tiene o o que haber alguien que se ponga en el pellejo del contrato.objectmentor. o ¿Qu´ son los IoC Containers? Son la herramienta externa que gestiona e las dependencias y las inyecta donde hacen falta.L.3. El art´ ıculo de R. 7.7.

En nuestro peque˜o ejemplo hemos seguin do la mayor parte de los principios S.I. Inversi´n del Control (IoC) o tro propio c´digo podemos inyectar las dependencias como queramos.L. resulta m´s sencillo de lo que parece a y sino.D.Cap´ ıtulo 7 7. aunque no hemos entrado a ver qu´ hacen las clases en detalle pero por lo menos queda una idea ilustrativa e del asunto que nos ocupa. Su curva de aprendizaje puede ser complicada. No se asuste. a 117 . ya ver´.3.O. Los o contenedores son una herramienta pero no son imprescindibles. TDD no lo va a ir cascando todo.

7.3. Inversi´n del Control (IoC) o

Cap´ ıtulo 7

118

Parte II

Ejercicios Pr´cticos a

119

Cap´tulo ı

8

Inicio del proyecto - Test Unitarios
Vamos a escribir nuestra primera aplicaci´n de ejemplo porque practicano do es como realmente se aprende. En lugar de implementarla por completo y pulirla para luego escribir este cap´ ıtulo, vamos a dise˜arla juntos desde n el principio para ver en realidad c´mo se razona y se itera en el desarrollo o dirigido por tests. La aplicaci´n ocupar´ este cap´ o a ıtulo y los siguientes para que tengamos la oportunidad de afrontar toda la problem´tica de cualquier a aplicaci´n real. Sin embargo, no es una aplicaci´n empresarial como pudiera o o ser un software de facturaci´n, sino que se basa en un dominio de negocio o que todos conocemos. Se podr´ argumentar que el software que vamos a ıa desarrollar no es muy com´n o muy real pero incluye todas las piezas que u lleva una aplicaci´n “m´s real”. Imag´ o a ınese que para entender el c´digo fuente o que nos ocupa tuviese uno que estudiar primero contabilidad o derecho. Tenga confianza en que nuestra aplicaci´n de ejemplo es perfectamente v´lida o a y representa a peque˜a escala el modo en que se desarrolla una aplicaci´n n o mucho m´s grande. Nos adentramos ya en las vicisitudes de este peque˜o a n gran desarrollo de software. Sent´monos con el cliente para escucharle hablar sobre su problema y e hacer un primer an´lisis. Lo que nos cuenta es lo siguiente: a

121

Cap´ ıtulo 8
' $

Una vez escuchado el discurso del cliente y habiendo decidido que lo primero a implementar en los primeros sprints1 ser´ el motor de juego de la a aritm´tica b´sica, nos disponemos a formular los criterios de aceptaci´n para e a o que el cliente los valide. Tal como dijimos en el cap´ ıtulo sobre ATDD, los
1

&

Quiero lanzar al mercado un software educativo para ense˜ar man tem´ticas a ni˜os. Necesito que puedan jugar o practicar a trav´s a n e de una p´gina web pero tambi´n a trav´s del tel´fono m´vil y a e e e o quiz´s m´s adelante tambi´n en la consola Xbox. El juego sera a e vir´ para que los ni˜os practiquen diferentes temas dentro de las a n matem´ticas y el sistema debe recordar a cada ni˜o, que tendr´ un a n a nombre de usuario y una clave de acceso. El sistema registrar´ toa dos los ejercicios que han sido completados y la puntuaci´n obo tenida para permitirles subir de nivel si progresan. Existir´ un a usuario tutor que se registra a la vez que el ni˜o y que tiene la n posibilidad de acceder al sistema y ver estad´ ısticas de juego del ni˜o. El tema m´s importante ahora mismo es la aritm´tica b´sin a e a ca con n´meros enteros. Es el primero que necesito tener listo u para ofrecer a los profesores de ense˜anza primaria un refuerzo n para sus alumnos en el pr´ximo comienzo de curso. El m´dulo o o de aritm´tica base incluye las cuatro operaciones b´sicas (sumar, e a restar, multiplicar y dividir) con n´meros enteros. Los alumnos u no solo tendr´n que resolver los c´lculos m´s elementales sino a a a tambi´n resolver expresiones con par´ntesis y/o con varias opee e raciones encadenadas. As´ aprender´n la precedencia de los opeı a radores y el trabajo con par´ntesis: las propiedades distributiva, e asociativa y conmutativa. Los ejercicios estar´n creados por el a usuario profesor que introducir´ las expresiones matem´ticas en a a el sistema para que su resultado sea calculado autom´ticamente a y almacenado. El profesor decide en qu´ nivel va cada expresi´n e o matem´tica. En otros ejercicios se le pedir´ al ni˜o que se invente a a n las expresiones matem´ticas y les ponga un resultado. El prograa ma dispondr´ de una calculadora que s´lo ser´ accesible para los a o a profesores y los jugadores de niveles avanzados. La calculadora evaluar´ y resolver´ las mismas expresiones del sistema de juego. a a Cuando el jugador consigue un cierto n´mero de puntos puede u pasar de nivel, en cuyo caso un email es enviado al tutor para que sea informado de los logros del tutelado. El n´mero m´ u ınimo de puntos para pasar de nivel debe ser configurable.

%

En Scrum un sprint es una iteraci´n o

122

Cap´ ıtulo 8 tests de aceptaci´n (o de cliente) son frases cortas y precisas escritas con el o lenguaje del dominio de negocio. Son tan sencillas que al verlas el cliente s´lo o tiene que decir si son afirmaciones correctas o no. Son ejemplos. Los ejemplos evitan la ambig¨edad que se puede inmiscuir en la descripci´n de una tarea. u o Sabemos que es muy peligroso dar por sentado cuestiones referentes a la l´gica de negocio, que debemos ce˜irnos exclusivamente a la funcionalidad o n que se requiere. El motor de juego para aritm´tica implica muchos ejemplos e a poco que nos pongamos a pensar. ¿Qu´ pasa si el profesor no ha a˜adido e n suficientes expresiones a un nivel como para alcanzar la puntuaci´n que pero mite pasar el nivel siguiente? ¿se pueden eliminar expresiones de un nivel? ¿se pueden a˜adir o modificar? ¿c´mo afecta eso a las puntuaciones? ¿y si n o hay jugadores en medio del nivel? ¿se puede trasladar una expresi´n de un o nivel a otro?. Cada pregunta se resolver´ con varios ejemplos. Tenemos por ıa delante un buen trabajo de an´lisis. Sin embargo, por fines docentes, empea zaremos abordando cuestiones m´s simples como el propio funcionamiento a de la calculadora. Los tests de aceptaci´n iniciales son: o
"2 + 2", devuelve 4 "5 + 4 * 2 / 2", devuelve 9 "3 / 2", produce el mensaje ERROR "* * 4 - 2": produce el mensaje ERROR "* 4 5 - 2": produce el mensaje ERROR "* 4 5 - 2 -": produce el mensaje ERROR "*45-2-": produce el mensaje ERROR

Estos ejemplos de funcionamiento parecen una chorrada pero si no estuvieran ah´ podr´ ı, ıamos haber pensado que se requer´ par´ntesis para definir ıan e la precedencia de los operadores o que la notaci´n de los comandos era polaca o inversa2 . A trav´s de los ejemplos queda claro que los diferentes elementos e del comando se operan seg´n la precedencia, donde la multiplicaci´n y la u o divisi´n se operan antes que la suma y la resta. Y tambi´n sabemos que o e un resultado con decimales, no se permite porque nuestra aritm´tica b´sica e a trabaja unicamente con enteros. Otra cosa que queda clara es que los ope´ radores y operandos de la expresi´n se separan por un espacio o sea, que las o expresiones son cadenas de texto. L´gicamente hay m´s ejemplos pero vamos a empezar la implementaci´n o a o ya para no dilatar m´s el an´lisis. As´ de paso vemos el caso en que descubria a ı mos nuevos requisitos de negocio cuando ya estamos implementando c´digo, o algo que sucede la gran mayor´ de las veces. ıa
2

http://es.wikipedia.org/wiki/Notaci %C3 %B3n polaca inversa

123

Esto no debe distraernos de lo que estamos haciendo o ni tampoco influenciarnos. uno para la aplicaci´n y otro para su o o bater´ de tests. Tenemos que centrarnos en una sola cosa cada vez para llevarla a cabo como es debido. que vaya escribiendo todos los fragmentos de c´digo fuente que se exponen mientras lee. Le recomiendo encarecidamente que lo haga tal cual.Cap´ ıtulo 8 Manos a la obra. literalmente. a Ahora toca escoger uno de los tests de aceptaci´n y pensar en una lista de o elementos que nos hacen falta para llevarlo a cabo. es com´n que vengan u a la mente casos de uso que no estaban contemplados o bien dudas sobre la l´gica de negocio. Este ser´ su primer a a proyecto TDD si lo tiene a bien. Abrimos un nuevo proyecto con nuestro IDE favorito por un lado y un editor de textos sencillo por otro. lo cual no significa que las suyas sean inapropiadas. Sucede que las decisiones de dise˜o las esn toy tomando yo y pueden diferir de las que usted tome. Los seguidores de uno de los lenguajes no deben saltarse los cap´ ıtulos en los que se usa el otro porque en ambos cap´ ıtulos existen explicaciones v´lidas para los dos lenguajes y referencias de uno a otro. Estamos combinando ATDD con o TDD. A la pantalla del editor de texto le vamos a llamar libreta y es donde apuntaremos los ejemplos que nos quedan por hacer y todo lo que se nos ocurre mientras estamos escribiendo tests o l´gica de negocio: normalmente o cuando estamos concentrados en el test que nos ocupa. Antes de copiar directamente del libro intente pensar por usted mismo cu´l es el siguiente paso a dar en cada momento. La limitaci´n de un libro impreso es su ausencia de interactividad y o no podemos hacer nada para solventar este inconveniente. por eso lo anotamos en la libreta con una frase breve que nos permita volver a retomarlo cuando acabemos. Si no o tuviera que escribir este cap´ ıtulo con fines docentes seguramente empezar´ ıa 124 . T´ ıpicamente creamos dos m´dulos. sino simplemente diferentes. Hacer un peque˜o an´lisis n a del ejemplo y tratar de definir al menos tres o cuatro ejemplos m´s sencillos a que podamos convertir en tests de desarrollo (unitarios y de integraci´n o o s´lo unitarios) para empezar a hacer TDD. Para no mezclar los dos lenguajes continuaa mente vamos a escribir primero en C# y luego veremos su contrapartida en Python. No se desanime por ello. Los dos elementos que se me ocurren ahora mismo son una clase que sea capaz de operar enteros y un analizador que sea capaz de identificar los distintos elementos de una expresi´n contenida en una cadena de texto. Si estamos con C# ser´n dos DLLs y si estamos con Python ıa a ser´n dos paquetes distintos. as´ ir´ practicando o ı a desde ya. Abrimos tambi´n la herramienta con que se ejecuta la bater´ de tests (NUnit e ıa gr´fico o una consola donde invocar a nunit-console o el script Python a que inicia unittest). Si cree que una determinada refactorizaci´n o decisi´n de dise˜o es inapropiada no dude en o o n compartirlo mediante la web del libro.

"* * 4 . Vamos a agregarle los tests unitarios que nos gustar´ hacer para resolver el primero de los tests de aceptaci´n. devuelve 4 o Sumar 2 al n´mero 2. mantendremos ambas cosas en la libreta."5 + 4 * 2 / 2". devuelve 2 u La cadena "2 + 2"tiene dos n´meros y un operador que son ’2’.2 : produce ERROR o # Aceptaci´n -"*45-2-": produce ERROR o § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 Vamos a por el primer test de desarrollo: System . System . AreEqual (4 . casi sin darnos cuenta: hemos penn sado en el nombre de la clase. ’2’ y ’+’ u # Aceptaci´n . System . NUnit . Add (2 . using using using using using namespace UnitTests { [ TestFixture ] public class CalculatorTests { [ Test ] public void Add () { Calculator calculator = new Calculator ()."3 / 2". Collections . Assert . SuperCalculator . } } } ¦ ¥ El c´digo no compila porque todav´ no hemos creado la clase Calculator. devuelve 9 o # Aceptaci´n . los par´metros que recibe y el resultado que e o a 125 ."2 + 2". a a Generalmente los tests de aceptaci´n se guardan por separado.2": produce ERROR o # Aceptaci´n -"* 4 5 . al margen o de la libreta que contiene las cuestiones relativas a desarrollo.2": produce ERROR o # Aceptaci´n -"* 4 5 . o ıa Sin embargo. devuelve 4 u Restar 3 al n´mero 5. Text . ıa o # Aceptaci´n .Cap´ ıtulo 8 por trabajar en el analizador pero los ejemplos de la clase que hace c´lculos a van a ser m´s f´ciles de asimilar inicialmente. en c´mo es su constructor y en c´mo es su o o primer m´todo (c´mo se llama. Si empleamos un framework tipo Fit o Concordion los tests de aceptaci´n tendr´ su proo ıan pio lugar pero por simplificar y de nuevo con fines docentes. 2). result ). Generic . Framework . ya hemos dise˜ado algo. produce ERROR o # Aceptaci´n . int result = calculator .

Vale vale. int arg2 ) { return 4. Cuando no tenemos experiencia con TDD es muy importante que sigamos el algoritmo al pie de la letra porque de lo contrario no llegaremos a exprimir al m´ximo la t´cnica como herramienta de dise˜o. Intente imaginar cu´l es literalmente el m´ a ınimo c´digo o posible para que el test pase. AreEqual (7 . a e n Ya que el test pasa. int arg2 ) { return arg1 + arg2 . int result = calculator . Estamos dise˜ando la API tal cual la necesitamos. 5). es decir. Assert . Pues s´ s´ que sirve. Seguro que es m´s largo que el que de verdad a es m´ ınimo: § 1 2 3 4 5 6 7 8 9 10 namespace SuperCalculator { public class Calculator { public int Add ( int arg1 . todos sab´ ıamos eso. } ¦ ¥ ¿Por qu´ no hemos devuelto 7 directamente que es el c´digo m´ e o ınimo? Porque entonces el test anterior deja de funcionar y se trata de escribir el 126 . A este proceso de definir el SUT a golpe de test se le llama triangulaci´n. } ¦ ¥ Ejecutamos. luz verde. puesto que hasta ahora nuestra funci´n de suma s´lo funciona en el caso 2 + 2. o § 1 2 3 4 5 6 7 [ Test ] public void A d d W i t h D i f f e r e n t A r g u m e n t s () { Calculator calculator = new Calculator ().Cap´ ıtulo 8 devuelve). Es como si o o estuvi´semos moldeando una figura con un cincel. descubrimos una cualidad de la funci´n: para valores de o entrada diferentes presenta resultados diferentes. result ). Cada test es un golpe que e moldea el SUT. en este caso es muy evidente pero ¿y si no tuvi´ramos un conocimiento e tan preciso del dominio?. Add (2 . A continuaci´n escribimos el m´ o ınimo c´digo posible o para que el test pase. ı seguir el algoritmo TDD y escribir literalmente el m´ ınimo c´digo posible para o que el test pase. observamos que estamos en rojo y acto seguido modificamos el SUT: § 1 2 3 4 public int Add ( int arg1 . ¡No sirve para nada!. } } } ¦ ¥ ¡Qu´ c´digo m´s simpl´n!. Al e o a o ı. sin limitaciones n ni funcionalidad extra. necesitamos otro test que nos obligue a terminar de implementar la funcionalidad deseada.

ya nos ´ ıbamos directos a escribir el c´digo de o la funci´n que resta! Tenemos que estar bien atentos para dirigirnos primero o 127 & a http://jamesnewkirk. AreEqual ( result . Algunos como o James Newkirk son tajantes en lo que respecta al uso del SetUp y dice que si por ´l fuera eliminar´ esta funcionalidad de NUnita . Add (2 . Una vez hemos conseguido luz verde.com/posts/2007/09/why-you-should.html % . } [ Test ] public void Add () { int result = _calculator . a $ ¥ Vamos a por la resta. El e ıa color de este libro no es blanco ni negro sino una escala de grises: haga usted lo que su experiencia le diga que es m´s conveniente. 5). Si cada uno de o los tests necesitase de instancias de la calculadora distintas para funcionar (por ejemplo haciendo llamadas a diferentes versiones de un constructor sobrecargado).Cap´ ıtulo 8 c´digo m´ o ınimo para que todos los tests tengan resultado positivo. AreEqual ( result . [ SetUp ] public void SetUp () { _calculator = new Calculator (). ´ Advertimos que la instancia de la calculadora es un fixture y por lo tanto puede ser extra´ como variable de instancia de la clase CalculatorTests. Uy!. ıda Eliminamos la duplicidad: public class CalculatorTests { private Calculator _calculator . 2). La manera m´s directa de identificarlo es buso a cando c´digo duplicado. 4). 7).typepad. ser´ conveniente crearlas en ıa cada uno de ellos en lugar de en la inicializaci´n. } § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ¦ ' Usar el SetUp no siempre es la opci´n correcta. Add (2 . Assert . En el SUT no hay nada duplicado pero en los tests o s´ La llamada al constructor de Calculator. hay que pensar si existe alg´n bloque de c´digo u o susceptible de refactorizaci´n. (v´ase l´ ı: e ınea 4 del ultimo test). } [ Test ] public void A d d W i t h D i f f e r e n t A r g u m e n t s () { int result = _calculator . Assert .

Cap´ ıtulo 8 al test unitario.2 : produce ERROR o # Aceptaci´n . Substract (5 . § 1 2 3 4 5 6 [ Test ] public void Substract () { int result = calculator ."* * 4 ."2 + 2". Assert . devuelve 9 o # Aceptaci´n . Si e o a se da cuenta de que ha olvidado escribir el test o de que est´ escribiendo m´s a a c´digo del necesario para la correcta ejecuci´n del test. Ahora necesitamos otro test que nos permita pro128 . AreEqual (2 . Entonces la apuntamos en la lio breta para no interrumpir lo que estamos haciendo y que no se nos olvide: ¿Puede ser negativo el resultado de una resta en nuestra calculadora? Confirmar que efectivamente el orden de los par´metros a produce resultados diferentes # Aceptaci´n . result ). Estamos ı o n definiendo una API donde el orden de los par´metros de los m´todos es ima e portante. La frustraci´n no nos ayudar´. Esto es probablemente tan obvio que no nos lo hemos planteado e tal que as´ pero si por un momento le hemos dado la vuelta a los par´metros ı a mentalmente. devuelve 4 o Sumar 2 al n´mero 2. det´ngase y vuelva a o o e empezar. funciona."*45-2-": produce ERROR o Como estamos en rojo vamos a ponernos en verde lo antes posible: § 1 2 3 4 public int Substract ( int ag1 . ’2’ y ’+’ u # Aceptaci´n .2": produce ERROR o # Aceptaci´n . devuelve 4 u Restar 3 al n´mero 5. ha tenido que llegarnos la pregunta de si se aceptan n´meros u negativos como resultado de la operaci´n. int arg2 ) { return 2. devuelve 2 u La cadena "2 + 2"tiene dos n´meros y un operador que son ’2’."5 + 4 * 2 / 2". Al comienzo es muy com´n encontrarse inmerso en la imu plementaci´n de c´digo sin haber escrito un test que falla antes y hay que o o tener ´sto en cuenta para no desanimarse."* 4 5 .2": produce ERROR o # Aceptaci´n . En el test que acabamos de escribir asumimos que a 5 se le resta 3 y no al rev´s. } ¦ ¥ Aqu´ acabamos de hacer otra decisi´n de dise˜o sin advertirlo. produce ERROR o # Aceptaci´n . } ¦ ¥ De acuerdo."3 / 2". 3)."* 4 5 .

Substract (3 .arg2 ."* 4 5 ."5 + 4 * 2 / 2". produce ERROR o # Aceptaci´n ."2 + -2"devuelve 0 o Y triangulamos."3 / 2". int arg2 ) { return arg1 . Como existe o una duda.Cap´ ıtulo 8 bar los otros casos de uso de la funci´n y miramos a la libreta. Para u aclararlo con un ejemplo se a˜ade un test de aceptaci´n a la libreta: n o # Aceptaci´n .. 129 .. Escribimos el m´ ınimo c´digo necesario para pedirle a la o resta que sea capaz de manejar resultados negativos: § 1 2 3 4 5 6 [ Test ] public void S u b s t r a c t R e t u r n i n g N e g a t i v e () { int result = calculator . AreEqual ( -2 ."*45-2-": produce ERROR o # Aceptaci´n . devuelve 2 u La cadena "2 + 2"tiene dos n´meros y un operador que son ’2’. devuelve 4 u Restar 3 al n´mero 5. result ). devuelve 9 o # Aceptaci´n . decide que es aceptable devolver n´meros negativos porque al u fin y al cabo es como si hubiese un signo “menos” delante del n´mero.2": produce ERROR o # Aceptaci´n . 5). nos reunimos con el equipo y lo comentamos.. preguntamos al cliente y ."* * 4 . Assert ."* 4 5 . } ¦ ¥ Busquemos el color verde: § 1 2 3 4 public int Substract ( int arg1 . devuelve 4 o Sumar 2 al n´mero 2."2 + 2". ’2’ y ’+’ u # Aceptaci´n . } ¦ ¥ Y justo al escribir esto se nos viene otra serie de preguntas.2": produce ERROR o # Aceptaci´n .2 : produce ERROR o # Aceptaci´n .

"* 4 5 ."*45-2-": produce ERROR o # Aceptaci´n .2 : produce ERROR o # Aceptaci´n . devuelve 9 o # Aceptaci´n ."2 + 2". Transformarmos las preguntas en nuevos tests de aceptaci´n: o 130 . n Ciertamente no sabemos si la calculadora correr´ en un ordenador con altas a prestaciones o en un tel´fono m´vil. ’2’ y ’+’ u # Aceptaci´n .2": produce ERROR o # Aceptaci´n ."2 + -2"devuelve 0 o ¿Cual es el n´mero m´s peque~o que se permite como par´metro? u a n a ¿Y el m´s grande? a ¿Qu´ pasa cuando el resultado es menor que el n´mero m´s peque~o permitido? e u a n ¿Qu´ pasa cuando el resultado es mayor que el n´mero m´s grande permitido? e u a Como puede apreciar hemos eliminado las cuestiones resueltas de la libreta.Cap´ ıtulo 8 # Aceptaci´n . devuelve 4 o La cadena "2 + 2"tiene dos n´meros y un operador que son ’2’. Quiz´s no tenga sentido permitir m´s e o a a d´ ıgitos de los que un determinado dispositivo puede mostrar en pantalla aunque el framework subyacente lo permita. produce ERROR o # Aceptaci´n . Las nuevas cuestiones ata˜en a todas las operaciones de la calculadora."* * 4 .2": produce ERROR o # Aceptaci´n ."3 / 2"."* 4 5 ."5 + 4 * 2 / 2".

Limite Inferior=10 y resultado menor que 10."3 / 2"."5 + 4 * 2 / 2".Si el l´mite inferior es -100 y alguno o ı de los par´metros o el resultado es menor que -100. ERROR a # Aceptaci´n .Limite Superior=100 y resultado mayor que 100."2 + -2"devuelve 0 o # Aceptaci´n . el analizador o podemos resolver la cuesti´n de los o 131 . devuelve 4 o La cadena "2 + 2"tiene dos n´meros y un operador que son ’2’.L´mite Inferior = -10 o ı # Aceptaci´n .Cap´ ıtulo 8 # Aceptaci´n . ERROR a Simplifiquemos las frases para tener unos tests de aceptaci´n m´s claros: o a # Aceptaci´n . produce ERROR o a # Aceptaci´n . Podemos seguir adelante afrontando la segunda l´ ınea de la libreta.2 : produce ERROR o # Aceptaci´n .2 : produce ERROR o # Aceptaci´n ."2 + 2"."* 4 5 ."3 / 2"."* 4 5 .L´mite Superior =500 o ı # Aceptaci´n ."* * 4 ."5 + 4 * 2 / 2"."* 4 5 .Limite Inferior=10 y par´metro menor que 10.Limite Superior=100 y par´metro mayor que 100. produce ERROR o Ahora tenemos dos caminos.Configurar el n´mero m´s peque~o o u a n que puede usarse como argumento y como resultado # Aceptaci´n . produce ERROR o # Aceptaci´n . produce ERROR o # Aceptaci´n .Configurar el n´mero m´s grande o u a que puede usarse como argumento y como resultado # Aceptaci´n . produce ERROR o # Aceptaci´n .2": produce ERROR o # Aceptaci´n .L´mite Superior =100 o ı # Aceptaci´n ."2 + -2"devuelve 0 o # Aceptaci´n . produce ERROR o a # Aceptaci´n . devuelve 9 o # Aceptaci´n . devuelve 9 o # Aceptaci´n . devuelve 4 o La cadena "2 + 2"tiene dos n´meros y un operador que son ’2’. ’2’ y ’+’ u # Aceptaci´n .2": produce ERROR o # Aceptaci´n ."*45-2-": produce ERROR o # Aceptaci´n ."* 4 5 ."2 + 2".L´mite Inferior = -1000 o ı # Aceptaci´n . ’2’ y ’+’ u # Aceptaci´n ."* * 4 ."*45-2-": produce ERROR o # Aceptaci´n .2": produce ERROR o # Aceptaci´n .2": produce ERROR o # Aceptaci´n .Si el l´mite superior es 100 y o ı alguno de los par´metros o el resultado es mayor que 100.

necesitamos modificar el SetUp porque usa la versi´n anterior. La a ra´ es el test de aceptaci´n y los nodos hijos son tests de desarrollo.Cap´ ıtulo 8 l´ ımites. Por otro lado podemos sobrecargar el constructor para tener ambas variantes pero. § 1 2 3 4 132 . int maxValue ) { } . con vistas a conseguir luz verde. ¿qu´ pasa entonces cuando no indicamos e los valores l´ ımite y se usan argumentos que superan el l´ ımite impuesto por el framework subyacente (en este caso .. No hay a ninguna regla que diga que primero se hace a lo ancho y luego a lo largo. en profundidad y en amplitud. con par´metros y sin ellos? a Por un lado si cambiamos el constructor para que acepte argumentos. int result = calculator . Assert . ya o que sin decidir no podemos ni compilar.. Substract (5 . ¿El constructor recibe par´metros? ¿Creamos dos versiones del a constructor. Es como si un contador diese la vuelta. mejor hacemos la prueba de sumar n´meros muy grandes cuyo e a u resultado excede dicho l´ ımite y observamos qu´ hace el runtime de . Bien pues ya podemos modificar el constructor para que admita los l´ ımites y los tests existentes que tras el cambio no compilen. lo cual nos o recuerda que los tests requieren mantenimiento. } § 1 2 3 4 5 6 7 ¦ ¥ Estamos en rojo y nisiquiera es posible compilar porque el constructor de la clase Calculator no estaba preparado para recibir par´metros. AreEqual ( -5 . result ).Net e (Int32. En TDD resolvemos el problema como si de un ´rbol se tratase. que ız o unas veces ser´n unitarios y otras veces quiz´s de integraci´n. Es un u comportamiento muy raro para una calculadora. Nos interesa m´s que sea a obligatorio definir los l´ ımites. el costo de este cambio es m´ ımino. La decisi´n la o tomamos en funci´n de nuestra experiencia. En este caso decidimos poder configurar la calculadora con el n´mero u m´s grande permitido y el m´s peque˜o. Hay que a tomar una decisi´n inmediata que no necesitamos apuntar en la libreta. un n´mero negativo. 100).. Sin embargo. 10). buscando lo que creemos que o nos va a dar m´s beneficio. Vamos a explorar a lo ancho.Net)?. El resultado es el n´mero m´s peque˜o u a n posible. Es a a n una funcionalidad que invocar´ ıamos de esta manera: [ Test ] public void S u b s t r a c t S e t t i n g L i m i t V a l u e s () { Calculator calculator = new Calculator ( -100 . No conviene decidir sin saber qu´ ocurrir´. bien en tiempo o bien en prestaciones.. Un ´rbol puea a o a de recorrerse de dos maneras.MaxValue + 1 por ejemplo). public class Calculator { public Calculator ( int minValue .

} ¦ ¥ § 1 2 No compila. hay que definir la propiedad UpperLimit en Calculator. de lo contrario deber´ a ıamos apuntarlo en la libreta) y nos encargamos de los casos de definici´n y consulta de l´ o ımites: [ Test ] public void SetAndGetUpperLimit () { Calculator calculator = new Calculator ( -100 . Si el m´todo Substract hubiese lanzado la e excepci´n. 100). 150). o [ Test ] public void S u b s t r a c t E x c e d i n g L o w e r L i m i t () { Calculator calculator = new Calculator ( -100 . Necesitamos poder consultar los l´ ımites definidos y actuar en consecuencia. Esto nos recuerda que hay l´ ıneas en nuestra lista que tenemos que resolver antes que la que nos ocupa. vamos a modificarlo escogiendo uno de los casos de uso de la lista. } catch ( OverflowException ) { // Ok . Assert . the SUT works as e x p e c t e d } } ¦ ¥ § 1 2 3 4 5 6 Efectivamente el test falla.Cap´ ıtulo 8 } 5 ¦ ¥ § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 El ultimo test que hab´ ´ ıamos escrito (SubstractSettingLimitValues) no tiene nada diferente a los dem´s porque ya todos definen los l´ a ımites. Fail ( " Exception is not being thrown when " + " exceeding lower limit " ). try { int result = calculator . Substract (10 . public class Calculator { 133 . ´sta hubiese sido capturada por el bloque catch que silenciosao e mente hubiese concluido la ejecuci´n del test. No es obligatorio que exista una sentencia Assert. AreEqual (100 . Por tanto dejamos aparcado ´ste (como el test falla no se nos e olvidar´ retomarlo. 100). Para conseguir luz verde ya no vale lanzar la excepci´n sin hacer o o ninguna comprobaci´n porque los otros tests fallar´ o ıan. Un test que concluye calladito o como este es un test con luz verde. calculator . aunque es conveniente usarlas para aumentar la legibilidad del c´digo. Puesto que la propiedad LowerLimit es ex´ctamente del mismo tipo que a UpperLimit. Tomamos el caso en que se excede el l´ ımite inferior y decidimos que en tal situaci´n queremos o lanzar una excepci´n de tipo OverflowException. Assert . UpperLimit ). aqu´ podemos atrevernos a escribir el c´digo que asigna y reı o cupera ambas.

int arg2 ) 134 . } 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ¦ ¥ As´ tiene sentido a˜adir otro Assert al test en que estamos trabajando y ı. Assert . } } public int UpperLimit { get { return _upperLimit . Assert . LowerLimit ). Es por eso que en algunos casos es permisible incluir varios Assert dentro de un mismo test y en otros no. sino en una toma de decisiones. } set { _upperLimit = value . 100). n cambiarle el nombre . Reconocer qu´ es e lo que el test est´ probando es important´ a ısimo para separar adecuadamente la funcionalidad en sus respectivos m´todos o clases. AreEqual ( -100 . o a ıcil § 1 2 3 4 5 6 7 [ Test ] public void SetAndGetLimits () { Calculator calculator = new Calculator ( -100 . Escribir tests no debe convertirse en una cuesti´n o de copiar y pegar. ¿No hab´ ıamos dicho que era conveniente que un test tuviera un unico Assert y probase una s´la cosa? Es que sem´nticamente o ´ o a funcionalmente ambas propiedades de la clase son para lo mismo. calculator . Cuando se escribe un e test sin tener claro lo que se pretende. int maxValue ) { _upperLimit = maxValue . calculator . public int LowerLimit { get { return _lowerLimit . AreEqual (100 . UpperLimit ). se obtiene un resultado doblemente negativo: c´digo de negocio problem´tico y un test dif´ de mantener. } ¦ ¥ El valor de los tests es que nos obligan a pensar y a descubrir el sentido de lo que estamos haciendo. } set { _lowerLimit = value .. desde el punto de vista del test: asignar valores y recuperar valores de variables de instancia. O sea que no estamos infringiendo ninguna norma. Ejecutamos los tests y pasan todos menos SubstractExcedingLowerLimit por lo que nos ponemos manos a la obra y escribimos el m´ ınimo c´digo posible o que le haga funcionar y no rompa los dem´s. a § 1 public int Substract ( int arg1 . private int _lowerLimit ..Cap´ ıtulo 8 private int _upperLimit . depende de si estamos probando la misma casu´ ıstica aplicada a varios elementos o no. } } public Calculator ( int minValue . _lowerLimit = minValue .

Assert . try { int result = calculator . 100). if ( result < _lowerLimit ) { throw new OverflowException ( " Lower limit exceeded " ). } return result . entonces es posible. the SUT works as e x p e c t e d } } ¦ ¥ § 1 2 3 4 5 6 7 8 9 He tomado el m´todo Add en lugar de restar para no olvidar que estas comproe baciones se aplican a todas las operaciones de la calculadora. Vamos paso a paso: [ Test ] public void A d d E x c e d i n g U p p e r L i m i t () { Calculator calculator = new Calculator ( -100 . if ( result > _upperLimit ) { throw new OverflowException ( " Upper limit exceeded " ). Add (10 . Implementaci´n o m´ ınima: public int Add ( int arg1 . } ¦ ¥ § 1 2 3 4 5 6 Funciona pero se ve claramente que este m´todo de suma no hace la e comprobaci´n del l´ o ımite inferior. } return result . int arg2 ) { int result = arg1 + arg2 . } 2 3 4 5 6 7 8 9 ¦ ¥ § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Nos queda probar el caso en el que el resultado excede el l´ ımite superior y los casos en que los argumentos tambi´n exceden los l´ e ımites. 150). try { 135 . Fail ( " This should fail : we ’ re exceding upper limit " ). Entonces es el momento de atacar los casos en que los par´metros que se a pasan superan ya de por s´ los l´ ı ımites establecidos. } catch ( OverflowException ) { // Ok .Cap´ ıtulo 8 { int result = arg1 . 100).arg2 . [ Test ] public void A r g u m e n t s E x c e e d L i m i t s () { Calculator calculator = new Calculator ( -100 . ¿Es posible que el resultado de una suma sea un n´mero menor que el l´ u ımite inferior? Si uno de sus argumentos es un n´mero m´s peque˜o que el propio l´ u a n ımite inferior.

Dos comprobaciones en el mismo test.1). } catch ( OverflowException ) { // Ok . if ( result > _upperLimit ) { throw new OverflowException ( " Upper limit exceeded " ). int result = arg1 + arg2 . Fail ( " This should fail : arguments exceed lim its " ). if ( arg2 < _lowerLimit ) throw new OverflowException ( " Second argument exceeds lower limit " ). } ¦ ¥ ¿Y qu´ tal a la inversa? e § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 [ Test ] public void A r g u m e n t s E x c e e d L i m i t s I n v e r s e () { Calculator calculator = new Calculator ( -100 . UpperLimit + 1). A por el verde: § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public int Add ( int arg1 . Add ( calculator . if ( arg2 < _lowerLimit ) 136 . LowerLimit . calculator . calculator . LowerLimit -1 . lo cual es v´lido porque son realmente la a misma caracter´ ıstica. Assert . int arg2 ) { if ( arg1 > _upperLimit ) throw new OverflowException ( " First argument exceeds upper limit " ). try { calculator .Cap´ ıtulo 8 calculator . 100). UpperLimit + 1 . } return result . this works } } ¦ ¥ Pint´moslo de verde!: e § 1 2 3 4 5 6 public int Add ( int arg1 . int arg2 ) { if ( arg1 > _upperLimit ) throw new OverflowException ( " First argument exceeds upper limit " ). Add ( calculator . } catch ( OverflowException ) { // Ok . Assert . this works } } 7 8 9 10 11 12 13 14 15 ¦ ¥ Este test se asegura de no caer en el caso anterior (el de que el resultado de la suma es inferior al l´ ımite) y aprovecha para probar ambos l´ ımites. Fail ( " This should fail : arguments exceed lim its " ).

Cap´ ıtulo 8 throw new OverflowException ( " First argument exceeds lower limit " ). } return result . if ( result > _upperLimit ) { throw new OverflowException ( " Upper limit exceeded " ). Sin embargo. Lo m´s r´pido ser´ copiar las l´ a a ıa ıneas de validaci´n de la o suma y pegarlas en la resta. 100). if ( arg2 > _upperLimit ) throw new OverflowException ( 137 . LowerLimit . Fail ( " This should fail : arguments exceed lim its " ). Estamos ante un caso perfecto para refactorizar extrayendo un m´todo: e § 1 2 3 4 5 6 7 8 9 10 11 12 13 public bool ValidateArgs ( int arg1 . if ( arg2 > _upperLimit ) throw new OverflowException ( " Second argument exceeds upper limit " ). int result = arg1 + arg2 . this works } } ¦ ¥ El test no pasa. } catch ( OverflowException ) { // Ok . Substract ( calculator . e Esto es lo aconsejable para lo programadores menos experiementados. algo tan evidente puede ser abreviado en un solo paso por el desarrollador experto.1). Efectivamente podemos hacerlo. calculator . if ( arg1 < _lowerLimit ) throw new OverflowException ( " Second argument exceeds lower limit " ). int arg2 ) { if ( arg1 > _upperLimit ) throw new OverflowException ( " First argument exceeds upper limit " ). if ( arg2 < _lowerLimit ) throw new OverflowException ( " First argument exceeds lower limit " ). Assert . luego ver que los tests pasan y despu´s observar que existe duplicidad y exige refactorizar. try { calculator . if ( arg1 < _lowerLimit ) throw new OverflowException ( " Second argument exceeds lower limit " ). UpperLimit + 1 . } 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ¦ ¥ La resta deber´ comportarse igual: ıa § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 [ Test ] public void A r g u m e n t s E x c e e d L i m i t s O n S u b s t r a c t () { Calculator calculator = new Calculator ( -100 .

Cap´ ıtulo 8 " Second argument exceeds upper limit " ). } 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 ¦ ¥ Los tests pasan. if ( result > _upperLimit ) { throw new OverflowException ( " Upper limit exceeded " ). int result = arg1 . if ( result < _lowerLimit ) { throw new OverflowException ( " Lower limit exceeded " ). arg2 ). ¿lo es? (la duda es buena querido lector. ¿Est´n todos ı a los casos de uso probados?. ıa SUT y es la l´ ınea que llama al m´todo de validaci´n pero de eso nos encare o garmos despu´s. int arg2 ) { ValidateArgs ( arg1 . Tener una sola l´ e ınea duplicada no es muy malo.. } public int Add ( int arg1 . } return result . arg2 ). } public int Substract ( int arg1 .. } return result . y va a ser que s´ que es malo). ¿Queda m´s c´digo duplicado?. S´ todav´ queda algo en el a o ı. return true . La libreta dice: 138 .arg2 . int result = arg1 + arg2 . int arg2 ) { ValidateArgs ( arg1 .

es f´cil que dejemos algunos atr´s por lo que expandimos a a la lista: 139 ."* * 4 ."* 4 5 .Limite Inferior=10 y par´metro menor que 10.Cap´ ıtulo 8 # Aceptaci´n .Limite Superior=100 y par´metro mayor que 100.L´mite Superior =100 o ı # Aceptaci´n ."*45-2-": produce ERROR o # Aceptaci´n . produce ERROR o Las ultimas l´ ´ ıneas albergan m´ltiples ejemplos y retenerlos todos mentalu mente es peligroso."3 / 2".L´mite Inferior = -1000 o ı # Aceptaci´n .2": produce ERROR o # Aceptaci´n .2": produce ERROR o # Aceptaci´n . produce ERROR o a # Aceptaci´n . devuelve 4 o La cadena "2 + 2"tiene dos n´meros y un operador que son ’2’. produce ERROR o # Aceptaci´n . produce ERROR o # Aceptaci´n ."* 4 5 . ’2’ y ’+’ u # Aceptaci´n .2 : produce ERROR o # Aceptaci´n ."2 + 2".L´mite Inferior = -10 o ı # Aceptaci´n .L´mite Superior =500 o ı # Aceptaci´n ."2 + -2"devuelve 0 o # Aceptaci´n ."5 + 4 * 2 / 2".Limite Inferior=10 y resultado menor que 10.Limite Superior=100 y resultado mayor que 100. devuelve 9 o # Aceptaci´n . produce ERROR o a # Aceptaci´n .

"* 4 5 . Necesitar´ o ıamos otro test m´s. Esto a empieza a oler mal.2": produce ERROR o # Aceptaci´n . devuelve 9 o # Aceptaci´n ."*45-2-": produce ERROR o # Aceptaci´n . 140 . ¿Qu´ necesitamos probar? Necesitae mos asegurarnos de que el validador valida y de que todas las operaciones aritm´ticas preguntan al validador. produce ERROR o # Aceptaci´n . De acuerdo. Cuando se avecina la jugada de copiar y pegar tests a diestro y siniestro. Assert . modificamos los dos tests que hacen al validador comprobar los argumentos: § 1 2 3 4 5 6 7 8 9 [ Test ] public void A r g u m e n t s E x c e e d L i m i t s () { Calculator calculator = new Calculator ( -100 .L´mite Superior =500 o ı # Aceptaci´n ."3 / 2". vao o e mos a terminar con una cantidad de tests muy grande e in´til (porque en u verdad est´n todos probando la misma cosa) a base de copiar y pegar.L´mite Inferior = -10 o ı A: El primer argumento sobrepasa el l´mite superior ı B: El primer argumento sobrepasa el l´mite inferior ı C: El segundo argumento sobrepasa el l´mite superior ı D: El segundo argumento sobrepasa el l´mite inferior ı E: El resultado de una operaci´n sobrepasa el l´mite superior o ı F: El resultado de una operaci´n sobrepasa el l´mite inferior o ı Todos los casos de uso anteriores se aplican a todas las operaciones aritmeticas No hemos probado por completo que la resta valida sus dos argumentos. LowerLimit . calculator .2": produce ERROR o # Aceptaci´n . En verdad es ´sto lo que queremos.L´mite Inferior = -1000 o ı # Aceptaci´n . la cosa huele mal. 100). Fail ( " This should fail : arguments exceed lim its " ). try { calculator . devuelve 4 o La cadena "2 + 2"tiene dos n´meros y un operador que son ’2’."* * 4 ."* 4 5 ."2 + 2". a Si escribimos dos tests para la validaci´n en cada operaci´n aritm´tica. UpperLimit + 1 ."2 + -2"devuelve 0 o # Aceptaci´n ."5 + 4 * 2 / 2". s´lo hemos probado los casos A y D restando. ValidateArgs ( calculator .L´mite Superior =100 o ı # Aceptaci´n .1). ’2’ y ’+’ u # Aceptaci´n .2 : produce ERROR o # Aceptaci´n .Cap´ ıtulo 8 # Aceptaci´n . Nos e e hemos dado cuenta al identificar un mal olor.

3 Ver Cap´ ıtulo 7 en la p´gina 111 a 141 . int lowerLimit = 100. try { calculator . o a ıa o solo que cambiando ValidateArgs por Add o Substract. Fail ( " This should fail : arguments exceed lim its " ). } catch ( OverflowException ) { // Ok . int arg2 = -20. Tiene m´s de una responsabilidad. A ıa menudo los m´todos privados son indicadores de colaboraci´n entre clases. 100). Assert . ValidateArgs ( calculator . UpperLimit + 1). calculator . que ya est´ probado a a con otros tests. Lo que queremos validar no es el resultado de las funciones matem´ticas. GenerateStrictMock < LimitsValidator >(). La clase Calculator se concibi´ para realizar operaciones aritm´ticas y ahora tambi´n est´ hacieno e e a do validaciones. Vamos a escribir el primer test que valida la cooperaci´n entre la calculao dora y el validador incluso aunque todav´ no hemos separado el c´digo. this works } } [ Test ] public void A r g u m e n t s E x c e e d L i m i t s I n v e r s e () { Calculator calculator = new Calculator ( -100 . sino su comportamiento.. De hecho el modificador a public con que se defini´ el m´todo ValidateArgs quedaba bastante rao e ro. var validatorMock = MockRepository .Cap´ ıtulo 8 } catch ( OverflowException ) { // Ok ..Mocks. puede que en lugar de definir el m´todo como privado sea m´s e a conveniente extraer una clase y hacer que ambas cooperen. this works } } 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 ¦ ¥ § 1 2 3 4 5 6 7 8 9 ¿C´mo comprobamos ahora que las operaciones aritm´ticas validan primero o e sin repetir c´digo? Porque tal como est´ ahora el test ser´ el mismo c´digo. e o es decir. Y cuando aparece la necesidad de validar comportamiento hay que detenerse un momento y analizar si las clases cumplen el Principio de una sola responsabilidad3 . [ Test ] public void S u b s t r a c t I s U s i n g V a l i d a t o r () { int arg1 = 10. int upperLimit = 100. cualquiera hubiera dicho que se deber´ haber definido como privado. LowerLimit -1 . ¡El ıa o test siempre primero! y para ello nos servimos del framework Rhino.

ValidateArgs ( arg1 .org/wiki/Decorator (patr´n de dise˜o) % 142 .. que no interceptan sino s´lo marcan. o $ o n http://es. ¿para comprobar que todas las operaciones aritm´ticas hablan con el validador tenemos que copiar y pegar este e test y modificarle una l´ ınea? ¡Sigue oliendo mal! Los m´todos de suma y resta no solo est´n realizando sus operaciones e a aritm´ticas respectivas. Es cierto. s´ por experiencia que tal herramienta del lenguaje debe limitarse e a funciones muy peque˜as que a˜aden atributos a la funci´n decon n o rada. estamos dejando de hacer programaci´n orientada a o objetos para regresar a la vieja programaci´n procedimental. entonces.. Add ( arg1 . arg2 ). Al fin y al cabo los l´ ımites s´lo le sirven al validador. que sea otra quien se encargue de operar comandos m´s n a complejos. arg2 )). } 10 11 12 13 14 15 16 ¦ ¥ El c´digo dice que hay un objeto que implementa la interfaz LimitsValidator o y que se espera que se llame a su m´todo ValidateArgs. Desde luego el mal olor del copiar/pegar indica que hay que cambiar algo. Crea una instancia e nueva de la calculadora y le inyecta el validador como par´metro en el consa tructor. antes y despu´s de operar hay que validar. es decir. En este e sentido tienen m´s potencia que los atributos de C# (lo que va a entre corchetes).wikipedia. Parece que es lo o que quer´ ıamos hacer pero. sino que incluyen una parte extra de l´gica de negoe o cio que es la que dice. Calculator calculator = new Calculator ( validatorMock ). Adem´s si el c´digo del decorador va m´s all´ de etiquetar a o a a al decorado... A veces con la propia carga de m´dulos Python en memoria o se ejecuta el c´digo de los decoradores con resultados impredecio bles. Por lo tano to un decorador a lo Python parece apropiado aqu´ Sin embargo ı. si la responsabilidad de la calculadora (la clase Calculator. A continuaci´n se ejecuta la llamada al m´todo de suma y finalmente se le o e pregunta al mock si las expectativas se cumplieron. V e r i f y A l l E x p e c t a t i o n s (). ¿No ser´ e ıa mejor si hubiese una clase que coordinase esto?. Lo que queremos hacer tiene toda la pinta del patr´n Decorador4 . si se produjo la llamada tal cual se especific´. validatorMock . calculator . Expect ( x = > x .Cap´ ıtulo 8 validatorMock . no la aplicaci´n) es resolver operao ciones peque˜as. o ' & 4 En Python los decoradores existen como parte del lenguaje. aunque no es el validador verdadero sino un impostor (un mock). Hemos decidido modificar el constructor de la o calculadora para tomar una instancia de un validador en lugar de los valores l´ ımite. son funciones que interceptan la llamada al m´todo decorado.

DynamicProxy2. Expect ( x = > x . int arg2 = -20. GenerateStrictMock < LimitsValidator >(). ValidateResult ( result )). V e r i f y A l l E x p e c t a t i o n s (). Haso a http://www. que es o la base de Rhino. V e r i f y A l l E x p e c t a t i o n s (). el principal motivo es que as´ nos aseguramos que no se ejecuta la llamada ı en ninguna clase particular sino s´lo en el mock o 6 5 143 . Expect ( x = > x . arg2 )). CalcProxy calcProxy = new CalcProxy ( validatorMock . int upperLimit = 100. Once (). arg2 )). calcProxy . Expect ( x = > x . validatorMock . validatorMock . Return ( r esult ). int result = 1000. } ¦ ¥ Lo que dice este ejemplo o test es lo siguiente: Existe un validador al cual se invocar´ mediante los m´todos SetLimits y ValidateArgs consecutivaa e mente (y una sola vez cada uno).Mocks pero la curva de aprendizaje que conlleva usarlo.org/wiki/Proxy (patr´n de dise˜o) 7 N´tese que estamos usando interfaces como punto de partida para la generaci´n de o o los mocks. Repeat . Existe una calculadora7 que ejecutar´ su a operaci´n de suma y acto seguido el validador chequear´ el resultado. var calculatorMock = MockRepository .wikipedia. Lo malo de esta soluci´n es que nos lleva de n o o nuevo a mucho c´digo duplicado. int lowerLimit = -100. BinaryOperation ( calculatorMock .Cap´ ıtulo 8 En C# tenemos varias alternativas. calculatorMock . Vamos a modificar el test anterior para explicar con un ejemplo qu´ es lo que queremos: e § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 [ Test ] public void C o o r d i n a t e V a l i d a t i o n () { int arg1 = 10. Lo m´s elegante ser´ el patr´n Proxy6 o a ıa o para interceptar la llamada. upperLimit ). var validatorMock = MockRepository . validatorMock . Repeat .dofactory. Add ( arg1 . Repeat .com/Patterns/PatternDecorator. arg1 . upperLimit )). validatorMock . Add . Once (). calculatorMock . La m´s com´n ser´ que la clase coordia u ıa nadora implementase la misma interfaz de la calculadora y que tuviese una instancia de la calculadora internamente de manera que “envolviese” la llamada y le a˜adiese c´digo5 . lowerLimit . arg2 ). Una opci´n es Castle. Once (). aunque es suave nos desv´ de la materia que estamos tratando. GenerateStrictMock < BasicCalculator >(). por lo que ıa vamos a implementar nuestra propia forma de proxy.aspx# self1 o n http://es. SetLimits ( lowerLimit . calculatorMock . ValidateArgs ( arg1 . Expect ( x = > x .

Si no hay duplicidad y todos los “casos de uso” se gestionan mediante el proxy. _calcProxy = new CalcProxy ( _calculator ). Plantearlo a as´ nos supone el esfuerzo de mover tests de sitio. Por ejemplo los de suma y ı resta los quitar´ ıamos de la calculadora y los pondr´ ıamos en el proxy. Personalmente descarto esta opci´n. int result = _calcProxy . Add . entonces tiene que ser que est´ trabajando bien. result ). Finalmente verifie a camos que la ejecuci´n del proxy ha satisfecho las expectativas definidas.Cap´ ıtulo 8 ta ah´ hemos definido las expectativas. Pensar en este test y escribirlo me o ha ayudado a pensar en el dise˜o pero he ido demasiado lejos. Empecemos por implementar primero el test de suma en el proxy: § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [ TestFixture ] public class CalcProxyTests { private Calculator _calculator . 2). ya que no los vamos a tener por duplicado. la a calculadora y los l´ ımites m´ximos permitidos para las operaciones aritm´tia e cas. Si puedo evitar n los mocks en este punto mejor y como ninguna de las operaciones requeridas infringen las reglas de los tests unitarios. AreEqual (4 . private CalcProxy _calcProxy . } } ¦ ¥ Por cierto. El SUT m´ ımimo es: § 1 2 public class CalcProxy { 144 . el test es realmente fr´gil. Es como si quisi´ramos implemene tarlo. o ¿De qu´ manera podemos probar que el supuesto proxy colabora con e validador y calculadora sin usar mocks? Respuesta: Podemos ejercitar toda la funcionalidad de que disponemos a trav´s del proxy y fijarnos en que no e haya duplicidad. BinaryOperation ( _calculator . o ¿Complicado . Queremos que exista un m´todo BinaryOperation donde se indique e el m´todo de la calculadora a invocar y sus par´metros. Ahora decimos que hay un proxy ı (CalcProxy) que recibe como par´metros de su constructor al validador. 2 . Es momento de replantearse la situaci´n.no? Como vimos en el cap´ ıtulo anterior. Assert . voy a seguir utilizando validaci´n o de estado. hemos eliminado el test de suma del conjunto CalculatorTests (para no duplicar). Cuenta con a todo lujo de detalles lo que hace el SUT. De la clase Calculator he movido las propiedades de l´ ımite inferior y l´ ımite superior a una clase Validator junto con el m´todo e ValidateArgs por si en breve los reutilizase. [ Test ] public void Add () { _calculator = new Calculator ().

arg2 ). private CalcProxy _calcProxy . Adem´s en el cap´ a ıtulo 9 continuamos trabajando con TDD. Add ( arg1 . Seguramente su c´digo inıa o cluir´ un gran bloque switch-case para actuar en funci´n de ıa o las cadenas de texto y en alg´n momento pudiera ser que tuviese u que reescribir funciones pero al tener toda una bater´ de pruebas ıa detr´s. nos echa una mano cuando a o n tenemos que retroceder y enmendar una decisi´n problem´tica.Cap´ ıtulo 8 private BasicCalculator _calculator . } } 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ¦ ¥ He decidido que el primer par´metro del SUT es un delegado: a 1 public delegate int S i n g l e B i n a r y O p e r a t i o n ( int a . } public int BinaryOperation ( S i n g l e B i n a r y O p e r a t i o n operation . int b ). Ser´n los mismos ı a casos que en este cap´ ıtulo pero marcados por las particularidades de Python. Si la persona que se est´ enfrentando a estas decisiones tuviese a poca experiencia y hubiese decidido utilizar cadenas. int arg1 . o a $ % 145 . ' § 1 2 3 4 5 6 7 En el cap´ ıtulo 11 repetiremos la implementaci´n con TDD pero sobre Python. As´ aunque TDD no nos da siempre la respuesta a ı. a cu´l es la mejor decisi´n de dise˜o. public CalcProxy ( BasicCalculator calculator ) { _calculator = calculator . avanzando en la implementaci´n de la soluci´n. igualmente tendr´ muchas ventajas al usar TDD. o as´ que no se preocupe si algo no le queda del todo claro. [ SetUp ] & En lugar de pasar una funci´n como primer par´metro de o a BinaryOperation podr´ ıamos haber usado una cadena de texto (“Add”) pero la experiencia nos dice que las cadenas son fr´giles y a hacen el c´digo propenso a errores dif´ o ıciles de corregir y detectar. le dar´ mucha a ıan ıan m´s confianza. int arg2 ) { return _calculator . tales cambios ser´ menos peligrosos. o o Vamos a triangular el proxy trasladando el test de la resta hasta ´l: e [ TestFixture ] public class CalcProxyTests { private Calculator _calculator .

2 . Add . Assert . 2). foreach ( MethodInfo method in calcultatorMethods ) { if ( method == operation . Invoke ( _calculator . No vamos a escribir un bloque condicional para conseguir luz verde porque eso no triangula a ninguna parte. tan o 8 Reflection8 http://msdn. arg2 }). int arg2 ) { int result = 0. MethodInfo [] calcultatorMethods = _calculator . } [ Test ] public void Substract () { int result = _calcProxy . BinaryOperation ( _calculator .95). result ).reflection(VS.aspx 146 .microsoft. 3). BinaryOperation ( _calculator . Instance ).com/es-es/library/system. } } return result . Publi c | BindingFlags . AreEqual (4 . GetType (). } [ Test ] public void Add () { int result = _calcProxy . Una pregunta frecuente de quienes comienzan a aprender TDD es si los tests se pueden modificar. GetMethods ( BindingFlags . Method ) { result = ( int ) method . } } 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 ¦ ¥ Ya est´ m´s dif´ buscar el c´digo m´ a a ıcil o ınimo para que los dos tests pasen. Substract . Aqu´ estamos viendo claramente que s´ Se pueden ı ı. AreEqual (2 . int arg1 . result ). Estamos empezando a notar que reescribir no cuesta mucho cuando hacemos TDD. 5 . Los dos tests pasan y ya han e sido eliminados del conjunto en que se encontraban inicialmente. } ¦ ¥ Hemos usado un poco de magia para buscar din´micamente el a m´todo de la clase calculadora que toca invocar. _calcProxy = new CalcProxy ( _calculator ). new Object [] { arg1 . Assert .Cap´ ıtulo 8 public void SetUp () { _calculator = new Calculator (). Es hora de implementar algo m´s serio. modificar tantas veces como haga falta porque un test es c´digo vivo. a § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public int BinaryOperation ( S i n g l e B i n a r y O p e r a t i o n operation .

_calcProxy = new CalcProxy ( _calculator ). Substract . 2 . } [ Test ] public void A d d W i t h D i f f e r e n t A r g u m e n t s () { int result = _calcProxy .Cap´ ıtulo 8 importante como el SUT. 5). 2 . 3 . result ). Add . } [ Test ] public void S u b s t r a c t R e t u r n i n g N e g a t i v e () { int result = _calcProxy . AreEqual (7 . 5). Al menos es inamovible hasta la siguiente reuni´n de fin de sprint con el cliente (sprint si usamos Scrum). Assert . BinaryOperation ( _calculator . } } ¦ ¥ Perfecto. private CalcProxy _calcProxy . todos pasan estupendamente con un esfuerzo m´ ınimo. } [ Test ] public void Add () { int result = _calcProxy . Lo unico inamovible es el test de aceptaci´n porque ´ o ha sido definido por el cliente. [ SetUp ] public void SetUp () { _calculator = new Calculator (). result ). BinaryOperation ( _calculator . BinaryOperation ( _calculator . AreEqual (4 . AreEqual ( -2 . 3). Substract . } [ Test ] public void Substract () { int result = _calcProxy . Add . AreEqual (2 . BinaryOperation ( _calculator . Assert . 2). result ). result ). Assert . Repasemos la libreta: 147 . 5 . Terminemos o de mover los tests de calculadora al proxy: § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 [ TestFixture ] public class CalcProxyTests { private Calculator _calculator . Assert .

"2 + 2".Cap´ ıtulo 8 # Aceptaci´n . No vamos a implementar el validador con su propio conjunto de tests para luego moverlos al proxy sino que ya con las ideas claras y el dise˜o m´s definido podemos ejercitar el SUT desde el proxy: n a § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 [ Test ] public void A r g u m e n t s E x c e e d L i m i t s () { CalcProxy calcProxyWithLimits = new CalcProxy ( new Validator ( -10 . 30 . Fail ( " This should fail as arguments exceed both limits " ).2 : produce ERROR o # Aceptaci´n .L´mite Superior =500 o ı # Aceptaci´n ."5 + 4 * 2 / 2"."*45-2-": produce ERROR o # Aceptaci´n . _calculator )."* 4 5 . ’2’ y ’+’ u # Aceptaci´n . Assert .L´mite Superior =100 o ı # Aceptaci´n . Add .L´mite Inferior = -10 o ı A: El primer argumento sobrepasa el l´mite superior ı B: El primer argumento sobrepasa el l´mite inferior ı C: El segundo argumento sobrepasa el l´mite superior ı D: El segundo argumento sobrepasa el l´mite inferior ı E: El resultado de una operaci´n sobrepasa el l´mite superior o ı F: El resultado de una operaci´n sobrepasa el l´mite inferior o ı Todos los casos de uso anteriores se aplican a todas las operaciones aritm´ticas e Hab´ ıamos llegado a este punto para coordinar la validaci´n de argumentos o y resultados."2 + -2"devuelve 0 o # Aceptaci´n . 10) .2": produce ERROR o # Aceptaci´n ."3 / 2". try { _calcProxy .2": produce ERROR o # Aceptaci´n . 50). devuelve 4 o La cadena "2 + 2"tiene dos n´meros y un operador que son ’2’. produce ERROR o # Aceptaci´n . BinaryOperation ( _calculator . this works } } ¦ ¥ He decidido que el proxy tiene un constructor que recibe al validador y a la 148 ."* 4 5 . } catch ( OverflowException ) { // Ok . devuelve 9 o # Aceptaci´n ."* * 4 .L´mite Inferior = -1000 o ı # Aceptaci´n .

} § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ¦ ¥ § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 El m´todo simplemente a˜ade una l´ e n ınea al c´digo anterior. foreach ( MethodInfo method in calcultatorMethods ) { if ( method == operation . } } return result . int upper ) 149 . GetMethods ( BindingFlags . } } public void ValidateArgs ( int arg1 . Instance ). Publi c | BindingFlags . } set { _lowerLimit = value . } } public int UpperLimit { get { return _upperLimit . GetType (). int arg2 ) { if ( arg1 > _upperLimit ) throw new OverflowException ( " ERROR " ). if ( arg2 > _upperLimit ) throw new OverflowException ( " ERROR " ). Para que el test o pase rescatamos el m´todo de validaci´n que ten´ e o ıamos guardado en el validador. int result = 0. int arg2 ) { _validator . Invoke ( _calculator . public Validator ( int lowerLimit . int arg1 . int upperLimit ) { SetLimits ( lowerLimit . upperLimit ). ValidateArgs ( arg1 . arg2 ). El ıa SUT: public int BinaryOperation ( S i n g l e B i n a r y O p e r a t i o n operation . new Object [] { arg1 . private int _lowerLimit . MethodInfo [] calcultatorMethods = _calculator . Al validador se le indican los valores l´ ımite v´ constructor. Method ) { result = ( int ) method . } set { _upperLimit = value . arg2 }). } public int LowerLimit { get { return _lowerLimit .Cap´ ıtulo 8 calculadora. } public void SetLimits ( int lower . public class Validator : LimitsValidator { private int _upperLimit .

public Validator ( int lowerLimit . } catch ( OverflowException ) { // Ok . } } public int UpperLimit { get { return _upperLimit . [ Test ] public void A r g u m e n t s E x c e e d L i m i t s I n v e r s e () { CalcProxy calcProxyWithLimits = new CalcProxy ( new Validator ( -10 . } } 32 33 34 35 36 ¦ § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ¥ Nos queda probar el l´ ımite inferior. } private void breakIfOverflow ( int arg . -30 . 10) . string msg ) { 150 . } } public void ValidateArgs ( int arg1 . int arg2 ) { breakIfOverflow ( arg1 . _calculator ). breakIfOverflow ( arg2 . Add . try { calcProxyWithLimits . } public int LowerLimit { get { return _lowerLimit . } set { _lowerLimit = value . } set { _upperLimit = value . upperLimit ). Assert .Cap´ ıtulo 8 { _lowerLimit = lower . this works } } ¦ § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 ¥ El SUT junto con su posterior refactoring: public class Validator : LimitsValidator { private int _upperLimit . Fail ( " This should fail as arguments exceed both limits " ). " First argument exceeds limits " ). BinaryOperation ( _calculator . -50). _upperLimit = upper . int upperLimit ) { SetLimits ( lowerLimit . " Second argument exceeds limit s " ). private int _lowerLimit .

int upper ) { _lowerLimit = lower . devuelve 9 o # Aceptaci´n . Fail ( " This should fail as result exceed upper limit " )."*45-2-": produce ERROR o # Aceptaci´n .2": produce ERROR o # Aceptaci´n ."* * 4 ."* 4 5 . produce ERROR o # Aceptaci´n ."5 + 4 * 2 / 2". Los dos ejemplos y su implementaci´n o son inmediatos. Pero siempre de uno en uno: § 1 2 3 4 5 6 7 8 9 10 11 [ Test ] public void V a l i d a t e R e s u l t E x c e e d i n g U p p e r L i m i t () { try { _ c a l c P r o x y W i t h L i m i t s . return false . } catch ( OverflowException ) 151 . Add ."3 / 2". } } 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 ¦ ¥ Ya podemos quitar de la libreta unas cuantas l´ ıneas: # Aceptaci´n . BinaryOperation ( _calculator . ’2’ y ’+’ u # Aceptaci´n ."* 4 5 ."2 + 2".2": produce ERROR o # Aceptaci´n . 10). if ( arg < _lowerLimit ) return true . Assert . _upperLimit = upper ."2 + -2"devuelve 0 o E: El resultado de una operaci´n sobrepasa el l´mite superior o ı F: El resultado de una operaci´n sobrepasa el l´mite inferior o ı Todos los casos de uso anteriores se aplican a todas las operaciones aritm´ticas e Solo nos queda validar el resultado. devuelve 4 o La cadena "2 + 2"tiene dos n´meros y un operador que son ’2’. } public void SetLimits ( int lower . 10 .Cap´ ıtulo 8 if ( ValueExceedLimits ( arg )) throw new OverflowException ( msg ).2 : produce ERROR o # Aceptaci´n . } public bool ValueExceedLimits ( int arg ) { if ( arg > _upperLimit ) return true .

Fail ( " This should fail as result exceed lower limit " ). } ¦ ¥ Le hemos a˜adido una l´ n ınea al m´todo para validar el resultado. GetMethods ( BindingFlags . ValidateResult ( result ). ValidateArgs ( arg1 . foreach ( MethodInfo method in calcultatorMethods ) { if ( method == operation . Method ) { result = ( int ) method .1: CalcProxy public int BinaryOperation ( S i n g l e B i n a r y O p e r a t i o n operation . Invoke ( _calculator . GetType (). BinaryOperation ( _calculator . -20 . int result = 0. -1). } catch ( OverflowException ) { // Ok . } ¦ § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ¥ [ Test ] public void V a l i d a t e R e s u l t E x c e e d i n g L o w e r L i m i t () { try { _ c a l c P r o x y W i t h L i m i t s . Instance ). arg2 }).Cap´ ıtulo 8 { // Ok . } } _validator . this works } } ¦ ¥ Para este ultimo test ni siquiera ha hecho falta tocar el SUT. MethodInfo [] calcultatorMethods = _calculator . " Result exceeds limits " ). Assert . Publi c | BindingFlags . La libreta queda ´ as´ ı: 152 . El resto del e SUT en el validador: § 1 2 3 4 8. int arg2 ) { _validator . arg2 ). int arg1 . new Object [] { arg1 .2: Validator public void ValidateResult ( int result ) { breakIfOverflow ( result . Add . return result . this works } } 12 13 14 15 ¦ § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ¥ 8.

AreEqual (4 . devuelve 4 o La cadena "2 + 2"tiene dos n´meros y un operador que son ’2’. Add .2 : produce ERROR o # Aceptaci´n . _calculator ). CalcProxyTests. Mocks . BinaryOperation ( _calculator . 2)."*45-2-": produce ERROR o # Aceptaci´n . _calculator ). private CalcProxy _ c a l c P r o x y W i t h L i m i t s ."* 4 5 . _calcProxy = new CalcProxy ( new Validator ( -100 . 2 . Text . 100) .2": produce ERROR o # Aceptaci´n . 10) . produce ERROR o # Aceptaci´n . Generic . § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 using using using using using using System . [ SetUp ] public void SetUp () { _calculator = new Calculator (). } [ Test ] public void Add () { int result = _calcProxy . _calcProxyWithLimits = new CalcProxy ( new Validator ( -10 . f r a m e w o r k dll is r e q u i r e d SuperCalculator . } [ Test ] 153 ."2 + 2"."3 / 2"."5 + 4 * 2 / 2". System ."* 4 5 . result ).Cap´ ıtulo 8 # Aceptaci´n ."* * 4 ."2 + -2"devuelve 0 o Para recapitular un poco veamos de nuevo todos los tests que hemos escrito hasta el momento. ’2’ y ’+’ u # Aceptaci´n . namespace UnitTests { [ TestFixture ] public class CalcProxyTests { private Calculator _calculator . Assert . Rhino .2": produce ERROR o # Aceptaci´n . private CalcProxy _calcProxy . System . devuelve 9 o # Aceptaci´n . // only nunit . que han quedado bajo el mismo conjunto de tests. NUnit . Collections . Al final no hemos utilizado ning´n doble de test y todos u son tests unitarios puesto que cumplen todas sus reglas. Framework .

this works } } [ Test ] public void A r g u m e n t s E x c e e d L i m i t s I n v e r s e () { try { _ c a l c P r o x y W i t h L i m i t s . -50). result ). 50). Add . Assert . 3). 5). BinaryOperation ( _calculator . Assert . result ). Substract . 30 . Assert . } [ Test ] public void A d d W i t h D i f f e r e n t A r g u m e n t s () { int result = _calcProxy . } [ Test ] public void A r g u m e n t s E x c e e d L i m i t s () { try { _ c a l c P r o x y W i t h L i m i t s . this works } } [ Test ] public void V a l i d a t e R e s u l t E x c e e d i n g U p p e r L i m i t () 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 154 . 3 . BinaryOperation ( _calculator . BinaryOperation ( _calculator . AreEqual ( -2 . } catch ( OverflowException ) { // Ok . BinaryOperation ( _calculator . -30 . result ). 5 . Substract . AreEqual (7 . 5). AreEqual (2 . Assert . } [ Test ] public void S u b s t r a c t R e t u r n i n g N e g a t i v e () { int result = _calcProxy . Add . } catch ( OverflowException ) { // Ok . Assert . BinaryOperation ( _calculator . Fail ( " This should fail as arguments exceed both limits " ).Cap´ ıtulo 8 public void Substract () { int result = _calcProxy . Add . 2 . Fail ( " This should fail as arguments exceed both limits " ).

Fail ( " This should fail as result exceed upper limit " ). En el pr´ximo cap´ o ıtulo continuaremos el desarrollo de la Supercalculadora con C#. this works } } } } 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 ¦ ¥ Es un buen momento para hacer un “commit” en el sistema de control de versiones y cerrar el cap´ ıtulo. Add . En el cap´ ıtulo 11 implementaremos lo mismo desde el inicio con Python. BinaryOperation ( _calculator . 10). -20 . En pr´ximos cap´ o ıtulos podr´ ıamos hacer modificaciones sobre las clases actuales. } catch ( OverflowException ) { // Ok . -1). para que lo pueda revisar si lo desea. para seguir profundizando en la t´cnica del dise˜o dirigido por e n ejemplos o TDD. Fail ( " This should fail as result exceed upper limit " ). } catch ( OverflowException ) { // Ok . 10 . 155 . this works } } [ Test ] public void V a l i d a t e R e s u l t E x c e e d i n g L o w e r L i m i t () { try { _ c a l c P r o x y W i t h L i m i t s . BinaryOperation ( _calculator .Cap´ ıtulo 8 { try { _ c a l c P r o x y W i t h L i m i t s . Assert . Add . Puede encontrar un archivo comprimido con el estado actual del proyecto en la web. Assert . por eso el archivo contiene expresamente la versi´n que hemos desarrollado o hasta aqu´ ı.

Cap´ ıtulo 8 156 .

"2 + 2"."5 + 4 * 2 / 2"."* 4 5 . Continuamos el desarrollo por donde lo dejamos atendiendo a lo que pone la libreta: # Aceptaci´n . Empezamos a dise˜ar partiendo de un ejemplo.2": produce ERROR o # Aceptaci´n . devuelve 0 o Es el momento de evaluar la cadena de texto que se utiliza para introducir expresiones y conectarla con la funcionalidad que ya tenemos.2": produce ERROR o # Aceptaci´n . GetTokens ( " 2 + 2 " ). como siempre: n § 1 2 3 4 5 6 7 8 [ TestFixture ] public class ParserTests { [ Test ] public void GetTokens () { MathParser parser = new MathParser (). 157 . devuelve 4 o La cadena "2 + 2"tiene dos n´meros y un operador que son ’2’."* 4 5 .Cap´tulo ı 9 Continuaci´n del proyecto .2 : produce ERROR o # Aceptaci´n ."2 + -2". devuelve 9 o # Aceptaci´n . List < MathToken > tokens = parser .Test o Unitarios En el ultimo cap´ ´ ıtulo llegamos a conseguir que nuestra calculadora sumase y restase teniendo en cuenta los valores l´ ımite de los par´metros y del a resultado. ’2’ y ’+’ u # Aceptaci´n ."* * 4 ."*45-2-": produce ERROR o # Aceptaci´n . produce ERROR o # Aceptaci´n ."3 / 2".

La experiencia me dice que devolver cadenas no me har´ progresar mucho. Adem´s sabemos que en caso de ene a a contrar una cadena incorrecta lanzaremos una excepci´n. Add ( new MathToken ( " 2 " )). AreEqual ( " 2 " . Assert . TDD es ideal para dise˜ar expresiones regulares n si mantenemos la m´xima de escribir un test exclusivo para cada posible caa dena v´lida. Tales objetos todav´ no existen pero ıa prefiero pensar en la expresi´n como en una lista de objetos en lugar de o una lista de cadenas. Afortunadamente las respuestas ya se encuentran en la libreta: sabemos qu´ expresiones son v´lidas y cuales no. Token ). IsExpressionValid ( expression ). tokens . Token ). Vamos con el ejemplo que construir´ la primera versi´n de la a a o expresi´n regular: o § 1 2 3 4 5 [ Test ] public void V a l i d a t e M o s t S i m p l e E x p r e s s i o n () { string expression = " 2 + 2 " . Token ). Assert . AreEqual ( " + " . Add ( new MathToken ( " 2 " )).Cap´ ıtulo 9 9 10 11 12 13 14 15 Assert . vamos a triangular paso a paso. En lugar de construir de una vez la expresi´n regular que valio da todo tipo de expresiones matem´ticas. o a o o nos resultara mas sencilla. su modificaci´n y sofisticaci´n. 158 . Add ( new MathToken ( " + " )). bool result = _parser . m´s tarde. return tokens . tokens . La implementaci´n m´ a o ınima para alcanzar verde: § 1 2 3 4 5 6 7 8 9 10 11 12 13 public class MathParser { public List < MathToken > GetTokens ( string expression ) { List < MathToken > tokens = new List < MathToken >(). AreEqual ( " 2 " . } } ¦ ¥ La simplicidad de este SUT nos sirve para traer varias preguntas a la mente. Assert . Podr´ o ıamos triangular hacia el reconocimiento de las expresiones con sentencias y bloques de c´digo varios pero las expresiones regulares son la mejor opci´n llegado o o este punto. tokens [2]. tokens . tokens [1]. tokens [0]. AreEqual (3 . tokens . Si o ıas construimos la expresi´n bas´ndonos en peque˜os ejemplos que vayan casano a n do con cada subexpresi´n regular. a Una expresi´n regular compleja nos puede llevar d´ de trabajo depurando. Count ). } } ¦ ¥ Acabo de tomar varias decisiones de dise˜o: MathParser es una clase con n un m´todo GetTokens que recibe la expresi´n como una cadena y devuelve e o una lista de objetos tipo MathToken.

" Failure with operator : " + operatorChar ). o asumir el riesgo para agrupar tests de una forma ordenada. IsTrue ( _parser .Cap´ ıtulo 9 6 7 8 Assert . o En vez de retornar verdadero directamente podemos permitirnos construir la expresi´n regular m´s sencilla que resuelve el ejemplo: o a § 1 2 3 4 5 9.1: MathParser public bool IsExpressionValid ( string expression ) { Regex regex = new Regex ( @ " \ d \+ \ d " ). Empty . Fij´se que en el e Assert he a˜adido una explicaci´n para que sea m´s sencilla la depuraci´n n o a o de bugs. IsExpressionValid ( expression ) . Assert . foreach ( char operatorChar in operators ) { expression = " 2 " + operatorChar + " 2 " . } ¦ ¥ En lugar de un m´todo void me ha parecido mejor idea que devuelva verdae dero o falso para facilitar la implementaci´n de los tests. string expression = String . Ahora vamos a probar con los cuatro operadores aritm´ticos. } ¦ § 1 2 3 4 5 6 7 8 ¥ ¿Qu´ tal se comporta con n´meros de m´s de una cifra? e u a [ Test ] public void V a l i d a t e M o r e T h a n O n e D i g i t E x p r e s s i o n () { string expression = " 25 + 287 " . IsExpressionValid ( expression ). Eso nos da permiso para agrupar los cuatro usos en un solo ejemplo: [ Test ] public void V a l i d a t e S i m p l e E x p r e s s i o n W i t h A l l O p e r a t o r s () { string operators = " + -*/ " . 159 . IsTrue ( result ). Assert . } ¦ ¥ § 1 2 3 4 5 6 7 8 9 10 11 12 13 ¡Funciona! No hemos tenido que modificar el SUT. En lugar de hacer cuatro tests nos e damos cuenta de que la expresi´n que queremos probar es la misma. IsMatch ( expression ). return regex . } } ¦ ¥ El peligro de este ejemplo es que estemos construyendo mal la cadena. Despu´s de escribirlo la he mostrado por e consola para asegurarme que era la que quer´ En mi opini´n merece la pena ıa. bool result = _parser . aunque o variando el operador. en cuyo caso dise˜ar´ n ıamos mal el SUT. IsTrue ( result ).

Assert . IsExpressionValid ( expression ).2": produce ERROR o # Aceptaci´n . Es importante a ´ recordar que el c´digo de los tests es tan importante como el del SUT y que o por tanto debemos cuidarlo y mantenerlo. IsMatch ( expression ). produce ERROR o # Aceptaci´n . devuelve 0 o § 1 2 3 4 5 6 7 De acuerdo."*45-2-": produce ERROR o # Aceptaci´n . nos confirman que s´ es posible y o ı anotamos en la libreta. } ¦ ¥ 160 . bool result = _parser .."3 / 2"."2 + -2". devuelve 4 o La cadena "2 + 2"tiene dos n´meros y un operador que son ’2’. ¿podr´ haber varios espacios entre los distintos a elementos de la expresi´n? Preguntamos. devuelve 9 o # Aceptaci´n . prob´moslo: e [ Test ] public void ValidateWithSpaces () { string expression = " 2 + 287 " ."* 4 5 . IsTrue ( result ). o § 1 2 3 4 5 6 9.3: MathParser public bool IsExpressionValid ( string expression ) { Regex regex = new Regex ( @ " \ d \ s +[+|\ -|/|*]\ s +\ d " )."5 + 4 * 2 / 2".2 : produce ERROR o # Aceptaci´n ."* * 4 . } ¦ ¥ Mejoramos la expresi´n regular: o § 1 2 3 4 5 6 9. Me asalta una duda. Podemos eliminar el primero que hab´ ıamos escrito (ValidateMostSimpleExpression) ya que est´ contenido en el ultimo."* 4 5 . } ¦ ¥ El test pasa.. # Aceptaci´n . ’2’ y ’+’ u Se permiten varios espacios entre s´mbolos ı # Aceptaci´n . return regex . return regex ."2 + 2".2": produce ERROR o # Aceptaci´n .Cap´ ıtulo 9 Incrementamos la expresi´n regular para hacer el test pasar. IsMatch ( expression ).2: MathParser public bool IsExpressionValid ( string expression ) { Regex regex = new Regex ( @ " \ d [+|\ -|/|*] \ d " ).

Escogemos nuevas ı. o a Busquemos un test que nos obligue a modificar la expresi´n regular: o [ Test ] public void V a l i d a t e C o m p l e x W r o n g E x p r e s s i o n () { string expression = " 2 + 7 a 2 b 4 " . IsMatch ( expression . IsExpressionValid ( expression ). 0). esta pasa incluso sin haber modificado la expresi´n regular. } ¦ ¥ Algunos tests que antes funcionaban est´n fallando. expresiones de la libreta: [ Test ] public void V a l i d a t e C o m p l e x E x p r e s s i o n () { string expression = " 2 + 7 .2 * 4 " . IsMatch ( expression . nos la est´ dando por buena. return regex .4: MathParser public bool IsExpressionValid ( string expression ) { Regex regex = new Regex ( @ " ^\ d ((\ s +)[+|\ -|/|*](\ s +)\ d )+ $ " ). Assert . bool result = _parser . Assert . Vamos a retocar m´s la a a expresi´n regular: o § 1 2 3 4 5 6 7 9. IsExpressionValid ( expression ). } ¦ § 1 2 3 4 5 6 7 ¥ 9. IsExpressionValid ( expression ). IsTrue ( result ).5: MathParser public bool IsExpressionValid ( string expression ) { Regex regex = new Regex ( @ " ^\ d +((\ s +)[+|\ -|/|*](\ s +)\ d +)+ $ " ). como una subcadena de la expresi´n casa. } ¦ ¥ 161 . } § 1 2 3 4 5 6 7 ¦ ¥ § 1 2 3 4 5 6 7 8 Pues s´ funciona sin que tengamos que tocar el SUT. IsFalse ( result ). Resulta o que. bool result = _parser .Cap´ ıtulo 9 ¿Estar´ cubierto el caso en que no se dejan espacios? a [ Test ] public void V a l i d a t e F a i l s N o S p a c e s () { string expression = " 2+7 " . IsFalse ( result ). 0). } ¦ ¥ § 1 2 3 4 5 6 7 8 Vaya. bool result = _parser . Assert . return regex .

1}\ d +)+ $ " ). Por cierto. Assert .2 a 3 b " .1}\ d +((\ s +)[+|\ -|/|*](\ s +) -{0 . IsExpressionValid ( expression ). bool result = _parser . hay que retocar la expresi´n regular. a o § 1 2 3 4 5 6 9. IsExpressionValid ( expression ). est´ en rojo. IsExpressionValid ( " -7 + 1 " )). } § 1 2 3 4 5 6 7 8 ¦ § 1 2 3 4 5 6 7 8 ¥ El test pasa. Assert .6: MathParser public bool IsExpressionValid ( string expression ) { Regex regex = new Regex ( @ " ^ -{0 . ¿Qu´ tal con dos operadores consecutivos? e e [ Test ] public void V a l i d a t e W i t h S e v e r a l O p e r a t o r s T o g e t h e r () { string expression = " + + 7 " . IsMatch ( expression . luz verde. 0). Assert . return regex . IsFalse ( result ). IsExpressionValid ( expression ).Cap´ ıtulo 9 El hecho de que algunos otros tests se hubieran roto me ha creado cierta desconfianza. o [ Test ] public void V a l i d a t e S i m p l e W r o n g E x p r e s s i o n () { string expression = " 2 a7 " . } ¦ ¥ He aprovechado para simplificar el test sin que pierda legibilidad. IsFalse ( result ). } ¦ ¥ § 1 2 3 4 5 Correcto. La expresi´n que nos queda por probar de las que tiene o la libreta es aquella que contiene n´meros negativos: u [ Test ] public void V a l i d a t e W i t h N e g a t i v e N u m e r s () { Assert . } ¦ § 1 2 3 4 5 6 7 8 ¥ Tambi´n funciona. bool result = _parser . } ¦ ¥ Funciona. IsFalse ( result ). bool result = _parser . Probemos alguna variante: 162 . Vamos a probar unas cuantas expresiones m´s para verificar a que nuestra validaci´n es buena. IsTrue ( _parser . A por otro caso: [ Test ] public void V a l i d a t e W r o n g E x p r e s s i o n W i t h V a l i d S u b e x p r e s s i o n () { string expression = " 2 + 7 .

-1 " )). Assert . } ¦ ¥ N´tese que no repetimos las afirmaciones referentes a los tokens 0."* 4 5 .-1 * 2 / 3 + -5 " )).Cap´ ıtulo 9 § 1 2 3 4 5 [ Test ] public void V a l i d a t e W i t h N e g a t i v e N u m e r s A t T h e E n d () { Assert . Hab´ o ıamos hecho pasar un test con una implementaci´n m´ o ınima pero no llegamos a triangular: [ Test ] public void G e t T o k e n s L o n g E x p r e s s i o n () { List < MathToken > tokens = _parser ."2 + -2". tokens . GetTokens ( " 2 . } ¦ ¥ Me da la sensaci´n de que nuestro validador de expresiones ya es suficienteo mente robusto. tokens [4]."* * 4 . tokens [3]. ´ [ Test ] public void V a l i d a t e S u p e r C o m p l e x E x p r e s s i o n () { Assert ."2 + 2". AreEqual ( " 3 " . 1 y 2 que o ya se hicieron en el test anterior para una expresi´n que es casi igual a la o actual. Vamos a por la ultima prueba del validador de expresiones. § 9. produce ERROR o # Aceptaci´n .2 : produce ERROR o # Aceptaci´n . IsExpressionValid ( " 7 ."*45-2-": produce ERROR o # Aceptaci´n . Token ). AreEqual (5 . Count ). devuelve 9 o # Aceptaci´n . IsTrue ( _parser ."3 / 2".1 + 3 " ). Contiene toda la funcionalidad que necesitamos por ahora.7: MathParser 163 . IsExpressionValid ( " -7 . IsTrue ( _parser . Assert .2": produce ERROR o # Aceptaci´n . ¿D´nde est´bamos? o a # Aceptaci´n . devuelve 4 o La cadena "2 + 2"tiene dos n´meros y un u operador que son ’2’."5 + 4 * 2 / 2". Token ). Assert . devuelve 0 o § 1 2 3 4 5 6 7 8 9 ¡Ah si!."* 4 5 . le est´bamos pidiendo al analizador que nos devolviera una lista a con los elementos de la expresi´n. ’2’ y ’+’ # Aceptaci´n . AreEqual ( " + " .2": produce ERROR o # Aceptaci´n . } ¦ § 1 2 3 4 5 6 ¥ Sigue funcionando.

Add ( new MathToken ( item )). Split ( ’ ’ ). } return tokens . Los he dejado dentro del e o a conjunto de tests ParserTests aunque ahora se ha quedado vac´ la clase ıa Parser. foreach ( String item in items ) { tokens . String [] items = expression .1}\ d +((\ s +)[+|\ -|/|*](\ s +) -{0 .1}\ d +)+ $ " ). } 1 2 3 4 5 6 7 8 9 10 ¦ ¥ Tengo la sensaci´n de que la clase Parser empieza a tener demasiadas reso ponsabilidades. Refactoricemos: § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class MathLexer { public List < MathToken > GetTokens ( string expression ) { List < MathToken > tokens = new List < MathToken >(). Escribamos un ejemplo que lo provoque: o § 1 2 3 4 5 [ Test ] public void G e t T o k e n s W r o n g E x p r e s s i o n () { try { 164 . } } public class MathParser { } ¦ ¥ Hemos tenido que renombrar algunas variables en los tests para que pasen despu´s de esta refactorizaci´n pero ha sido r´pido. Add ( new MathToken ( item )). Split ( ’ ’ ). } } public class ExpressionValidator { public bool IsExpressionValid ( string expression ) { Regex regex = new Regex ( @ " ^ -{0 . 0). String [] items = expression . foreach ( String item in items ) { tokens . La libreta dice que ante una expresi´n inv´lida el analizador producir´ una o a a excepci´n. } return tokens . return regex . IsMatch ( expression .Cap´ ıtulo 9 public List < MathToken > GetTokens ( string expression ) { List < MathToken > tokens = new List < MathToken >().

AreEqual ( " 88 " . Assert . } return tokens . List < MathToken > tokens = new List < MathToken >(). GetTokens ( " 2 . foreach ( String item in items ) { tokens . IsExpressionValid ( expression )) throw new I n v a l i d O p e r a t i o n E x c e p t i o n ( expression ). GetTokens ( " 5 Assert . } public List < MathToken > GetTokens ( string expression ) { if (! _validator . Inyectemos el validador: public class MathLexer { ExpressionValidator _validator . tokens [0]. Add ( new MathToken ( item )).1++ 3 " ). String [] items = expression . Assert . Assert . } catch ( I n v a l i d O p e r a t i o n E x c e p t i o n ) { } } 6 7 8 9 10 11 ¦ ¥ § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Nos hemos decantado por InvalidOperationException. tokens [2]. public MathLexer ( ExpressionValidator validator ) { _validator = validator . tokens [1]. Token ). Deber´ ıamos poder partir por cualquier car´cter en blanco: a § 1 2 3 4 5 9. AreEqual ( " -" . Token ). ¦ ¥ Pues resulta que no funciona. } } ¦ ¥ § 1 2 3 4 5 6 7 8 ¿Se crear´ bien la lista de tokens cuando haya varios espacios seguidos? Mejor a lo apuntalamos con un test: [ Test ] public void GetTokensWithSpaces () { List < MathToken > tokens = _lexer .Cap´ ıtulo 9 List < MathToken > tokens = _lexer . Token ). IsExpressionValid ( expression )) throw new I n v a l i d O p e r a t i o n E x c e p t i o n ( expression ). Luz roja. } 88 " ).8: MathLexer public List < MathToken > GetTokens ( string expression ) { if (! _validator . 165 . AreEqual ( " 5 " . Split ( ’ ’ ). Ahora podr´ ıamos escribir un “hack” veloz y triangular pero es un poco absurdo teniendo ya un validador de expresiones. Fail ( " Exception did not arise ! " ).

"5 + 4 * 2 / 2". devuelve 4 o # Aceptaci´n ."2 + 2". Por una lado sabemos que somos capaces de sumar y por otro ya conseguimos la lista de tokens de la expresi´n. ’\ t ’ }) . string [] items = splitExpression ( expression ). } private List < MathToken > c r e a t e T o k e n s F r o m S t r i n g s ( strin g [] items ) { List < MathToken > tokens = new List < MathToken >(). Refactorizo un poco: § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 9. } return tokens . return c r e a t e T o k e n s F r o m S t r i n g s ( items ). ıa 166 . Add ( new MathToken ( item )). foreach ( String item in items ) { tokens . devuelve 0 o El primer test de aceptaci´n de la lista nos exige comenzar a unir las o distintas piezas que hemos ido creando. StringSplitOptions . isExpressionValid ( expression )) throw new I n v a l i d O p e r a t i o n E x c e p t i o n ( expression ).Cap´ ıtulo 9 List < MathToken > tokens = new List < MathToken >(). } return tokens . Split (( new char [] { ’ ’ . String [] items = expression . Add ( new MathToken ( item )). foreach ( String item in items ) { tokens . RemoveEmptyEntries )."2 + -2"."3 / 2". RemoveEmptyEntries ). Aunque voy a intentar que cumpla con algunas normas de los tests unitarios (inocuo y r´pido) no es at´mico as´ que no me a o ı atrever´ a llamarle unitario sino simplemente funcional. } private string [] splitExpression ( string expression ) { return expression . ’\ t ’}) . En este caso concreto el test de aceptaci´n lo o podemos expresar con NUnit. produce ERROR o # Aceptaci´n . devuelve 9 o # Aceptaci´n . StringSplitOptions . } ¦ ¥ Limpiemos la lista para ver qu´ toca ahora: e # Aceptaci´n . } 6 7 8 9 10 11 12 13 14 ¦ ¥ OK luz verde.9: MathLexer public List < MathToken > GetTokens ( string expression ) { if (! _validator . Split (( new char [] { ’ ’ . o Queda conectar ambas cosas.

Cap´ ıtulo 9 § 1 2 3 4 5 6 [ Test ] public void P r o c e s s S i m p l e E x p r e s s i o n () { MathParser parser = new MathParser (). Y seguro que aparece pronto. AreEqual (4 . public MathParser ( CalculatorProxy calcProxy ) { _calcProxy = calcProxy . } ¦ ¥ Antes de implementar el SUT m´ ınimo. } } ¦ ¥ 167 . ProcessExpression ( " 2 + 2 " )). MathParser parser = new MathParser ( calcProxyMock ). BinaryOperation ( _calculator . Add . parser . parser . Calculator . } ¦ ¥ Para escribir el test tuve que extraer la interfaz CalculatorProxy a partir de la clase CalcProxy. Assert . BinayOperation ( _calcProxy . 2). Siento que me gustar´ que Calculator estuviese mejor ıa encapsulado dentro del proxy. me parece buena idea escribir un par de tests unitarios que fuercen la colaboraci´n entre los objetos que teo nemos. Return (4). 2 . Expect ( x = > x . calcProxyMock . V e r i f y A l l E x p e c t a t i o n s (). Add . calcProxyMock . 2)). Es algo que tengo en mente arreglar tan pronto como un requisito me lo pida. } public int ProcessExpression ( string expression ) { return _calcProxy . Return ( _calculator ). Calculator ). ProcessExpression ( " 2 + 2 " ). La intenci´n es forzar la colaboraci´n. Conseguimos el verde r´pido: a § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class MathParser { CalculatorProxy _calcProxy . calcProxyMock . As´ no se me olvida utilizarlos cuando me adentre en detalles de ı implementaci´n: o § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 [ Test ] public void P a r s e r W o r k s W i t h C a l c P r o x y () { CalculatorProxy calcProxyMock = MockRepository . No me gusta o o tener que ser tan expl´ ıcito al definir la llamada a la propiedad Calculator del proxy en la l´ ınea 6. 2 . Expect ( x = > x . GenerateMock < CalculatorProxy >().

IntValue . Add ( new MathToken ( " 2 " )). ProcessExpression ( " 3 + 1 + 2 " )). public MathParser ( Lexer lexer . CalculatorProxy calcProx y ) { _lexer = lexer . Calculator . new Calculator ())). Return ( tokens ). GetTokens ( " 2 + 2 " )). tokens . GetTokens ( expression ). lexerMock . Lexer lexerMock = MockRepository . tokens [0]. El SUT va tomando forma: public class MathParser { Lexer _lexer . tokens [2]. MathParser parser = new MathParser ( lexerMock . Expect ( x = > x . ProcessExpression ( " 2 + 2 " ). new CalcProxy ( new Validator ( -100 . Add ( new MathToken ( " + " )). _parser . 100) . Add . a ´ [ Test ] public void P r o c e s s E x p r e s s i o n 2 O p e r a t o r s () { Assert . CalculatorProxy _calcProxy . parser . } } ¦ ¥ § 1 2 3 4 5 6 Modifiqu´ el test anterior ya que el constructor ha cambiado. GenerateStrictMock < Lexer >(). lexerMock . } ¦ ¥ Voy a implementar un c´digo m´ o ınimo que resuelva la operaci´n procesando o la entrada de izquierda a derecha: 168 . BinaryOperation ( _calcProxy . IntValue ). V e r i f y A l l E x p e c t a t i o n s (). } public int ProcessExpression ( string expression ) { List < MathToken > tokens = _lexer . Add ( new MathToken ( " 2 " )).Cap´ ıtulo 9 Forcemos tambi´n la colaboraci´n con MathLexer: e o [ Test ] public void P a r s e r W o r k s W i t h L e x e r () { List < MathToken > tokens = new List < MathToken >(). tokens . AreEqual (6 . Estos dos tests e nos posicionan en un c´digo m´ o ınimo sin llegar a ser el cl´sico return 4. a Buen punto de partida para triangular hacia algo m´s util. } § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ¦ § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ¥ Extraje la interfaz Lexer para generar el mock. tokens . return _calcProxy . _calcProxy = calcProxy .

IntValue . GetTokens ( expression ). i < tokens . total = new MathToken ( partialResult .10: MathParser § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public int ProcessExpression ( string expression ) { List < MathToken > tokens = _lexer . } ¦ ¥ Ahora el caso positivo: 169 . foreach ( char op in operators ) { if ( _token == op . Add . cambio de SUT un momento. } } ¦ § 1 2 3 4 5 6 7 8 9 10 ¥ 9. i ++) { if ( tokens [ i ]. i ++. for ( int i = 0. } } return total . ToString ()). Assert . Count . int partialResult = _calcProxy . IntValue .11: MathToken public bool isOperator () { string operators = " + -*/ " . } return false . } ¦ ¥ Lo m´s sencillo que se me ha ocurrido es coger los operadores y dar por sena tado que los operandos (n´meros) est´n a su izquierda y derecha. isOperator ()) { MathToken totalForNow = total . IntValue ). Al escribir u a el c´digo me he dado cuenta que necesitaba la funci´n isOperator en la o o clase MathToken y he hecho uso de ella sin que est´ implementada.Cap´ ıtulo 9 9. BinaryOperation ( _calcProxy . MathToken total = tokens [0]. As´ pues e ı dejo a un lado el SUT y voy a por un test que me ayude a implementar dicha funci´n. IsFalse ( numberToken . o o § 1 2 3 4 5 6 7 8 9 10 [ TestFixture ] public class MathTokenTests { [ Test ] public void isOperator () { MathToken numberToken = new MathToken ( " 22 " ). Calculator . ´ sea. ToString ()) return true . totalForNow . isOperator ()). MathToken nextNumber = tokens [ i + 1]. nextNumber .

Cuando el problema se hace complejo como en el caso que nos ocupa."2 + -2". produce ERROR o # Aceptaci´n . _parser . devuelve 16 o # Aceptaci´n . devuelve 9 o # Aceptaci´n ... que olvid´ el criterio de aceptaci´n de la precedencia de operadores. o e o No pasa nada. seguro que es m´s f´cil partir de aqu´ hacia la soluci´n del a a ı o problema que haber partido de cero. AreEqual (9 . devuelve 0 o § 1 2 3 4 5 Una vez m´s."3 / 2". La cosa marcha. para irles teniendo en mente e a la hora de tomar decisiones de dise˜o: n # Aceptaci´n . De paso podemos a˜adir a la lista los casos n en que se utilizan par´ntesis en las expresiones."5 + 4 * 2 / 2". produce ERROR o # Aceptaci´n . Quiz´s si hubi´semos contemplado el n n a e caso complejo desde el principio hubi´semos olvidado casos que a la larga se e hubiesen traducido en bugs."2 + -2". Assert .Cap´ ıtulo 9 § 1 2 3 4 5 6 [ Test ] public void isOperatorTrue () { MathToken numberToken = new MathToken ( " * " ). Antes de seguir voy a refactorizar un poco los tests. ProcessExpression ( " 3 + 3 * 2 " )). moviendo los que son de validaci´n de expresiones a un conjunto fuera de ParserTests ya que se o est´ convirtiendo en una clase con demasiados tests1 . devuelve 9 o # Aceptaci´n ."3 / 2". } ¦ 1 ¥ Ver los cambios en el c´digo fuente que acompa˜a al libro o n 170 . a Una vez movidos los tests tenemos que dise˜ar una estrategia para la n precedencia de los operadores. Revisamos la lista a ver c´mo e o vamos: # Aceptaci´n ."5 + 4 * 2 / 2". ¡tambi´n funciona!. } ¦ ¥ Funciona. IsTrue ( numberToken . Un ejemplo sencillo primero para afrontar el SUT: a [ Test ] public void P r o c e s s E x p r e s s i o n W i t h P r e c e d e n c e () { Assert . isOperator ()). devuelve 0 o ¡No hemos tenido en cuenta la precedencia de operadores! El test anterior no la exig´ y me centr´ tanto en el c´digo m´ ıa e o ınimo que cumpl´ con la especiıa ficaci´n. es especialmente importante no saltarse la regla de dise˜ar en peque˜os pasos. Podemos regresar al SUT anterior y ejecutar el test para comprobar que ."(2 + 2) * (3 + 1)".

Precedence >= precedence ) { precedence = op . De paso para encapsular mejor la calculadora podr´ mover ıa las llamadas al proxy a una clase operador. MathOperator op = _parser . podr´ buscar ıa aquellos de mayor precedencia. } } } return m a x P r e c e d e n c e O p e r a t o r . MathOperator m a x P r e c e d e n c e O p e r a t o r = null . " * " ). Create ( token ). Token . AreEqual ( op . } set { _token = value . o a e § 1 2 3 4 5 6 7 [ Test ] public void GetMaxPrecedence () { List < MathToken > tokens = _lexer .12: MathParser public MathOperator GetMaxPrecedence ( List < MathToken > t okens ) { int precedence = 0. } ¦ ¥ No compila porque las clases MathOperator y OperatorFactory no existen y el m´todo Create tampoco. voy a escribir otro test que me sirva para obtener la precedencia m´s alta de la a expresi´n. m a x P r e c e d e n c e O p e r a t o r = op . GetTokens ( " 3 + 3 * 2 " ).Cap´ ıtulo 9 Si el operador tuviera asociado un valor para su precedencia. isOperator ()) { MathOperator op = OperatorFactory . Voy a intentar que compile lo m´s r´pidamente e a a posible: § 1 2 3 4 5 6 7 8 9 10 11 public class MathOperator { int _precedence = 0. string _token = String . if ( op . Empty . Assert . } ¦ ¥ El SUT: § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 9. GetMaxPrecedence ( tokens ). public string Token { get { return _token . Como el test que nos ocupa me parece demasiado grande para implementar el SUT de una sola vez. foreach ( MathToken token in tokens ) { if ( token . Precedence . operar y a continuaci´n hacer lo mismo con o los de menor. } } 171 . Un poco m´s adelante retomar´ el test que acabo de escribir.

AreEqual ( op . return op . AreEqual ( op . El test pasa. } } 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 } ¦ ¥ Bien. Precedence . Create ( new MathToken ( " / " )). if ( token . Token = token .13: OperatorFactory public static MathOperator Create ( MathToken token ) { MathOperator op . Create ( new MathToken ( " * " )). return op . Precedence . } } ¦ ¥ SUT m´ ınimo: § 1 2 3 4 5 6 7 8 9 10 9.Cap´ ıtulo 9 public int Precedence { get { return _precedence . op . op . Para ello tenemos que probar que la factor´ de operadores les pone precedencia: ıa § 1 2 3 4 5 6 7 8 9 10 [ TestFixture ] public class O p e r a t o r F a c t o r y T e s t s { [ Test ] public void C r e a t e M u l t i p l y O p e r a t o r () { MathOperator op = OperatorFactory . Token . Token = token . Si escribo otro test para la divisi´n creo que por o ahora tendr´ las precedencias resueltas: e § 1 2 3 4 5 6 [ Test ] public void C r e a t e D i v i s i o n O p e r a t o r () { MathOperator op = OperatorFactory . } ¦ ¥ He tenido que a˜adir un constructor para MathOperator que reciba el valor n de precedencia. else op = new MathOperator (0). } ¦ ¥ 172 . } } public class OperatorFactory { public static MathOperator Create ( MathToken token ) { MathOperator op = new MathOperator (). Token == " * " ) op = new MathOperator (2). 2). Assert . El test para el m´todo de obtener la m´xima precedencia funciona e a parcialmente pero no hemos triangulado. Token . 2). Assert .

Index . GetTokens ( expression ). Token = token . Token == " * " ) || ( token . int firstNumber = tokens [ op . RemoveAt ( op . He necesitado una propiedad Index en el operator que no exist´ as´ que para poder compilar la a˜ado: ıa ı n § 1 2 3 4 9. Token . Me limito a buscar el operador de mayor prioridad.1] = new MathToken ( result . } ¦ ¥ El SUT: § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 9. ToString ()). ıan Podr´ refactorizar pero habr´ que refactorizar m´s cosas pronto. else op = new MathOperator (0). Empiezo a pensar que deber´ compartir una interfaz. } return tokens [0]. secondNumber ). Index ). Primero ıa a a voy a terminar de implementar el SUT para el test que hab´ ıamos dejado en el aire: [ Test ] public void P r o c e s s E x p r e s s i o n W i t h P r e c e d e n c e () { Assert . ProcessExpression ( " 3 + 3 * 2 " )). 173 . _parser . Count > 1) { MathOperator op = GetMaxPrecedence ( tokens ).Cap´ ıtulo 9 9.16: MathParser public MathOperator GetMaxPrecedence ( List < MathToken > t okens ) { int precedence = 0. Index +1].14: OperatorFactory § 1 2 3 4 5 6 7 8 9 10 public static MathOperator Create ( MathToken token ) { MathOperator op . Token == " / " )) op = new MathOperator (2). op . while ( tokens . IntValue . tokens [ op . if (( token . MathOperator m a x P r e c e d e n c e O p e r a t o r = null . tokens . IntValue . operar los n´meros a su izquierda y derecha y sustituir los tres elementos u por el resultado. return op . IntValue . tokens . Index ). int result = op . } ¦ ¥ § 1 2 3 4 5 Perfecto los tests pasan. Tanto MathToken como MathOperator comparten la propiedad Token.15: MathParser public int ProcessExpression ( string expression ) { List < MathToken > tokens = _lexer . int secondNumber = tokens [ op . Resolve ( firstNumber . RemoveAt ( op . AreEqual (9 . } ¦ ¥ He simplificado el algoritmo. Index -1].

a § 1 2 3 4 5 6 7 8 9. BinaryOperation ( calcProxy . b ). Voy a implementar un stub r´pido para compilar. Index = index . CalculatorProxy calcProxy ) { if ( Token == " * " ) return a * b . isOperator ()) { MathOperator op = OperatorFactory . } ¦ ¥ Muy bien.Cap´ ıtulo 9 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int index = -1. El primer cambio que voy a hacer es inyectar el proxy n a como par´metro en el m´todo para utilizarlo: a e § 1 2 3 4 5 6 7 8 9 9. } } } return m a x P r e c e d e n c e O p e r a t o r . Los tests estaban dentro del conjunto de tests del proxy: 174 . if ( Token == " + " ) return a + b . return 0. Add . if ( op . En mi cabeza es el m´todo e e que encapsula el uso del proxy y a su vez la calculadora. return 0. No he utilizado el proxy para la multiplicaci´n porque no la tenemos implementada. Create ( token ). porque en el m´todo Resolve hemos hecho un e apa˜o r´pido y feo. int b . a . Precedence . } ¦ ¥ El m´todo Resolve del operator tampoco existe. } ¦ ¥ Perfecto. m a x P r e c e d e n c e O p e r a t o r . Todos los tests pasan. foreach ( MathToken token in tokens ) { index ++. int b ) { if ( Token == " * " ) return a * b .17: MathOperator public int Resolve ( int a . Precedence >= precedence ) { precedence = op . Calculator . if ( token . Ahora pasan todos los tests menos aquel en el que forz´bamos al a analizador a utilizar el proxy. Voy a a˜adir un par de tests o n sencillos de multiplicaci´n y divisi´n para completar la funcionalidad de la o o clase Calculator. m a x P r e c e d e n c e O p e r a t o r = op .18: MathOperator public int Resolve ( int a . if ( Token == " + " ) return calcProxy .

Divide . 2 .21: MathOperator public int Resolve ( int a . } ¦ § 1 2 3 4 ¥ 9. 10). Count > 1) { MathOperator op = GetMaxPrecedence ( tokens ). if ( Token == " + " ) return calcProxy . 10 . b ). CalculatorProxy calcProxy ) { if ( Token == " * " ) return calcProxy . b ).22: MathParser public int ProcessExpression ( string expression ) { List < MathToken > tokens = _lexer . while ( tokens . BinaryOperation ( _calculator . BinaryOperation ( calcProxy . Calculator . } ¦ § 1 2 3 4 5 6 7 ¥ [ Test ] public void Division () { Assert . voy a completar el m´todo que resuelve en el operador: e § 1 2 3 4 5 6 7 8 9 10 9. Multiply . 175 . Aprovecho para refactorizar: § 1 2 3 4 5 6 9. AreEqual ( _calcProxy . Multiply . AreEqual ( _calcProxy . } ¦ ¥ Bien. } ¦ § 1 2 3 4 ¥ 9. Calculator .19: Calculator public int Multiply ( int arg1 . 2) . } ¦ ¥ Todos los tests pasan. 5). int arg2 ) { return arg1 / arg2 . 5) . int arg2 ) { return arg1 * arg2 . GetTokens ( expression ). return 0.Cap´ ıtulo 9 § 1 2 3 4 5 6 7 [ Test ] public void Multiply () { Assert . BinaryOperation ( calcProxy . Add . int b . a . a . BinaryOperation ( _calculator .20: Calculator public int Divide ( int arg1 .

Voy a a˜adir la divisi´n para o e n o que el test pase y luego escribo otro test con una resta para estar obligado a implementarla. } ¦ ¥ Se esperaba 9 pero se devolvi´ 5. int secondNumber = tokens [ op . secondNumber . Index . devuelve 0 o § 1 2 3 4 5 Estamos preparados para escribir un test de aceptaci´n para la primera o l´ ınea: [ Test ] public void P r o c e s s A c c e p t a n c e E x p r e s s i o n () { Assert . CalculatorProxy calcProxy ) { if ( Token == " * " ) return calcProxy . int result ) { tokens [ indexOfOperator . RemoveAt ( indexOfOperator ). ProcessExpression ( " 5 + 4 * 2 / 2 " )). } return tokens [0]. Divide . § 1 2 3 4 5 6 7 8 9 10 11 12 13 9. if ( Token == " / " ) return calcProxy . BinaryOperation ( calcProxy . if ( Token == " + " ) return calcProxy . int b . } 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ¦ ¥ ¿C´mo est´ la libreta? o a # Aceptaci´n . Qu´ despiste. r e p l a c e T o k e n s W i t h R e s u l t ( tokens . Index +1]. Calculator . ¡Ah claro! Es que el m´todo de resolver no o e implementa la divisi´n ni la resta. _parser . Index -1]."(2 + 2) * (3 + 1)". Resolve ( firstNumber . Calculator ."2 + -2". result ). produce ERROR o # Aceptaci´n . int result = op . b ). Add . Calculator . IntValue . Multiply . _calcProxy )."3 / 2". IntValue . IntValue . } private void r e p l a c e T o k e n s W i t h R e s u l t ( List < MathToken > tokens . a . BinaryOperation ( calcProxy . b ). devuelve 16 o # Aceptaci´n . ToString ()). tokens . a . devuelve 9 o # Aceptaci´n .1] = new MathToken ( result ."5 + 4 * 2 / 2". RemoveAt ( indexOfOperator ). } ¦ ¥ 176 . b ). op . int indexOfOperator . AreEqual (9 .Cap´ ıtulo 9 int firstNumber = tokens [ op . a . return 0.23: MathOperator public int Resolve ( int a . tokens . BinaryOperation ( calcProxy .

a . } } 177 . Multiply . a . } ¦ § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ¥ 9. return 0. BinaryOperation ( calcProxy . b ).24: MathOperator public int Resolve ( int a . Add . En la factor´ de operadores se pregunta por el token para asignar ıa precedencia al operador y crearlo. } } public string Token { get { return _token . b ). else if ( Token == " -" ) return calcProxy . } set { _index = value . Empty . protected string _token = String . Si utilizamos polimorfismo podemos eliminar la condicional del m´todo Resolve haciendo que cada operador espec´ e ıfico implemente su propia resoluci´n. else if ( Token == " / " ) return calcProxy . } public int Index { get { return _index .1 * 2 / 2 " )). Divide . AreEqual (8 . a . Calculator . a . b ). Calculator .Cap´ ıtulo 9 § 1 2 3 4 5 6 [ Test ] public void P r o c e s s A c c e p t a n c e E x p r e s s i o n W i t h A l l O p e r a t o r s () { Assert . BinaryOperation ( calcProxy . Substract . b ). Calculator . BinaryOperation ( calcProxy . ProcessExpression ( " 5 + 4 . BinaryOperation ( calcProxy . else if ( Token == " + " ) return calcProxy . _parser . Calculator . int b . public MathOperator ( int precedence ) { _precedence = precedence . CalculatorProxy calcProxy ) { if ( Token == " * " ) return calcProxy . } ¦ ¥ § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ¡Luz verde! ¿Algo por refactorizar? Ciertamente hay dos condicionales repetidas. Esta refactorizaci´n de hecho se llama as´ o o ı: reemplazar condicional con polimorfismo: public abstract class MathOperator { protected int _precedence = 0. int _index = -1. En la resoluci´n tambi´n se pregunta por o e el token para invocar al proxy.

} } public class MultiplyOperator : MathOperator { public MultiplyOperator () : base (2) { _token = " * " . Calculator . int b . CalculatorProxy calcProxy ). int b . CalculatorProxy calcProxy ) { return calcProxy . Divide . Multiply . } } public class AddOperator : MathOperator { public AddOperator () : base (1) { _token = " + " . b ). Calculator . b ). } } public class DivideOperator : MathOperator { public DivideOperator () : base (2) { _token = " / " . } public override int Resolve ( int a . b ). Calculator . } } public abstract int Resolve ( int a . a . } public override int Resolve ( int a . int b . } } 178 . BinaryOperation ( calcProxy . int b . Add . a . } public override int Resolve ( int a . BinaryOperation ( calcProxy . CalculatorProxy calcProxy ) { return calcProxy . CalculatorProxy calcProxy ) { return calcProxy . BinaryOperation ( calcProxy .Cap´ ıtulo 9 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 public int Precedence { get { return _precedence . a .

"3 / 2". el compilador me obliga o a a hacerlo. produce ERROR o # Aceptaci´n . a . Token == " * " ) return new MultiplyOperator ()."(2 + 2) * (3 + 1)". a Recordemos que la finalidad de TDD no es alcanzar una cobertura de tests del 100 % sino dise˜ar acorde a los requisitos mediante los ejemplos. CalculatorProxy calcProxy ) { return calcProxy ."2 + -2". Calculator . else if ( token . Token == " / " ) return new DivideOperator (). n Repasemos la libreta: # Aceptaci´n . ıa No veo la necesidad de escribir un test para ese caso porque ya tenemos un validador de expresiones y m´todos que comprueban si un token es n´mero e u u operador y ambas cosas est´n debidamente probadas. ya que de lo contrario tendr´ que devolver un objeto sin sentido. } } 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 ¦ ¥ El c´digo queda m´s claro y la condicional en un unico sitio. Token == " -" ) return new SubstractOperator (). Parecen muchas o a ´ l´ ıneas pero ha sido una refactorizaci´n de tres minutos. Token == " + " ) return new AddOperator (). int b . devuelve 16 o # Aceptaci´n . devuelve 0 o Pensemos en las operaciones con par´ntesis.Cap´ ıtulo 9 public class SubstractOperator : MathOperator { public SubstractOperator () : base (1) { _token = " -" . Substract . } } public class OperatorFactory { public static MathOperator Create ( MathToken token ) { if ( token . else if ( token . else if ( token . b ). throw new I n v a l i d O p e r a t i o n E x c e p t i o n ( " The given token is not a valid operator " ). BinaryOperation ( calcProxy . o Aunque no hay ning´n test que pruebe que el m´todo Create lanza una u e excepci´n en caso que el token recibido no sea v´lido. } public override int Resolve ( int a . En c´mo resolver el problee o 179 .

AreEqual (1 . Una expresi´n a su vez puede verse como una lista de o tokens que en ultima instancia contiene un solo elemento que es un n´mero. Push ( ch ). } ¦ ¥ De momento el test est´ dentro de ParserTests pero ya estoy pensando a moverlo a un nuevo conjunto LexerTests. AreEqual ( " 2 + 2 " . No voy a devolver un resultado fijo directamente sino a dar los primeros pasos en el algoritmo que encuentra expresiones dentro de los par´ntesis. devuelve 0 o El primero de los tests: § 1 2 3 4 5 6 7 8 [ Test ] public void G e t E x p r e s s i o n s W i t h 1 P a r e n t h e s i s () { List < string > expressions = _lexer . demasiado engorroso. expressions [0]).25: MathLexer public List < string > GetExpressions ( string expression ) { List < string > expressions = new List < string >()."3 / 2". devuelve 16 o "(2 + 2)". Empty )."(2 + 2) * (3 + 1)". a o a ıcil Por otro lado no sabr´ utilizar expresiones regulares para comprobar que un ıa par´ntesis abierto casa con uno cerrado y cosas as´ Si nos fijamos bien. Aumentar la complejidad de la expresi´n regular que valida las expreo siones matem´ticas no me parece sostenible. } else if ( ch == ’) ’) { 180 . ´ u Vamos a partir el test de aceptaci´n en unos cuantos tests de granularidad o m´s fina para ir abordando poco a poco la implementaci´n. GetExpressions ( " (2 + 2) " ). produce ERROR o # Aceptaci´n . produce una excepci´n o # Aceptaci´n . expressions . se traduce en la expresi´n "2 + 2" o "((2) + 2)". Add ( String . el e ı. a o # Aceptaci´n .Cap´ ıtulo 9 ma. expressions . Assert . foreach ( char ch in expression ) { if ( ch == ’( ’) { parenthesis . Assert . contenido de un par´ntesis ha de ser una expresi´n de las que ya sabemos e o validar y resolver. Me da la sensaci´n de que ir a o por ese camino har´ el c´digo m´s dif´ de mantener. se traduce en la expresi´n "2 + 2" o "(2 + 2". Count ). e § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 9."2 + -2". Stack < char > parenthesis = new Stack < char >().

Assert . Empty ). } 15 16 17 18 19 20 21 22 23 24 ¦ ¥ Cada vez que se encuentra un par´ntesis abierto se crea una nueva expresi´n. expressions . } } c l e a n E m p t y E x p r e s s i o n s ( expressions ).26: MathLexer public List < string > GetExpressions ( string expression ) { List < string > expressions = new List < string >(). Count -1] += ch . Count ). expressions [0]). } else { expressions [ expressions . Pop (). foreach ( char ch in expression ) { if ( ch == ’( ’) { parenthesis . Count -1] += ch . e o El algoritmo es simple. } else { expressions [ expressions . ToString (). } ¦ ¥ El test falla porque la funci´n est´ devolviendo dos expresiones. Push ( ch ). Eso lo dejar´ para un e test que lo requiera. } 181 . AreEqual (1 . } } return expressions . Stack < char > parenthesis = new Stack < char >(). ToString (). return expressions . aunque o no estoy haciendo uso de ella al final del algoritmo. } else if ( ch == ’) ’) { parenthesis . GetExpressions ( " ((2) + 2) " ).Cap´ ıtulo 9 parenthesis . Add ( String . AreEqual ( " 2 + 2 " . § 1 2 3 4 5 6 7 8 [ Test ] public void G e t E x p r e s s i o n s W i t h N e s t e d P a r e n t h e s i s () { List < string > expressions = _lexer . He utilizado una pila para llevar control de par´ntesis e abiertos y cerrados en vista de los pr´ximos tests que hay en la libreta. expressions . Pop (). ıas: § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 9. la primera o a de ellas vac´ Hay que limpiar expresiones vac´ ıa. Assert .

Add ( String . Count .27: MathLexer public List < string > GetExpressions ( string expression ) { List < string > expressions = new List < string >().Cap´ ıtulo 9 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 private void c l e a n E m p t y E x p r e s s i o n s ( List < string > expres sions ) { bool endOfList = false . AreEqual (3 . expressions . } } } ¦ ¥ Ya tenemos luz verde. expressions . index ++. Empty ). break . Fail ( " Wrong expression split " ). endOfList = false . Se me acaba de venir a la mente una pregunta. foreach ( char ch in expression ) { if ( ch == ’( ’) { parenthesis . Assert . while (! endOfList ) { endOfList = true . a las expresiones que no tienen sentido matem´tico a por s´ mismas. Stack < int > parenthesis = new Stack < int >(). o § 1 2 3 4 5 6 7 8 9 10 11 12 13 9. i ++) if ( expressions [ i ]. Length == 0) { expressions . 182 . i < expressions . RemoveAt ( i ). } ¦ ¥ El test expresa mi decisi´n de evitar devolver expresiones del tipo “+ 1” prefio riendo los tokens sueltos. Count ). He tenido cuidado de no especificar en las afirmaciones las ı posiciones de las expresiones dentro del vector de expresiones para no escribir un test fr´gil. for ( int i = 0. ¿Y si al leer las expresiones se forma una que no empieza por un n´mero?. Push ( index ). GetExpressions ( " ((2 + 1) + 2) " ). int index = 0. Ejemplo: u § 1 2 3 4 5 6 7 8 9 10 11 12 13 [ Test ] public void G e t N e s t e d E x p r e s s i o n s () { List < string > expressions = _lexer . Lo que me interesa es el contenido de las cadenas y no la a posici´n. foreach ( string exp in expressions ) if (( exp != " 2 + 1 " ) && ( exp != " + " ) && ( exp != " 2 " )) Assert .

for ( int i = 0. } } } 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 ¦ ¥ La nueva funci´n busca expresiones que empiecen por un operador y entonces o las parte en dos.Cap´ ıtulo 9 } else if ( ch == ’) ’) { index = parenthesis . 2 . while (! endOfList ) { endOfList = true . string [] nexExps = exp . Trim (). expressions . Count . Split ( new char [] { ’ ’ . e o o Recordemos que a veces los m´todos privados sugieren ser movidos a e clases colaboradoras. por un lado el operador y por otro el resto de la expresi´n. Tener acceso desde el test a la funci´n que se quiere probar sin pasar por otras funciones o o m´todos de entrada acelera la detecci´n y correcci´n de defectos. i < expressions . endOfList = false . ToString (). Pop (). exp = exp . expressions [ i ] = nexExps [0]. bool endOfList = false . nexExps [1]). } else { expressions [ index -1] += ch . Insert ( i + 1 . Al escribir esta funci´n me o a n o doy cuenta de que probablemente quiera escribir unos cuantos tests unitarios para cubrir otros usos de la misma pero el m´todo es privado y eso limita e la granularidad de los tests ya que no tengo acceso directo. return expressions . Vamos a hacer un poco de limpieza: § 1 2 public class ExpressionFixer { 183 . s p l i t E x p r e s s i o n s S t a r t i n g W i t h O p e r a t o r ( expressions ). RemoveEmptyEntries ). ’\ t ’ } . a El c´digo est´ empezando a ser una mara˜a. o Por ahora no hace nada m´s. StringSplitOptions . i ++) if ( regex . } private void s p l i t E x p r e s s i o n s S t a r t i n g W i t h O p e r a t o r ( List < string > expressions ) { Regex regex = new Regex ( @ " ^(\ s *)[+|\ -|/|*](\ s +) " ). IsMatch ( expressions [ i ])) { string exp = expressions [ i ]. } } c l e a n E m p t y E x p r e s s i o n s ( expressions ).

Stack < int > parenthesis = new Stack < int >(). Insert ( i + 1 . i ++) if ( regex . 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 184 . ExpressionFixer _fixer . bool endOfList = false . Count . Trim (). endOfList = false . i < expressions . string [] nexExps = exp . } } } } public class MathLexer : Lexer { ExpressionValidator _validator . 2 . Count . Length == 0) { expressions . RemoveAt ( i ). exp = exp . public MathLexer ( ExpressionValidator validator . for ( int i = 0. i < expressions . break . expressions . expressions [ i ] = nexExps [0]. IsMatch ( expressions [ i ])) { string exp = expressions [ i ]. while (! endOfList ) { endOfList = true . ’\ t ’ } . Split ( new char [] { ’ ’ . int index = 0. _fixer = fixer . while (! endOfList ) { endOfList = true . } } } public void S p l i t E x p r e s s i o n s S t a r t i n g W i t h O p e r a t o r ( List < string > expressions ) { Regex regex = new Regex ( @ " ^(\ s *)[+|\ -|/|*](\ s +) " ). ExpressionFixer fixer ) { _validator = validator . RemoveEmptyEntries ). nexExps [1]). endOfList = false .Cap´ ıtulo 9 public void C l e a n E m p t y E x p r e s s i o n s ( List < string > express ions ) { bool endOfList = false . } public List < string > GetExpressions ( string expression ) { List < string > expressions = new List < string >(). StringSplitOptions . i ++) if ( expressions [ i ]. for ( int i = 0.

} return false . ToString (). Pop (). return true . } } _fixer .Cap´ ıtulo 9 foreach ( char ch in expression ) { if ( ch == ’( ’) { parenthesis . while ( listHasChanged ) { listHasChanged = false . i ++) if ( D o e s E x p r e s s i o n S t a r t s W i t h O p e r a t o r ( expressions . C l e a n E m p t y E x p r e s s i o n s ( expressions ). RemoveAt ( index ). } } } public bool IsEmptyExpression ( List < string > expressions . 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 ¦ ¥ He creado la nueva clase ExpressionFixer (reparador de expresiones) que se inyecta a MathLexer. index ++. Count .. e Vamos a afinar un poco m´s: a § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class ExpressionFixer { public void FixExpressions ( List < string > expressions ) { bool listHasChanged = true . Push ( index ). 185 . _fixer . i )) { listHasChanged = true . for ( int i = 0. break . return expressions . S p l i t E x p r e s s i o n s S t a r t i n g W i t h O p e r a t o r ( express ions ). int index ) { if ( expressions [ index ].. Length == 0) { expressions . } else if ( ch == ’) ’) { index = parenthesis . Ahora me sigue pareciendo que hay duplicidad en los bucles de los dos m´todos del reparador de expresiones. i ) || IsEmptyExpression ( expressions . Empty ). } else { expressions [ index -1] += ch . expressions . L´gicamente he tenido que modificar las llamadas o al constructor de lexer en los tests. Add ( String . } . i < expressions .

Fail ( " Wrong expression split " ). RemoveEmptyEntries ). " + " ). Todav´ me parece que el c´digo ıa o del ultimo m´todo tiene varias responsabilidades pero por ahora voy a parar ´ e de refactorizar y a anotarlo en la libreta para retomarlo en breve. } [ Test ] 186 . int index ) { Regex regex = new Regex ( @ " ^(\ s *)[+|\ -|/|*](\ s +) " ). if ( regex . GetExpressions ( " ((2) + 2) " ). return true . StringSplitOptions . Hay c´digo duplicado en los tests. Trim (). Insert ( i + 1 . Lo arreglamos: o § 1 2 3 4 5 6 7 8 9 9. GetExpressions ( " ((2) + 2) " ). ’\ t ’ } . IsMatch ( expressions [ index ])) { string exp = expressions [ index ]. expressions [ i ] = nexExps [0]. Ejecuto toda la bater´ de tests despu´s de tanto cambio y veo que el test ıa e est´ funcionando pero que se ha roto el que hab´ a ıamos escrito anteriormente. } ¦ ¥ Ahora ya funciona. exp = exp . 2 .Cap´ ıtulo 9 } public void D o e s E x p r e s s i o n S t a r t s W i t h O p e r a t o r ( List < string > expressions . Como no es un test de aceptaci´n puedo cambiarlo sin o problema. f a i l I f O t h e r S u b E x p r e s s i o n T h a n ( expressions . } } 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 ¦ ¥ § 1 2 3 4 5 6 7 8 9 10 11 En MathLexer cambi´ las dos llamadas de las l´ e ıneas 79 y 80 del pen´ltimo u listado por una sola a FixExpressions. string [] nexExps = exp . " 2 " .28: LexerTests [ Test ] public void G e t E x p r e s s i o n s W i t h N e s t e d P a r e n t h e s i s () { List < string > expressions = _lexer . Split ( new char [] { ’ ’ . [ Test ] public void G e t E x p r e s s i o n s W i t h N e s t e d P a r e n t h e s i s () { List < string > expressions = _lexer . } return false . expressions . foreach ( string exp in expressions ) if (( exp != " 2 " ) && ( exp != " + " )) Assert . nexExps [1]). Voy a reescribirlo.

Assert . expressions . int index = 1. } private void f a i l I f O t h e r S u b E x p r e s s i o n T h a n ( List < string > expressions . Corrijo el SUT: o § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 9. foreach ( string exp in expressions ) if ( exp == subExpression ) { isSubExpression = true . expressions . params string [] e x p e c t e d S u b E x p r e s s i o n s ) { bool isSubExpression = false . 187 . foreach ( char ch in expression ) { if ( ch == ’( ’) { parenthesis . } if (! isSubExpression ) Assert . } ¦ ¥ De momento falla con una excepci´n. AreEqual (3 .Cap´ ıtulo 9 public void G e t N e s t e d E x p r e s s i o n s () { List < string > expressions = _lexer . " 3 * 1 " . index ++. Stack < int > parenthesis = new Stack < int >(). foreach ( string subExpression in e x p e c t e d S u b E x p r e s s i o n s ) { isSubExpression = false . " 2 + 1 " . expressions . Empty ). " + " . Empty ). Fail ( " Wrong expression split : " + subExpression ). GetExpressions ( " 2 + (3 * 1) " ). " + " . Add ( String . " 2 " ).29: MathLexer public List < string > GetExpressions ( string expression ) { List < string > expressions = new List < string >(). o ıa ´ § 1 2 3 4 5 6 7 8 [ Test ] public void G e t E x p r e s s i o n W i t h P a r e n t h e s i s A t T h e E n d () { List < string > expressions = _lexer . Push ( index ). failIfOtherSubExpressionThan ( expressions . " 2 " ). GetExpressions ( " ((2 + 1) + 2) " ). break . Add ( String . Count ). } } 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 ¦ ¥ ¿C´mo se comporta nuestra clase cuando el par´ntesis aparece en la parte o e final de la expresi´n? Me ha surgido la duda mientras escrib´ el ultimo SUT. failIfOtherSubExpressionThan ( expressions .

Vamos a escribir un test o espec´ ıfico para el reparador de expresiones a fin de corregir el problema: § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 [ TestFixture ] public class E x p r e s s i o n F i x e r T e s t s { [ Test ] public void S p l i t E x p r e s s i o n W h e n O p e r a t o r A t T h e E n d () { ExpressionFixer fixer = new ExpressionFixer (). } } _fixer . Pop (). expressions . i ) || I s E m p t y E x p r e s s i o n T h e n R e m o v e ( expressions . expressions ).Cap´ ıtulo 9 } else if ( ch == ’) ’) { index = parenthesis . } 15 16 17 18 19 20 21 22 23 24 25 26 27 28 ¦ ¥ El SUT no contemplaba que la expresi´n comenzase sin par´ntesis (la l´ o e ınea 7 lo corrige). a pesar de devolver e verdadero o falso. No estoy partiendo la a expresi´n en caso que el operador quede al final. poco los nombres de los dos m´todos para denotar que. i )) { listHasChanged = true . Contains ( " + " . List < string > expressions = new List < string >(). return expressions . } else { expressions [ index -1] += ch . expressions ). Count . Assert . Assert . i < expressions . Ahora la ejecuci´n no se interrumpe pero seguimos en rojo porque o se est´n devolviendo las cadenas "2 +" y "3 * 1". } } ¦ ¥ Efectivamente est´ fallando ah´ Voy a corregirlo y de paso a modificar un a ı. while ( listHasChanged ) { listHasChanged = false . break . } } 188 . Contains ( " 2 " . modifican la lista de expresiones: § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class ExpressionFixer { public void FixExpressions ( List < string > expressions ) { bool listHasChanged = true . ToString (). FixExpressions ( expressions ). fixer . Add ( " 2 + " ). FixExpressions ( expressions ). i ++) if ( I s N u m b e r A n d O p e r a t o r T h e n S p l i t ( expressions . for ( int i = 0.

expressions . foreach ( string subExp in nextExps ) { expressions . int j = position . Puesto que tenemos una clase que n gestiona expresiones regulares (ExpressionValidator) tiene sentido que la 189 . subExp . } return false . } } 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 ¦ ¥ N´tese que tambi´n extraje el m´todo splitByOperator como resultado de o e e otra refactorizaci´n. if ( startsWithOperator . e Est´ indicando que estamos violando el principio de una unica responsabia ´ lidad. int position ) { string [] nextExps = Regex . int index ) { Regex startsWithOperator = new Regex ( @ " ^(\ s *)([+|\ -|/|*])(\ s +) " ). Split ( inputExpression . Trim (). RemoveAt ( index ). La hice en dos pasos aunque haya pegado el c´digo una o o sola vez (todo sea por ahorrar papel). } } public bool I s E m p t y E x p r e s s i o n T h e n R e m o v e ( List < string > e xpressions . } private void splitByOperator ( List < string > expressions . string exp = expressions [ index ]. IsMatch ( exp ) || endsWithOperator . return true . @ " ([+|\ -|/|*]) " ). RemoveAt ( j ). Insert (j . IsMatch ( exp )) { splitByOperator ( expressions . exp . Tratemos de mejorar el dise˜o. exp = exp . string inputExpression . return true . index ). j ++. Length == 0) { expressions . Me cost´ trabajo decidir qu´ nombre ponerle a los m´todos y al final o e e el que hemos puesto denota claramente que cada m´todo hace dos cosas.Cap´ ıtulo 9 } public bool I s N u m b e r A n d O p e r a t o r T h e n S p l i t ( List < string > expressions . Trim ()). Regex endsWithOperator = new Regex ( @ " (\ s +)([+|\ -|/|*])(\ s *) $ " ). int index ) { if ( expressions [ index ]. } return false .

expressions . listHasChanged = true . Insert (j . break . listHasChanged = true . break . foreach ( string subExp in nextExps ) { expressions . i < expressions . Trim ( ))). Count . } public void FixExpressions ( List < string > expressions ) { bool listHasChanged = true . for ( int i = 0. } if ( expressions [ i ]. } } } } private void splitByOperator ( List < MathExpression > expr essions . RemoveAt ( j ). while ( listHasChanged ) { listHasChanged = false .1}\ d +)+ $ " ). IsNumberAndOperator ( expressions [ i ])) { splitByOperator ( expressions .1}\ d +((\ s +)[+|\ -|/|*](\ s +) -{0 . § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 190 .Cap´ ıtulo 9 pregunta de si la expresi´n contiene un n´mero y un operador pase a estar o u ah´ ı: public class ExpressionFixer { ExpressionValidator _validator . expressions [ i ] . int position ) { string [] nextExps = Regex . i ++) { if ( _validator . @ " ([+|\ -|/|*]) " ). RemoveAt ( i ). public ExpressionFixer ( ExpressionValidator validator ) { _validator = validator . Length == 0) { expressions . string inputExpression . i ). int j = position . } } } public class ExpressionValidator { public bool IsExpressionValid ( string expression ) { Regex fullRegex = new Regex ( @ " ^ -{0 . new MathExpression ( subExp . Split ( inputExpression . j ++.

string exp = expression . public ExpressionFixer ( MathRegex mathRegex ) { _mathRegex = mathRegex . Regex singleNumber = new Regex ( @ " ^\ d + $ " ). } public bool IsNumberAndOperator ( string expression ) { Regex startsWithOperator = new Regex ( @ " ^(\ s *)([+|\ -|/|*])(\ s +) " ). return ( fullRegex .. Mucho mejor ahora. } } ¦ ¥ Hemos renombrado ExpressionValidator por MathRegex. IsMatch ( exp ) || endsWithOperator . El que valida es lexer.Cap´ ıtulo 9 Regex singleOperator = new Regex ( @ " ^[+|\ -|/|*] $ " ). } public bool IsNumberAndOperator ( string expression ) { . IsMatch ( expression . if ( startsWithOperator . 0)). El c´digo fuente debe poderse entender f´cilmente al leerlo n o a y para eso es fundamental que los nombres describan con total precisi´n o qu´ hacen los m´todos. 191 . 0) || singleNumber . Buen momento para hacer el cambio: public class ExpressionFixer { MathRegex _mathRegex . ¿ExpressionValidator es realmente un validador? M´s bien es un clase de consulta de expresiones regulares del a dominio. 0) || singleOperator .. return false .... Regex endsWithOperator = new Regex ( @ " (\ s +)([+|\ -|/|*])(\ s *) $ " ). IsMatch ( expression .. IsMatch ( expression . IsMatch ( exp )) return true . } } 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 ¦ ¥ § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Ahora las responsabilidades est´n bien repartidas y ning´n nombre de m´todo a u e suena extra˜o. e e Algo sigue sin encajar del todo. } . } public class MathRegex { public bool IsExpressionValid ( string expression ) { .

Stack < int > parenthesis = new Stack < int >(). e entonces excepci´n: o § 1 2 3 4 5 6 7 8 9 10 11 12 [ Test ] public void T h r o w E x c e p t i o n O n O p e n P a r e n t h e s i s () { try { List < string > expressions = _lexer . Luz verde en toda la bater´ de ı e ıa tests. foreach ( char ch in expression ) { if ( ch == ’( ’) { parenthesis ."3 / 2". } else if ( ch == ’) ’) { index = parenthesis . int index = 1. devuelve 16 o "(2 + 2". Revisamos la libreta: # Aceptaci´n .Cap´ ıtulo 9 El test pasa y as´ tambi´n el anterior. Empty ). Empty ). } catch ( I n v a l i d O p e r a t i o n E x c e p t i o n ) { } } ¦ ¥ Se arregla con dos l´ ıneas (26 y 27): § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 9."2 + -2". Assert . devuelve 0 o Si un par´ntesis abierto no encuentra correspondencia con otro cerrado. Add ( String . expressions . Pop (). } } if ( parenthesis .30: MathLexer public List < string > GetExpressions ( string expression ) { List < string > expressions = new List < string >(). Add ( String . index ++. GetExpressions ( " (2 + 3 * 1 " ). produce ERROR o # Aceptaci´n . produce una excepci´n o # Aceptaci´n . ToString ()."(2 + 2) * (3 + 1)". Count > 0) throw new 192 . Push ( index ). } else { expressions [ index -1] += ch . Fail ( " Exception didn ’t arise ! " ). expressions .

currentIndex = closePosition . currentIndex ++) { char currentChar = fullInputExpression [ currentIndex ]. return expressions . Empty . int closePosition = getExpressions ( fullInputExpression . totalExpressionsFound . El c´digo de GetExpressions me est´ empezando a hacer da˜o a la o a n vista. FixExpressions ( t o t a l E x p r e s s i o n s F o u n d ). _fixer . int openedParenthesis = 0. int subExpressionStartIndex . getExpressions ( expression . if ( openedParenthesis != 0) throw new I n v a l i d O p e r a t i o n E x c e p t i o n ( " Parenthesis do not match " ). string subExpressionUnderConstruction . } else if ( currentChar == CLOSE_SUBEXPRESSION ) 193 . FixExpressions ( expressions ).31: MathLexer public List < string > GetExpressions ( string expression ) { List < string > t o t a l E x p r e s s i o n s F o u n d = new List < string >(). Length . Vamos a ver c´mo nos las apa˜amos para refactorizarlo: o n § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 9. currentIndex < fullInputExpression . } // / < summary > // / Returns the p o s i t i o n where the close p a r e n t h e s i s is found or // / the p o s i t i o n of the last char in the string . Empty . _fixer . // / Also p o p u l a t e s the list of e x p r e s s i o n s along the way // / </ summary > private int getExpressions ( string fullInputExpression . List < string > totalSubexpressionsFound . String . currentIndex + 1 . ref openedParenthesis ). 0 . ref int openedParanthesis ) { for ( int currentIndex = s u b E x p r e s s i o n S t a r t I n d e x .Cap´ ıtulo 9 I n v a l i d O p e r a t i o n E x c e p t i o n ( " Parenthesis do not match " ). } 28 29 30 31 32 ¦ ¥ Aprovecho tambi´n para mover algunos tests a sus clases ya que Mathe Lexer ha crecido bastante. return t o t a l E x p r e s s i o n s F o u n d . String . totalSubexpressionsFound . if ( currentChar == OPEN_SUBEXPRESSION ) { openedParanthesis ++. ref openedParanthesis ).

} else { s u b E x p r e s s i o n U n d e r C o n s t r u c t i o n += fullInputExpression [ currentIndex ]. Si pudiera cambiar eso me dar´ por satisfecho de momento. return fullInputExpression . para sopesar la poca claridad n o de su c´digo. } 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 ¦ ¥ Despu´s de un rato d´ndole vueltas no encuentro una manera de deshacerme e a de ese bloque if-else. FixExpressions ( t o t a l E x p r e s s i o n s F o u n d ). totalExpressionsFound . return t o t a l E x p r e s s i o n s F o u n d . e e Lo que no me gusta nada es devolver un valor en la funci´n recursiva que o en algunos casos no utilizo: en la l´ ınea 7 del listado estoy invoc´ndola sin a utilizar para nada su valor de retorno. openedParanthesis . Empty . tiene la naturaleza recursiva ıa a o a de las propias expresiones con par´ntesis anidados pero tambi´n es compleja. Ahora la funci´n es m´s natural. if ( openedParenthesis != 0) throw new I n v a l i d O p e r a t i o n E x c e p t i o n ( " Parenthesis do not match " ).-. ToString (). currentIndex < fullInputExpression . return currentIndex . Length . Add ( s u b E x p r e s s i o n U n d e r C o n s t r u c t i o n ). currentIndex ++) 194 . List < string > totalSubexpressionsFound . ref int subExpressionStartIndex . String . string subExpressionUnderConstruction . o ıa Reintento: § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 9.Cap´ ıtulo 9 { t o t a l S u b e x p r e s s i o n s F o u n d . int startSearchingAt = 0. Add ( s u b E x p r e s s i o n U n d e r C o n s t r u c t i o n ).32: MathLexer public List < string > GetExpressions ( string expression ) { List < string > t o t a l E x p r e s s i o n s F o u n d = new List < string >(). getExpressions ( expression . } } t o t a l S u b e x p r e s s i o n s F o u n d . Si utilizo polimorfismo tengo la sensaci´n de que se va a o liar todav´ m´s. int openedParenthesis = 0. } private void getExpressions ( string fullInputExpression . _fixer . ref openedParenthesis ). Esta es la raz´n por la que he cre´ o ıdo conveniente a˜adir un comentario a la funci´n. ref int openedParanthesis ) { for ( int currentIndex = s u b E x p r e s s i o n S t a r t I n d e x . Length . ref startSearchingAt .

"(2 + 2) * (3 + 1)". s u b E x p r e s s i o n S t a r t I n d e x = currentIndex +1. Add ( s u b E x p r e s s i o n U n d e r C o n s t r u c t i o n ). Empty . String . " * " ). Devuelve las subcadenas que queremos pero se han colado algunos espacios en blanco delante y detr´s del asterisco. ref openedParanthesis ). devuelve 0 o Voy a ver qu´ tal se maneja MathLexer con la expresi´n que tiene dos e o par´ntesis: e § 1 2 3 4 5 6 7 8 [ Test ] public void G e t E x p r e s s i o n W i t h T w o G r o u p s () { List < string > expressions = _lexer .Cap´ ıtulo 9 { char currentChar = fullInputExpression [ currentIndex ]. ref subExpressionStartIndex . failIfOtherSubExpressionThan ( expressions . getExpressions ( fullInputExpression . s u b E x p r e s s i o n S t a r t I n d e x = s u b E x p r e s s i o n U n d e r C o n s t r u c t i o n . currentIndex = s u b E x p r e s s i o n S t a r t I n d e x . } else { s u b E x p r e s s i o n U n d e r C o n s t r u c t i o n += fullInputExpression [ currentIndex ]. a Volvamos a la libreta: # Aceptaci´n . Add ( s u b E x p r e s s i o n U n d e r C o n s t r u c t i o n ). " 3 + 1 " . devuelve 16 o # Aceptaci´n . if ( currentChar == OPEN_SUBEXPRESSION ) { openedParanthesis ++. } ¦ ¥ Vaya no funciona. return . s u b E x p r e s s i o n S t a r t I n d e x = currentIndex . Escribimos otro test a de grano m´s fino para solucionarlo: a 195 . } } t o t a l S u b e x p r e s s i o n s F o u n d . } 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 ¦ ¥ Ahora todos los tests est´n pasando y el c´digo pinta algo mejor. produce ERROR o # Aceptaci´n . openedParanthesis . GetExpressions ( " (2 + 2) * (3 + 1) " )."2 + -2". } else if ( currentChar == CLOSE_SUBEXPRESSION ) { t o t a l S u b e x p r e s s i o n s F o u n d . Segua o ramente m´s adelante tengamos oportunidad de afinarlo. totalSubexpressionsFound ."3 / 2". " 2 + 2 " .-. ToString (). Length .

while ( listHasChanged ) { listHasChanged = false . AreEqual ( " * " . _expressions [0]). } [ Test ] public void Trim () { _expressions . } if ( expressions [ i ]. } } 196 . for ( int i = 0. Count . IsNumberAndOperator ( expressions [ i ])) { splitByOperator ( expressions . i ++) { expressions [ i ] = expressions [ i ]. } [ Test ] public void S p l i t E x p r e s s i o n W h e n O p e r a t o r A t T h e E n d () { _expressions . RemoveAt ( i ). listHasChanged = true . Contains ( " + " . Length == 0) { expressions . i < expressions . Assert . Contains ( " 2 " .Cap´ ıtulo 9 § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 [ TestFixture ] public class E x p r e s s i o n F i x e r T e s t s { ExpressionFixer _fixer . break . expressions [ i ] . Trim (). Assert . _fixer . break . } ¦ ¥ Luz roja. Add ( " 2 + " ).33: EspressionFixer public void FixExpressions ( List < string > expressions ) { bool listHasChanged = true . _expressions ). listHasChanged = true . _fixer . Assert . FixExpressions ( _expressions ). [ SetUp ] public void SetUp () { _fixer = new ExpressionFixer (). List < string > _expressions . _expressions ). Add ( " * " ). _expressions = new List < string >(). i ). FixExpressions ( _expressions ). Ya podemos arreglarlo: § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 9. if ( _mathRegex .

IsMatch ( exp ) && operatorRegex . GetTokens ( expression ). } private int r e s o l v e S i m p l e E x p r e s s i o n ( string expression ) { List < MathToken > mathExp = _lexer . Count > 1) { MathOperator op = GetMaxPrecedence ( mathExp ). Regex numberRegex = new Regex ( @ " \ d + " ). Hora de resolver una expresi´n que tiene par´ntesis: o e [ Test ] public void P r o c e s s A c c e p t a n c e E x p r e s s i o n W i t h P a r e n t h e s i s () { Assert . if ( numberRegex . } private bool isSubExpression ( string exp ) { Regex operatorRegex = new Regex ( @ " [+|\ -|/|*] " ). como si tuvi´ramos dos niveles. String flatExpression = String . secondNumber .22 en p´gina 155 a 197 . int firstNumber . } return r e s o l v e S i m p l e E x p r e s s i o n ( flatExpression ). while ( mathExp . foreach ( string subExp in subExpressions ) { if ( isSubExpression ( subExp )) flatExpression += r e s o l v e S i m p l e E x p r e s s i o n ( subExp ). } ¦ ¥ Se lanza una excepci´n porque parser est´ intentando extraer de lexer o a los tokens en lugar de las expresiones2 . firstNumber = mathExp [ op .1]. Excelente. return false . IsMatch ( exp )) return true . ProcessExpression ( " (2 + 2) * (3 + 1) " )). Empty . AreEqual (16 .34: MathParser public int ProcessExpression ( string expression ) { List < string > subExpressions = _lexer . _parser . Ver listado 11. else flatExpression += " " + subExp + " " . Voy a adaptar el c´digo: e o § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 2 9. GetExpressions ( expression ). IntValue .Cap´ ıtulo 9 } } 25 26 ¦ ¥ § 1 2 3 4 5 6 7 Ahora funciona el test actual y el anterior (arreglado con l´ ınea 9). Acabamos de dise˜ar para que se n extraigan las expresiones primero y luego los tokens de cada una. Index .

Index . ref startSearchingAt . expressions [1]).35: MathLexer public List < MathExpression > GetExpressions ( string expr ession ) { List < MathExpression > t o t a l E x p r e s s i o n s F o u n d = new List < MathExpression >(). IntValue . Empty ) . return t o t a l E x p r e s s i o n s F o u n d . FixExpressions ( t o t a l E x p r e s s i o n s F o u n d ). AreEqual ( " 3 + 1 " . op . Si no lo anoto en la a libreta seguro que me olvido. Assert . int result = op . El test sigue fallando porque el orden en que llegan las subexpresiones del lexer est´ cambiado. getExpressions ( expression . AreEqual ( " * " . " ). AreEqual ( " 2 + 2 " . Out . _fixer . Resolve ( firstNumber . ref openedParenthesis ). GetExpressions ( " 2 + 2) * (3 + 1) " ). } 198 . Assert . no he sido estricto con la regla del c´digo o o m´ ınimo y esto me ha hecho anotar en la libreta que me gustar´ escribir ıa algunos tests para isSubExpression m´s adelante. if ( openedParenthesis != 0) throw new I n v a l i d O p e r a t i o n E x c e p t i o n ( " Parenthesis do not match " ). El algoritmo aplana las expresiones hasta llegar a una expresi´n simple de las que sabe resolver. } return mathExp [0]. Index + 1]. Para resolverlo tendr´ que escribir un nuevo test para lexer que e exija que el orden de aparici´n de las subexpresiones se mantenga: o § 1 2 3 4 5 6 7 8 9 10 11 [ Test ] public void G e t S e v e r a l P a r e n t h e s i s E x p r e s s i o n s I n O r d e r () { List < string > expressions = _lexer . Assert . IntValue . secondNumber . } 35 36 37 38 39 40 41 ¦ ¥ El m´todo resolveSimpleExpression ya lo ten´ e ıamos escrito antes pero con el nombre ProcessExpression. expressions [2]). foreach ( string exp in expressions ) Console . } ¦ ¥ Tal como tenemos la funci´n en el lexer puedo hacer que se registre el orden o de aparici´n de cada subexpresi´n y luego ordenar si me ayudo de una nueva o o clase (MathExpression): § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 9. WriteLine ( " x : " + exp + " . expressions [0]). Est´ llegando "2 + 2" "3 a a + 1" y "*". result ). int openedParenthesis = 0. r e p l a c e T o k e n s W i t h R e s u l t ( mathExp . new MathExpression ( String .Cap´ ıtulo 9 secondNumber = mathExp [ op . int startSearchingAt = 0. He escrito mucho o c´digo para implementar el SUT. totalExpressionsFound . _calcProxy ).

subExpressionStartIndex ). Empty . new MathExpression ( String . if ( s u b E x p r e s s i o n U n d e r C o n s t r u c t i o n . subExpressionStartIndex = s u b E x p r e s s i o n U n d e r C o n s t r u c t i o n .36: MathExpression public class MathExpression { private string _expression . totalSubexpressionsFound . s u b E x p r e s s i o n S t a r t I n d e x = currentIndex . Order = currentIndex . public MathExpression ( string expression ) { _expression = expression . currentIndex < fullInputExpression . Add ( s u b E x p r e s s i o n U n d e r C o n s t r u c t i o n ). } ¦ § 1 2 3 4 5 6 7 8 9 10 ¥ 9. currentIndex = s u b E x p r e s s i o n S t a r t I n d e x . getExpressions ( fullInputExpression . Add ( s u b E x p r e s s i o n U n d e r C o n s t r u c t i o n ). ref subExpressionStartIndex . ref int subExpressionStartIndex . List < MathExpression > totalSubexpressionsFound . s u b E x p r e s s i o n S t a r t I n d e x = currentIndex +1. ref openedParanthesis ).-.Cap´ ıtulo 9 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 private void getExpressions ( string fullInputExpression . } else if ( currentChar == CLOSE_SUBEXPRESSION ) { t o t a l S u b e x p r e s s i o n s F o u n d . Length . private int _order . ToString (). if ( currentChar == OPEN_SUBEXPRESSION ) { openedParanthesis ++. MathExpression subExpressionUnderConstruction . Expression . Order == -1) s u b E x p r e s s i o n U n d e r C o n s t r u c t i o n . Length . currentIndex ++) { char currentChar = fullInputExpression [ currentIndex ]. } 199 . openedParanthesis . } else { s u b E x p r e s s i o n U n d e r C o n s t r u c t i o n . return . } } t o t a l S u b e x p r e s s i o n s F o u n d . _order = -1. Expression += fullInputExpression [ currentIndex ]. ref int openedParanthesis ) { for ( int currentIndex = s u b E x p r e s s i o n S t a r t I n d e x .

Order ) { subExpressions [ j ] = exp2 . j < subExpressions . i < subExpressions . totalExpressionsFound . } private void b u b b l e S o r t E x p r e s s i o n s ( List < MathExpression > subExpressions ) { for ( int i = 0. Order < exp1 . 200 . ref startSearchingAt . Count -1. } } } ¦ ¥ He cambiado las cadenas por un objeto sencillo llamado MathExpression que guarda la posici´n en que aparece la subexpresi´n dentro de la expresi´n o o o de entrada. _fixer . } set { _order = value . b u b b l e S o r t E x p r e s s i o n s ( t o t a l E x p r e s s i o n s F o u n d ). ref openedParenthesis ). Empty ) . new MathExpression ( String .37: MathLexer public List < MathExpression > GetExpressions ( string expr ession ) { List < MathExpression > t o t a l E x p r e s s i o n s F o u n d = new List < MathExpression >(). getExpressions ( expression . if ( exp2 . FixExpressions ( t o t a l E x p r e s s i o n s F o u n d ). j ++) { MathExpression exp1 = subExpressions [ j ]. return t o t a l E x p r e s s i o n s F o u n d . int openedParenthesis = 0.Cap´ ıtulo 9 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public MathExpression ( string expression . } set { _expression = value . } } public int Order { get { return _order . _order = order . if ( openedParenthesis != 0) throw new I n v a l i d O p e r a t i o n E x c e p t i o n ( " Parenthesis do not match " ). } public string Expression { get { return _expression . Count . int order ) { _expression = expression . MathExpression exp2 = subExpressions [ j + 1]. i ++) { for ( int j = 0. int startSearchingAt = 0. Solamente me falta ordenar las subexpresiones para hacer pasar el test: § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 9.

Index . int firstNumber . El ultimo test ya pasa y todos ´ los dem´s tambi´n. } private int r e s o l v e S i m p l e E x p r e s s i o n ( string expression ) { List < MathToken > mathExp = _lexer . secondNumber . firstNumber = mathExp [ op . GetTokens ( expression ).38: parte de MathParser public int ProcessExpression ( string expression ) { List < MathExpression > subExpressions = _lexer . return true . if ( numberRegex . int result = op . que todav´ tiene un peque˜o bug por falta de adaptar ıa n el c´digo a la nueva clase MathExpression. secondNumber . else flatExpression += " " + subExp . Expression + " " .Net me ordena la lista (lo anoto en la libreta).1]. IntValue . Empty . Expression )) flatExpression += r e s o l v e S i m p l e E x p r e s s i o n ( subExp . } } } } 32 33 34 35 36 37 ¦ ¥ Una ordenaci´n sencilla bastar´ por ahora. secondNumber = mathExp [ op . con la excepci´n de ProcessAcceptanceExpressiona e o WithParenthesis. Count > 1) { MathOperator op = GetMaxPrecedence ( mathExp ). } private bool isSubExpression ( string exp ) { Regex operatorRegex = new Regex ( @ " [+|\ -|/|*] " ). IsMatch ( exp )) { Console . String flatExpression = String . while ( mathExp . Out . WriteLine ( " YES : " + exp ). Expression ). GetExpressions ( expression ). } return r e s o l v e S i m p l e E x p r e s s i o n ( flatExpression ). foreach ( MathExpression subExp in subExpressions ) { if ( isSubExpression ( subExp . o Corrijo: § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 9. _calcProxy ). Luego hago un spike para ver si o a . 201 . IsMatch ( exp ) && operatorRegex . IntValue .Cap´ ıtulo 9 subExpressions [ j + 1] = exp1 . } return false . Resolve ( firstNumber . Regex numberRegex = new Regex ( @ " \ d + " ). Index + 1].

Cap´ ıtulo 9
r e p l a c e T o k e n s W i t h R e s u l t ( mathExp , op . Index , result ); } return mathExp [0]. IntValue ; }

42 43 44 45

¦

¥

¡Ya funciona! ¿Hay algo que refactorizar ahora? S´ hay unos cuantos m´todos ı, e que no est´n en el sitio m´s adecuado. a a Para recordar al lector c´mo est´ ahora mismo nuestro diagrama de clases o a emergente veamos la siguiente figura:

§
1 2 3 4

Movamos isSubExpression a nuestro MathRegex. Vaya, en ese caso necesito inyectar MathRegex en MathParser aunque ya estaba inyectado en MathLexer. Eso de que parser necesite de lexer y que ambos tengan inyectado al que entiende de expresiones regulares, pinta mal. ¿Para qu´ estamos e usando lexer? Para extraer elementos de la expresi´n de entrada. ¿Y parser? o Para encontrarles el sentido a los elementos. Entonces, ¿a qui´n corresponde e la validaci´n de la expresi´n que est´ haciendo ahora lexer? ¡A parser! De o o a acuerdo, lo primero que muevo es la validaci´n de expresiones: o
public class MathParser { Lexer _lexer ; MathRegex _mathRegex ;

202

Cap´ ıtulo 9
CalculatorProxy _calcProxy ; public MathParser ( Lexer lexer , CalculatorProxy calcProxy , MathRegex mathRegex ) { _lexer = lexer ; _mathRegex = mathRegex ; _calcProxy = calcProxy ; } public MathOperator GetMaxPrecedence ( List < MathToken > t okens ) { int precedence = 0; MathOperator m a x P r e c e d e n c e O p e r a t o r = null ; int index = -1; foreach ( MathToken token in tokens ) { index ++; if ( token . isOperator ()) { MathOperator op = OperatorFactory . Create ( token ); if ( op . Precedence >= precedence ) { precedence = op . Precedence ; m a x P r e c e d e n c e O p e r a t o r = op ; m a x P r e c e d e n c e O p e r a t o r . Index = index ; } } } return m a x P r e c e d e n c e O p e r a t o r ; } public int ProcessExpression ( string expression ) { List < MathExpression > subExpressions = _lexer . GetExpressions ( expression ); String flatExpression = String . Empty ; foreach ( MathExpression subExp in subExpressions ) { if ( _mathRegex . IsSubExpression ( subExp . Expression )) flatExpression += r e s o l v e S i m p l e E x p r e s s i o n ( subExp . Expression ); else flatExpression += " " + subExp . Expression + " " ; } return r e s o l v e S i m p l e E x p r e s s i o n ( flatExpression ); } private int r e s o l v e S i m p l e E x p r e s s i o n ( string expression ) { if (! _mathRegex . IsExpressionValid ( expression )) throw new I n v a l i d O p e r a t i o n E x c e p t i o n ( expression ); List < MathToken > mathExp = _lexer . GetTokens ( expression ); while ( mathExp . Count > 1) { MathOperator op = GetMaxPrecedence ( mathExp );

5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63

203

Cap´ ıtulo 9
int firstNumber , secondNumber ; firstNumber = mathExp [ op . Index - 1]. IntValue ; secondNumber = mathExp [ op . Index + 1]. IntValue ; int result = op . Resolve ( firstNumber , secondNumber , _calcProxy ); r e p l a c e T o k e n s W i t h R e s u l t ( mathExp , op . Index , result ); } return mathExp [0]. IntValue ; } private void r e p l a c e T o k e n s W i t h R e s u l t ( List < MathToken > tokens , int indexOfOperator , int result ) { tokens [ indexOfOperator - 1] = new MathToken ( result . ToString ()); tokens . RemoveAt ( indexOfOperator ); tokens . RemoveAt ( indexOfOperator ); } } public class MathLexer : Lexer { ExpressionFixer _fixer ; static char OPEN_SUBEXPRESSION = ’( ’; static char CLOSE_SUBEXPRESSION = ’) ’; public MathLexer ( ExpressionFixer fixer ) { _fixer = fixer ; } public List < MathToken > GetTokens ( string expression ) { string [] items = splitExpression ( expression ); return c r e a t e T o k e n s F r o m S t r i n g s ( items ); } private string [] splitExpression ( string expression ) { return expression . Split (( new char [] { ’ ’ , ’\ t ’ }) , StringSplitOptions . RemoveEmptyEntries ); } private List < MathToken > c r e a t e T o k e n s F r o m S t r i n g s ( strin g [] items ) { List < MathToken > tokens = new List < MathToken >(); foreach ( String item in items ) { tokens . Add ( new MathToken ( item )); } return tokens ; } public List < MathExpression > GetExpressions ( string expr ession ) { List < MathExpression > t o t a l E x p r e s s i o n s F o u n d = new List < MathExpression >();

64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122

204

Cap´ ıtulo 9
int openedParenthesis = 0; int startSearchingAt = 0; getExpressions ( expression , ref startSearchingAt , new MathExpression ( String . Empty ) , totalExpressionsFound , ref openedParenthesis ); if ( openedParenthesis != 0) throw new I n v a l i d O p e r a t i o n E x c e p t i o n ( " Parenthesis do not match " ); _fixer . FixExpressions ( t o t a l E x p r e s s i o n s F o u n d ); b u b b l e S o r t E x p r e s s i o n s ( t o t a l E x p r e s s i o n s F o u n d ); return t o t a l E x p r e s s i o n s F o u n d ; } private void b u b b l e S o r t E x p r e s s i o n s ( List < MathExpression > subExpressions ) { for ( int i = 0; i < subExpressions . Count ; i ++) { for ( int j = 0; j < subExpressions . Count -1; j ++) { MathExpression exp1 = subExpressions [ j ]; MathExpression exp2 = subExpressions [ j + 1]; if ( exp2 . Order < exp1 . Order ) { subExpressions [ j ] = exp2 ; subExpressions [ j + 1] = exp1 ; } } } } private void getExpressions ( string fullInputExpression , ref int subExpressionStartIndex , MathExpression subExpressionUnderConstruction , List < MathExpression > totalSubexpressionsFound , ref int openedParanthesis ) { for ( int currentIndex = s u b E x p r e s s i o n S t a r t I n d e x ; currentIndex < fullInputExpression . Length ; currentIndex ++) { char currentChar = fullInputExpression [ currentIndex ]; if ( currentChar == OPEN_SUBEXPRESSION ) { openedParanthesis ++; s u b E x p r e s s i o n S t a r t I n d e x = currentIndex +1; getExpressions ( fullInputExpression , ref subExpressionStartIndex , new MathExpression ( String . Empty , subExpressionStartIndex ), totalSubexpressionsFound , ref openedParanthesis ); currentIndex = s u b E x p r e s s i o n S t a r t I n d e x ; } else if ( currentChar == CLOSE_SUBEXPRESSION ) { t o t a l S u b e x p r e s s i o n s F o u n d . Add (

123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181

205

Cap´ ıtulo 9
s u b E x p r e s s i o n U n d e r C o n s t r u c t i o n ); s u b E x p r e s s i o n S t a r t I n d e x = currentIndex ; openedParanthesis - -; return ; } else { s u b E x p r e s s i o n U n d e r C o n s t r u c t i o n . Expression += fullInputExpression [ currentIndex ]. ToString (); if ( s u b E x p r e s s i o n U n d e r C o n s t r u c t i o n . Order == -1) s u b E x p r e s s i o n U n d e r C o n s t r u c t i o n . Order = currentIndex ; } } t o t a l S u b e x p r e s s i o n s F o u n d . Add ( s u b E x p r e s s i o n U n d e r C o n s t r u c t i o n ); subExpressionStartIndex = s u b E x p r e s s i o n U n d e r C o n s t r u c t i o n . Expression . Length ; } } public class MathRegex { public bool IsExpressionValid ( string expression ) { Regex fullRegex = new Regex ( @ " ^ -{0 ,1}\ d +((\ s +)[+|\ -|/|*](\ s +) -{0 ,1}\ d +)+ $ " ); Regex singleOperator = new Regex ( @ " ^[+|\ -|/|*] $ " ); Regex singleNumber = new Regex ( @ " ^\ d + $ " ); return ( fullRegex . IsMatch ( expression , 0) || singleOperator . IsMatch ( expression , 0) || singleNumber . IsMatch ( expression , 0)); } public bool IsNumberAndOperator ( string expression ) { Regex startsWithOperator = new Regex ( @ " ^(\ s *)([+|\ -|/|*])(\ s +) " ); Regex endsWithOperator = new Regex ( @ " (\ s +)([+|\ -|/|*])(\ s *) $ " ); string exp = expression ; if ( startsWithOperator . IsMatch ( exp ) || endsWithOperator . IsMatch ( exp )) return true ; return false ; } public bool IsSubExpression ( string expression ) { Regex operatorRegex = new Regex ( @ " [+|\ -|/|*] " ); Regex numberRegex = new Regex ( @ " \ d + " ); if ( numberRegex . IsMatch ( expression ) && operatorRegex . IsMatch ( expression )) return true ; return false ; } }

182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238

¦

¥

He corregido los tests para que compilen despu´s de los cambios y falla uno. e 206

Cap´ ıtulo 9 Aquel que le dice al lexer que ante una expresi´n inv´lida lance una excepci´n. o a o Se debe a que ya lexer no valida expresiones sino parser. Entonces tenemos que mover el test a parser. Sin embargo, el m´todo resolveSimpleExe pression de parser es privado y no lo podemos testear directamente. Me empieza a parecer que parser tiene demasiadas responsabilidades. Est´ bien a que entienda la expresi´n pero prefiero que sea otra clase la que resuelva las o operaciones:
§
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48

public class Resolver { MathRegex _mathRegex ; Lexer _lexer ; CalculatorProxy _calcProxy ; public Resolver ( MathRegex mathRegex , Lexer lexer , CalculatorProxy calcProxy ) { _mathRegex = mathRegex ; _lexer = lexer ; _calcProxy = calcProxy ; } public MathOperator GetMaxPrecedence ( List < MathToken > t okens ) { int precedence = 0; MathOperator m a x P r e c e d e n c e O p e r a t o r = null ; int index = -1; foreach ( MathToken token in tokens ) { index ++; if ( token . isOperator ()) { MathOperator op = OperatorFactory . Create ( token ); if ( op . Precedence >= precedence ) { precedence = op . Precedence ; m a x P r e c e d e n c e O p e r a t o r = op ; m a x P r e c e d e n c e O p e r a t o r . Index = index ; } } } return m a x P r e c e d e n c e O p e r a t o r ; } public int R e s o l v e S i m p l e E x p r e s s i o n ( string expression ) { if (! _mathRegex . IsExpressionValid ( expression )) throw new I n v a l i d O p e r a t i o n E x c e p t i o n ( expression ); List < MathToken > mathExp = _lexer . GetTokens ( expression ); while ( mathExp . Count > 1) { MathOperator op = GetMaxPrecedence ( mathExp ); int firstNumber , secondNumber ;

207

} public int ProcessExpression ( string expression ) { List < MathExpression > subExpressions = _lexer . ToString ()). int result ) { tokens [ indexOfOperator . secondNumber . _calcProxy ). Resolver resolver ) { _lexer = lexer . tokens . R e s o l v e S i m p l e E x p r e s s i o n ( flatExpress ion ). Resolver _resolver . e a 3 Usar m´todos est´ticos es casi siempre una mala idea. Es hora de que se convierta en una serie de m´todos est´ticos3 y deje de ser una dependencia inyectada. public MathParser ( Lexer lexer . _mathRegex = mathRegex . int result = op . tokens . else flatExpression += " " + subExp . RemoveAt ( indexOfOperator ). Expression ). Index + 1].1] = new MathToken ( result . Index . String flatExpression = String . RemoveAt ( indexOfOperator ). op . MathRegex _mathRegex . result ). int indexOfOperator . R e s o l v e S i m p l e E x p r e s s i o n ( subExp . Expression )) flatExpression += _resolver . foreach ( MathExpression subExp in subExpressions ) { if ( _mathRegex . Resolve ( firstNumber . Expression + " " . secondNumber = mathExp [ op .1]. MathRegex mathRegex . } return mathExp [0]. IsSubExpression ( subExp . Index . Ahora hay demasiadas inyecciones y dependencias. } return _resolver .Cap´ ıtulo 9 firstNumber = mathExp [ op . } private void r e p l a c e T o k e n s W i t h R e s u l t ( List < MathToken > tokens . IntValue . IntValue . } } 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 ¦ ¥ Tuve que mover un test de sitio y modificar las llamadas a los constructores puesto que han cambiado. } } public class MathParser { Lexer _lexer . al final del cap´ e a ıtulo se vuelve 208 . Empty . GetExpressions ( expression ). r e p l a c e T o k e n s W i t h R e s u l t ( mathExp . Demasiadas clases necesitan a MathRegex. _resolver = resolver . IntValue .

} ¦ ¥ Luz verde. Index + 1].39: Resolver § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public int R e s o l v e S i m p l e E x p r e s s i o n ( string expression ) { if (! MathRegex . secondNumber . List < MathToken > mathExp = _lexer . Como no estoy dise˜ando un compilador ni siguiendo el n m´todo tradicional de construcci´n de una herramienta de an´lisis de c´digo. IntValue . IsExpressionValid ( expression )) throw new I n v a l i d O p e r a t i o n E x c e p t i o n ( expression ). Resolve ( firstNumber . IntValue . IsSubExpression ( " 2 + 2 " )). op . Index . quer´ a˜adir un test para las subexpresiones: ıa n § 1 2 3 4 5 9. Este test me simplificar´ la b´squeda de posibles defectos. _calcProxy ). while ( mathExp . u ¿Queda algo m´s por mejorar? Si quisi´ramos ser fieles a la teor´ de a e ıa compiladores entonces la funci´n GetExpressions de lexer se mover´ a o ıa parser ya que en ella se hace la validaci´n de par´ntesis y se le busca sentido o e a las expresiones. e o a o no me importa que mis clases no coincidan exactamente con la teor´ ıa. IntValue . GetTokens ( expression ). result ). } ¦ ¥ Por cierto.1]. } return mathExp [0].Net seguramente lo resuelve.41: MathExpression 209 . Por otra parte.Cap´ ıtulo 9 9. secondNumber = mathExp [ op . Ahora acabo de hacer el spike y veo que s´lo con imo plementar la interfaz IComparable en MathExpression ya puedo utilizar el m´todo Sort de las listas: e § a hablar sobre esto 9. a u Veamos el diagrama de clases resultante: Los m´todos que empiezan en may´scula son p´blicos y los que empiezan e u u en min´scula privados. Count > 1) { MathOperator op = GetMaxPrecedence ( mathExp ). secondNumber . r e p l a c e T o k e n s W i t h R e s u l t ( mathExp . hab´ ıamos hecho un m´todo de ordenaci´n que probablee o mente no sea necesario ya que . En el momento de escribirlo me result´ m´s r´pido utilizar el conocido m´todo de la burbuja o a a e que hacer un spike. int firstNumber .40: MathRegexTests [ Test ] public void IsSubExpression () { Assert . IsTrue ( MathRegex . Index . firstNumber = mathExp [ op . int result = op .

public MathExpression ( string expression ) { _expression = expression .1: Diagrama de clases actual 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class MathExpression : IComparable { private string _expression . } public string Expression { get { return _expression . int order ) { _expression = expression . } } public int Order { get { return _order .Cap´ ıtulo 9 Figura 9. _order = -1. _order = order . } set { _expression = value . private int _order . } 210 . } set { _order = value . } public MathExpression ( string expression .

new MathExpression ( String . Expression . totalExpressionsFound . _fixer . Expression . ref startSearchingAt . return _order . ref openedParenthesis ). Expression )) { splitByOperator ( expressions . 211 . i ). int startSearchingAt = 0.42: MathLexer public List < MathExpression > GetExpressions ( string expr ession ) { List < MathExpression > t o t a l E x p r e s s i o n s F o u n d = new List < MathExpression >(). } ¦ ¥ El m´todo de ordenaci´n de la burbuja fue eliminado por completo. for ( int i = 0.. } } 28 29 30 31 32 33 34 35 36 37 38 39 40 ¦ § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ¥ 9. t o t a l E x p r e s s i o n s F o u n d .43: ExpressionFixer public void FixExpressions ( List < MathExpression > expres sions ) { bool listHasChanged = true . Expression = expressions [ i ]. Empty ) . if ( openedParenthesis != 0) throw new I n v a l i d O p e r a t i o n E x c e p t i o n ( " Parenthesis do not match " ). i < expressions . FixExpressions ( t o t a l E x p r e s s i o n s F o u n d ). Order ). getExpressions ( expression . Count . expressions [ i ].> O r d e n a c i o n return t o t a l E x p r e s s i o n s F o u n d . listHasChanged = true .Cap´ ıtulo 9 } public bool IsEmpty () { return _expression . CompareTo ( exp . Sort (). int openedParenthesis = 0. e o A˜ad´ el m´todo IsEmpty a MathExpression para encapsular esta can ı e racter´ ıstica que se usaba aqu´ (l´ ı ınea 18): § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 9. Length == 0. while ( listHasChanged ) { listHasChanged = false .. } public int CompareTo ( Object obj ) { MathExpression exp = ( MathExpression ) obj . // . IsNumberAndOperator ( expressions [ i ]. if ( MathRegex . i ++) { expressions [ i ]. Trim ().

La verdad es que el m´todo GetMaxe Precedence de Resolver est´ haciendo demasiadas cosas. listHasChanged = true . IsEmpty ()) { expressions . } } public int Precedence { get { return _precedence . } } public string Token { get { return _token . } } // E l i m i n a d o metodo I s O p e r a t o r } public abstract class MathOperator : MathToken { public MathOperator ( int precedence ) 212 . Al leer el c´digo me da o la sensaci´n de que ese metodo deber´ ser privado o mejor. } public int Index { get { return _index . Voy a refactorizar: public class MathToken { protected int _precedence = 0. protected string _token = String . } if ( expressions [ i ]. } } } } 17 18 19 20 21 22 23 24 25 26 27 ¦ ¥ § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 Por ultimo nos hab´ quedado pendiente estudiar la relaci´n entre las ´ ıa o clases MathToken y MathOperator. } set { _index = value . estar en otra o ıa clase. break . protected int _index = -1. No s´lo busca a o la precedencia sino que devuelve un objeto operador. } public MathToken ( int precedence ) { _precedence = precedence .Cap´ ıtulo 9 break . RemoveAt ( i ). Empty . public MathToken ( string token ) { _token = token .

CalculatorProxy _calcProxy . if ( token . foreach ( MathToken token in tokens ) { index ++. CalculatorProxy calcProxy ). MathToken maxPrecedenceToken = null . int b . Index = index . maxPrecedenceToken . maxPrecedenceToken = token . CalculatorProxy calcProxy ) { _lexer = lexer . Precedence >= precedence ) { precedence = token . } 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 213 . public Resolver ( Lexer lexer . } } public static int GetTokenValue ( string token ) { return Int32 . int index = -1.Cap´ ıtulo 9 : base ( precedence ) { } public abstract int Resolve ( int a . } } public class Resolver { Lexer _lexer . Parse ( token ). } } return maxPrecedenceToken . Precedence . } public class MathNumber : MathToken { public MathNumber () : base (0) {} public MathNumber ( string token ) : base ( token ) {} public int IntValue { get { return Int32 . Parse ( _token ). } public MathToken GetMaxPrecedence ( List < MathToken > toke ns ) { int precedence = 0. _calcProxy = calcProxy .

_calcProxy ).Create est´ intentando crear operadores a partir de tokens que no a lo son.1]. Count > 1) { MathToken token = GetMaxPrecedence ( mathExp ). Token ). secondNumber . tokens . GetTokens ( expression ). Index + 1]. int indexOfOperator . int result ) { tokens [ indexOfOperator . por a lo que no hay distinci´n: o § 1 2 3 4 5 6 7 8 9 9. while ( mathExp . RemoveAt ( indexOfOperator ).1] = new MathToken ( result . Index . } } 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 ¦ ¥ Al ejecutar los tests veo que todos los del parser fallan porque OperatorFactory. Voy a pedirle a MathRegex que me responda si los tokens son operadores o n´meros: u § 9. tokens . List < MathToken > mathExp = _lexer . firstNumber = MathNumber . r e p l a c e T o k e n s W i t h R e s u l t ( mathExp . Token ). } return tokens . result ). RemoveAt ( indexOfOperator ). foreach ( String item in items ) { tokens . GetTokenValue ( mathExp [0]. int firstNumber . Add ( new MathToken ( item )).Cap´ ıtulo 9 public int R e s o l v e S i m p l e E x p r e s s i o n ( string expression ) { if (! MathRegex . secondNumber . } private void r e p l a c e T o k e n s W i t h R e s u l t ( List < MathToken > tokens . op . Token ). IsExpressionValid ( expression )) throw new I n v a l i d O p e r a t i o n E x c e p t i o n ( expression ). MathOperator op = OperatorFactory . } ¦ ¥ Si este m´todo crease tokens de tipo operador o n´mero tendr´ e u ıamos el problema resuelto. } return MathNumber .44: MathLexer private List < MathToken > c r e a t e T o k e n s F r o m S t r i n g s ( strin g [] items ) { List < MathToken > tokens = new List < MathToken >(). ToString ()). Resolve ( firstNumber . int result = op . secondNumber = MathNumber . Lexer est´ construyendo todos los tokens con precedencia cero. GetTokenValue ( mathExp [ op . Index . GetTokenValue ( mathExp [ op . Create ( token ).45: MathRegexTests 214 .

} public static bool IsNumberAndOperator ( string expressio n ) { Regex startsWithOperator = 215 . IsNumber ( " 22 " )). 0) || IsNumber ( expression ) || IsOperator ( expression )). public static bool IsExpressionValid ( string expression ) { Regex fullRegex = new Regex ( @ " ^ -{0 . } ¦ ¥ No necesito un test para comprobar que el token es un n´mero si tiene u espacios delante o detr´s porque la funci´n va a ser usada cuando ya se han a o filtrado los espacios. } ¦ ¥ ¿Hay c´digo duplicado en MathRegex? S´ se repiten a menudo partes de o ı. return exactOperator .47: MathRegex public static bool IsOperator ( string token ) { Regex exactOperator = new Regex ( @ " ^[*|\ -|/|+] $ " ). ¿Es operador? § 1 2 3 4 5 6 7 [ Test ] public void IsOperator () { string operators = " *+/ ." . IsMatch ( expression . IsOperator ( op . IsMatch ( token . Refactorizo: § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class MathRegex { public static string operators = @ " [*|\ -|/|+] " . } ¦ § 1 2 3 4 5 ¥ 9. 0). 0). return exactNumber . } 1 2 3 4 5 ¦ ¥ Ahora el SUT: § 1 2 3 4 5 9.46: MathRegex public static bool IsNumber ( string token ) { Regex exactNumber = new Regex ( @ " ^\ d + $ " ).1}\ d +((\ s +) " + operators + @ " (\ s +) -{0 . ToString ())). return ( fullRegex .Cap´ ıtulo 9 [ Test ] public void IsNumber () { Assert . foreach ( char op in operators ) Assert . IsTrue ( MathRegex . IsMatch ( token . expresiones regulares.1}\ d +)+ $ " ). IsTrue ( MathRegex .

IsMatch ( token . Assert . string exp = expression . string rege x ) { Regex exactRegex = new Regex ( @ " ^ " + regex + " $ " ). Regex numberRegex = new Regex ( @ " \ d + " ). @ " \ d + " ).48: LexerTests [ Test ] public void G e t T o k e n s R i g h t S u b c l a s s e s () { List < MathToken > tokens = _lexer . } public static bool IsExactMatch ( string token . return exactRegex . if ( numberRegex . } 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 ¦ ¥ ¿Por d´nde ´ o ıbamos? Ejecuto toda la bater´ de tests y veo que lexer est´ deıa a volviendo tokens gen´ricos. } public static bool IsSubExpression ( string expression ) { Regex operatorRegex = new Regex ( operators ).49: MathLexer private List < MathToken > c r e a t e T o k e n s F r o m S t r i n g s ( strin g [] items ) { 216 .Cap´ ıtulo 9 new Regex ( @ " ^(\ s *)( " + operators + @ " )(\ s +) " ). } public static bool IsOperator ( string token ) { return IsExactMatch ( token . IsMatch ( expression )) return true . IsMatch ( exp )) return true . Ahora que ya sabemos distinguir n´meros de e u operadores podemos hacer que lexer construya los objetos adecuadamente: § 1 2 3 4 5 6 7 8 9. operators ). } public static bool IsNumber ( string token ) { return IsExactMatch ( token . if ( startsWithOperator . IsTrue ( tokens [0] is MathNumber ). 0). IsTrue ( tokens [1] is MathOperator ). return false . Regex endsWithOperator = new Regex ( @ " (\ s +)( " + operators + @ " )(\ s *) $ " ). IsMatch ( exp ) || endsWithOperator . Assert . return false . GetTokens ( " 2 + 2 " ). } ¦ ¥ SUT: § 1 2 9. IsMatch ( expression ) && operatorRegex .

IsOperator ( item )) tokens . CalculatorProxy calcProxy ) { _lexer = lexer . } public MathToken GetMaxPrecedence ( List < MathToken > toke ns ) { int precedence = 0. MathToken maxPrecedenceToken = null . Index = index . int index = -1. ¿Pasan todos los tests?. El c´digo queda as´ a o ı: § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 4 9. public Resolver ( Lexer lexer . } public int R e s o l v e S i m p l e E x p r e s s i o n ( string expression ) { if (! MathRegex . } } return maxPrecedenceToken . No. Precedence . List < MathToken > mathExp = _lexer . CalculatorProxy _calcProxy . a´n faltaba adaptar replaceTou kensWithResult de Resolver para que devuelva un MathNumber en lugar de un token gen´rico. Precedence >= precedence ) { precedence = token . maxPrecedenceToken = token . IsExpressionValid ( expression )) throw new I n v a l i d O p e r a t i o n E x c e p t i o n ( expression ). } 3 4 5 6 7 8 9 10 11 12 ¦ ¥ El test pasa.50: Resolver public class Resolver { Lexer _lexer . maxPrecedenceToken . Create ( item )). Add ( new MathNumber ( item )). GetTokens ( expression ). Lo pospongo por fines docentes 217 .Cap´ ıtulo 9 List < MathToken > tokens = new List < MathToken >(). else tokens . foreach ( MathToken token in tokens ) { index ++. Ya pasan todos los tests menos uno que discutiremos e un poco m´s adelante4 . if ( token . _calcProxy = calcProxy . foreach ( String item in items ) { if ( MathRegex . Add ( OperatorFactory . } return tokens .

int indexOfOperator . 218 . CalculatorProxy _calcProxy . if ( token . } } return maxPrecedenceToken . int firstNumber . r e p l a c e T o k e n s W i t h R e s u l t ( mathExp . Index = index . Index . IntValue . maxPrecedenceToken . Precedence >= precedence ) { precedence = token . secondNumber .Cap´ ıtulo 9 while ( mathExp . } } public class Resolver { Lexer _lexer . TokenPrecedence _precedence .1] = new MathNumber ( result . tokens . IntValue . foreach ( MathToken token in tokens ) { index ++. } return (( MathNumber ) mathExp [0]). secondNumber = (( MathNumber ) mathExp [ op . int result ) { tokens [ indexOfOperator . Index + 1]). Resolve ( firstNumber . tokens . _calcProxy ). secondNumber . int index = -1. MathOperator op = ( MathOperator ) token . int result = op . } public class Precedence : TokenPrecedence { public MathToken GetMaxPrecedence ( List < MathToken > toke ns ) { int precedence = 0.1]). } } 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 ¦ § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 ¥ ¿Movemos el c´lculo de precedencia a una clase espec´ a ıfica?: public interface TokenPrecedence { MathToken GetMaxPrecedence ( List < MathToken > tokens ). firstNumber = (( MathNumber ) mathExp [ op . IntValue . Precedence . } private void r e p l a c e T o k e n s W i t h R e s u l t ( List < MathToken > tokens . Index . Count > 1) { MathToken token = GetMaxPrecedence ( mathExp ). maxPrecedenceToken = token . op . RemoveAt ( indexOfOperator ). ToSt ring ()). result ). MathToken maxPrecedenceToken = null . RemoveAt ( indexOfOperator ).

El motivo es que se est´n haciendo varias a llamadas al lexer y el mock estricto dice que s´lo le hab´ avisado de una. es poco importante.. ya que hay dos llamadas. CalculatorProxy calcProxy . p´gina 148 a Ver Cap´ ıtulo 7 en la p´gina 111 a 219 . Pero es demasiado trabajo.Cap´ ıtulo 9 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 public Resolver ( Lexer lexer .. Count > 1) { MathToken token = _precedence .. GetTokens ( expression ). _precedence = precedence . List < MathToken > mathExp = _lexer . while ( mathExp . o ıan Para corregir el test podr´ partir en dos. Se ha convertido en una depedencia dif´ de eludir. ıa A estas alturas el test no merece la pena porque toda la l´gica de negocio de o parser utiliza a lexer. } public int R e s o l v e S i m p l e E x p r e s s i o n ( string expression ) { if (! MathRegex . un test que comprueba que se usa.. . Generalmente los typecasts son un indicador de algo Sustituci´n de Liskov o que no se est´ haciendo del todo bien. Recordemos c´mo qued´ el m´todo: a o o e § 1 2 3 4 5 5 6 9. . GetMaxPrecedence ( mathExp ). Simplemente lo elimino. IsExpressionValid ( expression )) throw new I n v a l i d O p e r a t i o n E x c e p t i o n ( expression ). TokenPrecedence precedence ) { _lexer = lexer . Repasemos el diagrama de clases: ¿Qu´ sensaci´n da el diagrama y el c´digo? Personalmente me gusta e o o como ha quedado el diagrama pero hay c´digo que da un poco de mal olor. } } ¦ ¥ Hubo que rectificar un poquito los tests para que compilasen pero fue cuesti´n o de un minuto. Ahora ejecuto todos los tests y falla uno que hab´ quedado por ah´ penıa ı diente: ParserWorksWithLexer5 . Ya tenemos todos los tests en verde.51: Resolver public int R e s o l v e S i m p l e E x p r e s s i o n ( string expression ) { if (! MathRegex . o Se trata de ResolveSimpleExpression ya que est´ violando el Principio de a 6 . IsExpressionValid ( expression )) throw new I n v a l i d O p e r a t i o n E x c e p t i o n ( expression ). _calcProxy = calcProxy . con ıcil lo cual. Una la siıa mular´ tipo stub y la otra tipo mock y viceversa.

op . IntValue . IntValue . r e p l a c e T o k e n s W i t h R e s u l t ( mathExp . _calcProxy ). List < MathToken > mathExp = _lexer . Count > 1) { MathToken token = _precedence . podr´ tratar a todos los tokens por igual: ıa § 1 2 3 4 5 6 7 8 9. Index . GetTokens ( expression ).1]). Index . int firstNumber . Count > 1) { 220 . result ). } return (( MathNumber ) mathExp [0]). while ( mathExp . while ( mathExp . firstNumber = (( MathNumber ) mathExp [ op . secondNumber . IsExpressionValid ( expression )) throw new I n v a l i d O p e r a t i o n E x c e p t i o n ( expression ). secondNumber = (( MathNumber ) mathExp [ op . MathOperator op = ( MathOperator ) token . int result = op .Cap´ ıtulo 9 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 List < MathToken > mathExp = _lexer . } ¦ ¥ Si refactorizo de manera que Resolve queda en la clase base (Pull up method). GetMaxPrecedence ( mathExp ). Resolve ( firstNumber . IntValue . Index + 1]).52: Resolver public int R e s o l v e S i m p l e E x p r e s s i o n ( string expression ) { if (! MathRegex . GetTokens ( expression ). secondNumber .

Resolve ()... Resolve (). int secondNumber = mathExp [ op . } return mathExp [0]. Index + 1].1]. } } 221 . Parse ( _token ).. } public class MathNumber : MathToken { public MathNumber () : base (0) {} public MathNumber ( string token ) : base ( token ) {} public int IntValue { get { return Int32 . Index . r e p l a c e T o k e n s W i t h R e s u l t ( mathExp .} public abstract int Resolve (). protected int _index = -1. CalcProxy = _calcProxy . op . } 9 10 11 12 13 14 15 16 17 ¦ § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 ¥ public abstract class MathToken { protected int _precedence = 0.. protected string _token = String . GetMaxPrecedence ( mathExp ). result ). op . } } public override int Resolve () { return IntValue . public MathToken ( string token ) { _token = token . } public int Index {. Empty . int result = op ..} public int Precedence {. Resolve ( firstNumber .} public string Token {. int firstNumber = mathExp [ op . secondNumber ). Index .. } public MathToken ( int precedence ) { _precedence = precedence .Cap´ ıtulo 9 MathOperator op = _precedence . Resolve ().

Al fin y al cabo queremos trabajar con instancias concretas. No hemos respetado el principio de Liskov del todo.Cap´ ıtulo 9 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 public abstract class MathOperator : MathToken { protected int _firstNumber . } } public override int Resolve () { return Resolve ( _firstNumber . Al convertir Matho Token en abstracta nos aseguramos que nadie crea instancias de esa clase. } public class Precedence : TokenPrecedence { public MathOperator GetMaxPrecedence ( List < MathToken > t okens ) { . } } ¦ ¥ N´tese que CalProxy ha sido movido dentro del operador. La clase que gestiona precedencia sigue necesitando una conversi´n expl´ o ıcita.. } set { _calcProxy = value . } public interface TokenPrecedence { MathOperator GetMaxPrecedence ( List < MathToken > tokens ). } set { _firstNumber = value . } } public CalculatorProxy CalcProxy { get { return _calcProxy . return ( MathOperator ) maxPrecedenceToken . public MathOperator ( int precedence ) : base ( precedence ) { } public int FirstNumber { get { return _firstNumber .. } } public int SecondNumber { get { return _secondNumber . } public abstract int Resolve ( int a . int b ). Vamos a darle una 222 . } set { _secondNumber = value . _secondNumber ). protected CalculatorProxy _calcProxy . protected int _secondNumber .

1]. _nextToken . Count > 1) { MathToken op = _precedence . PreviousToken = mathExp [ op . while ( mathExp . GetTokens ( expression ). GetMaxPrecedence ( mathExp ).53: Resolver public int R e s o l v e S i m p l e E x p r e s s i o n ( string expression ) { if (! MathRegex . } public MathToken PreviousToken { get { return _previousToken . protected int _index = -1. List < MathToken > mathExp = _lexer . NextToken = mathExp [ op . } set { _previousToken = value . op .Cap´ ıtulo 9 vuelta de tuerca m´s: a § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 9. protected string _token = String . IsExpressionValid ( expression )) throw new I n v a l i d O p e r a t i o n E x c e p t i o n ( expression ). protected MathToken _previousToken . } } public MathToken NextToken { get { return _nextToken . int result = op . Resolve (). public MathToken ( string token ) { _token = token . } public MathToken ( int precedence ) { _precedence = precedence . r e p l a c e T o k e n s W i t h R e s u l t ( mathExp . op . Resolve (). } return mathExp [0]. Index + 1]. Empty . } } public string Token 223 . result ). op . } ¦ § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 ¥ public abstract class MathToken { protected int _precedence = 0. } set { _nextToken = value . } } public int Index { get { return _index . Index . } set { _index = value . Index .

Llegado este punto no dejar´ que el constructor ıa crease la instancia del colaborador porque sino. R esolve ()). public MathOperator ( int precedence ) : base ( precedence ) { _calcProxy = new CalcProxy ( new Validator ( -100 . El constructor de Matu hOperator crea el proxy. } } public int Precedence { get { return _precedence . int b ). Resolve () . _nextToken . } set { _calcProxy = value . _calProxy = container . } public abstract int Resolve ( int a . En ese caso dentro del constructor se har´ una llamada al contenedor para pedirle una instancia del colaborador ıa de manera que ´ste la inyecte.Windsor. Es el momento perfecto para introducir un contenedor de inyecci´n o de dependencias tipo Castle. perdemos la inversi´n del o control. Resolve < CalculatorProxy >( " simpleProxy " ). new Calculator ()). 100) . } public abstract class MathOperator : MathToken { protected CalculatorProxy _calcProxy . } } public override int Resolve () { return Resolve ( _previousToken . e § 1 2 3 4 5 6 7 8 9. } ¦ ¥ 224 . } public CalculatorProxy CalcProxy { get { return _calcProxy .54: Supuesto MathOperator public MathOperator ( int precedence ) : base ( precedence ) { WindsorContainer container = new WindsorContainer ( new XmlInterpreter ()). } 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 ¦ ¥ Resolver ya no necesita ning´n CalculatorProxy. } } public abstract int Resolve ().Cap´ ıtulo 9 { get { return _token .

return expBuilder . expBuilder . expBuilder . if ( openedParenthesis != 0) throw new I n v a l i d O p e r a t i o n E x c e p t i o n ( " Parenthesis do not match " ). return . Voy a utilizar un ıa a a objeto para agrupar datos y funcionalidad: § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 9. getExpressions ( expBuilder . ExpressionBuilder expBuilder = ExpressionBuilder . InputText = expression . AllExpressions . } expBuilder . AllExpressions . no tenemos que tocar el c´digo fuente. AllExpressions ). openedParanthesis . Create (). FixExpressions ( expBuilder . 225 . ref openedParenthesis ). S u b E x p r e s s i o n E n d F o u n d (). _fixer . Una funci´n no a o deber´ tener m´s de uno o dos par´metros. Por o tanto el m´todo Resolve del contenedor buscar´ qu´ clase concreta hemos e a e configurado para ser instanciada cuando usamos como par´metro la interfaz a CalculatorProxy. if ( currentChar == OPEN_SUBEXPRESSION ) { openedParanthesis ++. varias candidatas a ser inyectadas como dependencias. getExpressions ( expBuilder . P r o c e s s N e w S u b E x p r e s s i o n () .-. a o Lo ultimo que me gustar´ refactorizar es la funci´n recursiva getEx´ ıa o pressions de MathLexer. ThereAreMoreChars ()) { char currentChar = expBuilder . } else if ( currentChar == CLOSE_SUBEXPRESSION ) { expBuilder . a lo sumo tres. Hemos podido eliminar la conversi´n de tipos en la funci´n que obtiene o o la m´xima precedencia y ahora el c´digo tiene mucha mejor pinta.55: MathLexer public List < MathExpression > GetExpressions ( string expr ession ) { int openedParenthesis = 0. de tal manera que si existen varias clases o que implementan una interfaz. s´lo tenemos que modificar la configuraci´n para reemo o plazar una dependencia por otra. Tiene demasiados par´metros. } private void getExpressions ( ExpressionBuilder expBuild er . S u b E x p r e s s i o n E n d F o u n d (). ref int openedParanthesis ) { while ( expBuilder . es decir. ref openedParanthesis ).Cap´ ıtulo 9 Los contenedores de inyecci´n de dependencias se configuran a trav´s de un o e fichero o bien mediante c´digo. GetCurrentChar (). } else expBuilder . Sort (). A d d S u b E x p r e s s i o n C h a r ().

Expression += _inputText [ _currentIndex ]. Order == -1) _subExpression . } set { _inputText = value . return builder . return builder . Empty . Empt y ). ToString (). SubExpression = new MathExpression ( String . private static List < MathExpression > _allExpressions . } public string InputText { get { return _inputText . updateIndex (). InputText = _inputText . builder . } public void A d d S u b E x p r e s s i o n C h a r () { _subExpression . } } public char GetCurrentChar () { 226 . builder .Cap´ ıtulo 9 } 38 ¦ § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 ¥ public class ExpressionBuilder { private static string _inputText . } public ExpressionBuilder P r o c e s s N e w S u b E x p r e s s i o n () { ExpressionBuilder builder = new ExpressionBuilder (). updateIndex (). SubExpression = new MathExpression ( String . updateIndex (). builder . CurrentIndex = 0. AllExpressions = new List < MathExpression >(). builder . Order = _currentIndex . private ExpressionBuilder () { } public static ExpressionBuilder Create () { ExpressionBuilder builder = new ExpressionBuilder (). Empt y ). } public void S u b E x p r e s s i o n E n d F o u n d () { _allExpressions . private static int _currentIndex = 0. } public bool ThereAreMoreChars () { return _currentIndex < MaxLength . Add ( _subExpression ). builder . private MathExpression _subExpression . if ( _subExpression . InputText = String . builder .

c´digo m´s claro y casi todos los tests pasando. } set { _subExpression = value . } set { _allExpressions = value . } } private void updateIndex () { _currentIndex ++. Tenemos toda la bater´ de test en verde y ı o o ıa la refactorizaci´n terminada por el momento. " 1 " ).Cap´ ıtulo 9 return _inputText [ _currentIndex ]. Length . failIfOtherSubExpressionThan ( expressions . " 2 + 2 " . o o a El test ParserWorksWithCalcProxy se ha roto ya que el parser no recibe ning´n proxy. ¡Estupendo!. } } public List < MathExpression > AllExpressions { get { return _allExpressions .56: LexerTests [ Test ] public void G e t C o m p l e x N e s t e d E x p r e s s i o n s () { List < MathExpression > expressions = _lexer . " + " . GetExpressions ( " ((2 + 2) + 1) * (3 + 1) " ). ¿Ser´ parser capaz de operar esa a lista de subexpresiones? [ Test ] public void P r o c e s s C o m p l e x N e s t e d E x p r e s s i o n s () 227 . } set { _currentIndex = value . " 3 + 1 " . } } public MathExpression SubExpression { get { return _subExpression . } ¦ ¥ § 1 2 Luz verde sin tocar el SUT. Nos ha prestado buen servicio hasta u aqu´ pero ya termin´ su misi´n. o Mientras resolv´ los ultimos tests me vino a la mente un ejemplo m´s ıa ´ a rebuscado que apunt´ en la libreta y que ahora expreso de forma ejecutable: e § 1 2 3 4 5 6 7 8 9. } public int MaxLength { get { return _inputText . " * " . Es momento de eliminarlo. } } public int CurrentIndex { get { return _currentIndex . } } 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 ¦ ¥ Refactorizaci´n hecha.

va adapt´ndose a los rea o a quisitos de una manera org´nica. A lo largo de este a cap´ ıtulo hemos resuelto un problema cl´sico de la teor´ de compiladores pero a ıa de manera emergente. nadie tiene capacidad para escribir del tir´n el c´digo perfecto que los resuelve todos. A menudo en mis cursos de a TDD. Hay que afinar m´s MathParser. para un problema que lleva tantos a˜os resuelto como es el de los analizadores de c´digo. La segunda o o es que aun conociendo todos los ejemplos posibles. es mostrar con ejemplos como se hace un desarrollo dirigido por ejemplos.. valga la redundancia. Luego se ganan horas. hubiera n o buscado alg´n libro de Alfred V. junto con los dem´s ejemplos pendientes a de nuestra libreta: # Aceptaci´n . entonces har´ a ıamos programas igual que hacemos edificios: es una situaci´n ut´pica. Es m´s humano o o a y por tanto m´s productivo progresar paso a paso. Como habr´ observado el c´digo va mutando. MathParser tiene que colaborar m´s con MathLee a xer y tal vez ir resolviendo subexpresiones al tiempo que se van encontrando.. ¿Hacer software o e de calidad es perder el tiempo? Realmente no lleva tanto tiempo refactorizar. d´ y hasta meses o ıas 228 . La primera es que si conoci´semos absolutamente el 100 % de e los detalles de un programa y adem´s fuesen inamovibles. produce ERROR o # Aceptaci´n . Sin embargo."3 / 2". a Tambi´n me comentan a veces que se modifica mucho c´digo con las ree o factorizaciones y que eso da sensaci´n de p´rdida de tiempo. Para esa frase hay dos respuestas. devuelve 20 o # Aceptaci´n . no lo voy a hacer yo. Pero el fin de a a este libro es docente. haciendo TDD. A esa persona le parece que el diagrama de clases que resulta no se corresponde con el modelado mental de conceptos que tiene en la cabeza y le cuesta admitir que las clases no necesariamente tienen una correspondencia con esos conceptos que manejamos los humanos. devuelve 0 o Hemos llegado al final del segundo cap´ ıtulo pr´ctico. } 3 4 5 6 7 ¦ ¥ Pues no. t´ ıpicamente es cuesti´n de minutos."2 + -2". Aho o alg´n colega suyo y habr´ utilizado u u ıa directamente los algoritmos que ya est´n m´s que inventados.Cap´ ıtulo 9 { Assert . Se nos est´ perdiendo la precedencia de las operaciones a con par´ntesis anidados. Lo dejo a abierto como ejercicio para el lector. cuando llegamos al final de la implementaci´n del ejemplo alguien o dice."((2 + 2) + 1) * (3 + 1)". no puede. incremental. _parser . “si hubiera sabido todos esos detalles lo hubiese implementado de otra manera”. ProcessExpression ( " ((2 + 2) + 1) * (3 + 1) " )). AreEqual (20 . Ciertamente.

O. respetar con conciencia el ultimo paso del algoritmo TDD que dice que debemos eliminar el c´digo ´ o duplicado y si es posible. con una cierta a o a calidad. 229 .Cap´ ıtulo 9 cuando hay que mantener el software. aunque en la libreta o o me apunto que debo cambiarla tan pronto como sepa hacerlo. Todo lo contrario a la estresante tarea de hacer modificaciones sobre c´digo sin tests. La artesan´ sigue jugando un ıa papel muy importante en esta t´cnica de desarrollo.D. modificar el c´digo es o una actividad con la que se disfruta. Al disponer de una bater´ de tests de ıa tanta calidad como la que surge de hacer TDD bien. C´digo f´cil de mantener. Para m´ esos m´ ı ınimos son los principios S. o e El c´digo que tenemos hasta ahora no es perfecto y mi objetivo no es o que lo sea. Generalmente los m´todos est´ticos rompen con la orientaci´n a objetos y e a o nos hacen volver a la programaci´n funcional. o a o podemos esperar a terminar de refactorizar. A pesar de que no tiene mal aspecto es posible hacer mejoras todav´ Lo que pretendo mostrar es que el dise˜o emergente genera c´digo ıa. Lo mejor ser´ dividir en clases e o ıa cuando est´n claras las distintas responsabilidades que tiene y as´ arreglar e ı el problema de que tantas otras clases dependiesen de la misma entidad.L. Se deben de evitar a toda o costa porque se nos hace imposible probar cuestiones como la interacci´n o entre objetos.I. n o que est´ preparado para el cambio. En el pr´ximo cap´ o ıtulo afrontaremos ejemplos que nos exigir´n dobles de a prueba y terminaremos integrando nuestro c´digo con el resto del sistema pao ra visitar todos los escenarios que se presentan en el desarrollo de aplicaciones “empresariales”. aunque no siempre es necesario que se cumplan completamente cada vez que se refactoriza: si la experiencia nos avisa de que tendremos que hacer cambios en el futuro cercano (en los pr´ximos tests) que nos dar´n oportunidad de seguir evolucionando el c´digo. puesto que un conjunto de m´todos est´ticos no constituyen e a un objeto. nadie tiene tiempo de entretenerse eternamente a retocar o un bloque de c´digo pero desde luego hay que cumplir unos m´ o ınimos. No es decisivo que en la etapa de refactorizaci´n lleguemos a un o c´digo impoluto. e Debo reconocer que el hecho de que MathRegex sea un conjunto de m´todos tipo static es una mala soluci´n. o Lo ideal es refactorizar un poquito a cada vez. Sin embargo en el punto en que estamos no he visto con claridad c´mo refactorizar y he decidido preservar esa soluci´n. mejorar el c´digo tambi´n.

Cap´ ıtulo 9 230 .

ı Atendiendo a los requisitos que se exponen al comienzo del cap´ ıtulo 8. no necesitamos acceder a ning´n sistema de ficheros para desarrollar nuestra u soluci´n sino s´lo a una base de datos. a e La utilidad mas com´n de una aplicaci´n es la manipulaci´n de datos.Cap´tulo ı 10 Fin del proyecto . Si ha seguido los cap´ e n ıtulos anteriores escribiendo c´digo frente a la pantalla. para mostrar en detalle o o como podemos hacer integraci´n a la TDD. En todo momento hemos trabajado con variau bles en memoria. Nuestra libreta contiene lo a siguiente: 231 . a la par que le´ el libro. o ıa ya habr´ comprendido de qu´ va TDD. No obstante. M´s adelante trabajaremos con la base de datos. claridad y atomicidad. no ha habido acceso al sistema de ficheros ni al sistema de gesti´n de base de datos. u o o algo que todav´ no sabemos hacer de manera emergente. Por ello vamos a ıa dar un giro a la implementaci´n de nuestro problema tratando criterios de o aceptaci´n que requieren que nuestros objetos se integren con otros objetos o externos. imaginemos que el usuario nos o ha pedido que la lista de alumnos del juego se guarde en un fichero en disco. Hemos respetado las propiedades de r´pidez. a inocuidad.Test de Integraci´n o Hasta ahora nos hemos movido por la l´gica de negocio mediante ejemo plos en forma de test unitario. aunque la granularidad de algunos tests no fuese m´ ınima en alg´n caso. que s´ tienen acceso a una base de datos. Hemos trabajado en el escenario ideal para o comprender de qu´ trata el dise˜o emergente.

el cliente no nos ha contado c´mo se modifican y se borran usuarios. Alberto). Como podr´ observar. Alberto) # Aceptaci´n: o Obtener todas las expresiones introducidas por el usuario Alberto ([Usuario(1. modificar y borrar usuarios o # Aceptaci´n: La clave de acceso est´ encriptada o a clave plana: pantera. 4]. Sabemos que al crearlo sus a atributos se guardan en el fichero de datos porque se tiene que listar el nick y el identificador de usuario pero de momento. Yeray)] Si usuarios del sistema son Esteban y Eladio. 4. Usuario(1. Alberto) "2 + 1".Cap´ ıtulo 10 # Aceptaci´n: o Ruta y nombre del fichero de datos son especificados por el usuario Fichero datos = C:datos. 3]) El criterio de aceptaci´n de que los usuarios se pueden modificar y borrar o no est´ bien escrito. o o e Empieza a oler a integraci´n porque la definici´n de los ejemplos se ayuda o o 232 . clave: pantera # Aceptaci´n: Crear.txt # Aceptaci´n: Un usuario tiene nick y clave de acceso: o nick: adri.txt Fichero datos = /home/jmb/datos. (1. no llega a haber ejemplos. Fran). 3. Alberto). Lo dejaremos as´ para empezar o ı cuanto antes con la implementaci´n. "2 + 1". fichero contendr´: a # Begin 0:Esteban:2d58b0ac72f929ca9ad3238ade9eab69 1:Eladio:58e53d1324eef6265fdb97b08ed9aadf # End Si a~adimos el usuario Alberto al fichero anterior: n # Begin 0:Esteban:2d58b0ac72f929ca9ad3238ade9eab69 1:Eladio:58e53d1324eef6265fdb97b08ed9aadf 2:Alberto:5ad4a96c5dc0eae3d613e507f3c9ab01 # End Las expresiones introducidas por el usuario 1 se guardan junto con su resultado "2 + 2". los cuales no llegan a adentrarse en c´mo sino en qu´. [Usuario(1. hash MD5: 2d58b0ac72f929ca9ad3238ade9eab69 # Aceptaci´n: Si los usuarios que existen son Fran y Yeray o El listado de usuarios del sistema devuelve [(0. en la libreta o a vuelve a haber tests de aceptaci´n y tests de desarrollo. Usuario(1. "2 + 2". Los tests de desarrollo o son una propuesta que responde a c´mo queremos implementar los tests de o aceptaci´n.

tenemos que pensar que la parte derecha ya est´ implementada. a Tenemos que dise˜ar la frontera y pensar que ya hay alguna clase que implen menta ese contrato y que es capaz de recibir y enviar datos al sistema.1. Gr´ficamente pienso en ello como la parte a a la izquierda y la parte a la derecha. Una interfaz es un contrato al fin y al cabo. 10. a Para que la parte izquierda contenga el mayor volumen de l´gica de negoo cio posible. Es decir. llega un punto a en que inevitablemente tenemos que romper las reglas de los tests unitarios y modificar el estado del sistema mediante una escritura en disco. trabajar en el ´mbito de los tests unitarios es r´pido a a y productivo.1. en este caso de un fichero y tambi´n e de una base de datos.Cap´ ıtulo 10 10. En el caso de lenguajes interpretados no se necesitan interfaces. ¿Por d´nde empezamos a trabajar? Lo primero es o delimitar la frontera entre nuestros objetos y los objetos de terceros. simplemente habr´ clases. La frontera entre tests unitarios y tests de integraci´n o de muchos datos de contexto (fixtures). Tal frontera se delimita mediante interfaces en lenguajes como Java y C#. utilizando mocks y stubs para hablar con ellas. 233 . m´s f´cil ser´ detectar y corregir ıa a a a problemas. La frontera entre tests unitarios y tests de integraci´n o Como hemos visto. sin que realmente toquen el sistema. La mejor forma de hacerlo es estableciendo una frontera a modo de contrato entre las dos partes: la que se limita a trabajar con datos en memoria y la que modifica el estado del sistema. adem´s de ampliar funcionalidad. Cuanta m´s l´gica de negocio podamos mantener controlada a o bajo nuestra bater´ de tests unitarios. Sin embargo. nos mantenemos en el ´mbito de los tests unitarios considerando que a las clases que modifican el estado del sistema ya existen.

FileHandler handlerMock = MockRepository . Expect ( x = > x . CreateFile ( filePath )).1. que devolver´ un objeto de tipo UserFile. ıan Aunque la plataforma sobre la que trabajemos nos ofrezca ya interfaces que nos puedan parecer v´lidas para establecer la frontera. Luego. GenerateMock < FileHandler >(). Return ( new UserFile ()). o a que las clases efectivamente se integran bien con el sistema externo. handlerMock . manager . Para ejecutar los tests de integraci´n necesitaremos un o entorno de preproducci´n que se pueda montar y desmontar antes y despu´s o e de la ejecuci´n de la bater´ de tests de integraci´n. que probar´n ı. V e r i f y A l l E x p e c t a t i o n s (). con unos delimitadores de comienzo y fin de fichero y unos delimitadores de campos para los atributos de los usuarios. Por un lado veo la necesidad de crear un fichero y por otra la de leerlo e interpretarlo. Empiezo a resolver el problema por la parte izquierda: § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 [ TestFixture ] public class UserManagementTests { [ Test ] public void ConfigUsersFile () { string filePath = " / home / carlosble / data . SetUsersFile ( filePath ). UsersStorageManager manager = new UsersStorageManager ( handlerMock ). La frontera entre tests unitarios y tests de integraci´n o Cap´ ıtulo 10 Una vez llegamos ah´ vamos a por los tests de integraci´n. Existir´ una interfaz FileHandler con un m´todo para a a e crear el fichero. En los tests de desarrollo se aprecia que el fichero de usuarios contiene una l´ ınea por cada usuario. txt " . Nada m´s terminar a de escribir el test me he preguntado qu´ pasar´ si hay alg´n problema con e ıa u la creaci´n del fichero (ej: insuficiencia de permisos) y lo he apuntado en la o 234 . Hay un test de aceptaci´n que dice que crearemos el fichero de datos de o o usuario en la ruta especificada por alguien. escribiendo y leyendo datos. handlerMock . } } ¦ ¥ Mi planteamiento ha sido el siguiente: Quiero una clase para gesti´n de datos o de usuarios que tenga un m´todo para definir cu´l es el fichero donde se e a guardar´n los datos. ya que dichos tests o ıa o podr´ dejar el sistema en un estado inconsistente.10. Estoy obligando a a que mi SUT colabore con el manejador de ficheros. se recomienda dea finir nuestra propia interfaz independiente. la clase que implemente la interfaz frontera podr´ hacer uso de todas las herramientas que la plataforma a le ofrezca. Vamos a verlo con ejemplos mediante la implementaci´n de nuestra soluo ci´n.

resulta que ante posibles defectos en las clases que implementen FileHandler. CreateFile ( path ). POSIX o alg´n otro para evitar problemas con las rutas. Lo primero que se me viene a la cabeza antes de empezar es que tendremos que cuidarnos de saber si estamos en un entorno MS Windows. Ahora podemos ir desarrollando todos los casos que nos vayan haciendo falta. La frontera entre tests unitarios y tests de integraci´n o libreta. } public void SetUsersFile ( string path ) { _handler .Cap´ ıtulo 10 10. Acabamos de definir una frontera mediante la interfaz FileHandler. El a SUT est´ bien aislado. El subconjunto de pr´ximas tareas de la libreta es el siguiente: o Crear un fichero en la ruta especificada Resolver casos en que no se puede crear Permisos insuficientes Ruta no existe Leer un fichero de texto l´nea a l´nea ı ı Voy a ocuparme de que el test pase lo antes posible.Mocks que lance una excepci´n por e o falta de permisos y nos ocuparemos de que nuestra clase gestora responda adecuadamente. etc 235 . Stubs. como por ejemplo el de que no haya permisos para crear el fichero. } } ¦ ¥ Luz verde.1. Lo apunto en u 1 Mocks. como siempre: § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class UsersStorageManager { FileHandler _handler . a ¿C´mo implementamos los tests de integraci´n para la clase que cumple o o con el contrato FileHandler? Para esta funcionalidad me parece bien seguir usando NUnit. Simularemos tal caso dici´ndole a Rhino. El juego consiste en utilizar los dobles1 para que produzcan un determinado comportamiento y nosotros podamos programar nuestra l´gica de negocio acorde a ese comportamiento. Por un lado estamos trabajando con tests unitarios y por otro. nuestro test no se romper´. o El beneficio que obtenemos es doblemente bueno. public UsersStorageManager ( FileHandler handler ) { _handler = handler .

1. if (! File . Fail ( " File was not created " ).10. Por este motivo a veces la etapa de afirmaci´n de un test o de integraci´n se puede llegar a complicar bastante.Net. o Ser´ absurdo invocarlo para hacer la comprobaci´n de que el fichero se ha ıa o creado ya que eso no garantiza que efectivamente el sistema de ficheros se ha modificado. Voy a crear un nuevo proyecto dentro de la soluci´n para los tests o de integraci´n (otra DLL) de modo que no se ejecuten cada vez que lance o los tests unitarios. Exists ( path )) { Assert . Hay que cuidarse de no hacer la comprobaci´n mediante el mismo SUT cuando se trata de integrao ci´n. La libreta dice: Crear un fichero en la ruta especificada Resolver casos en que no se puede crear Permisos insuficientes Ruta no existe Leer un fichero de texto l´nea a l´nea ı ı Integraci´n: o Crear el fichero en Windows Crear el fichero en Ubuntu Crear el fichero en MacOS § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 Voy con el primero de ellos: namespace IntegrationTests { [ TestFixture ] public class FileHandlerTests { [ Test ] public void C r e a t e F i l e W i t h W i n d o w s P a t h () { string path = @ " c :\ data . } } } } ¦ ¥ He creado una clase UserFileHandler que implementa la interfaz FileHandler. Llamo a su m´todo de crear fichero y luego compruebo que e el fichero existe mediante la API que me ofrece . txt " . handler . CreateFile ( path ). supongamos que el SUT tiene un metodo tipo IsFileOpened. Vamos a hacer que el o primer test pase: 236 . La frontera entre tests unitarios y tests de integraci´n o Cap´ ıtulo 10 la libreta. UserFileHandler handler = new UserFileHandler (). Es decir.

UserFileHandler handler = new UserFileHandler (). Exists ( path )) { Assert . CreateFile ( path ). Create ( path ). Voy a aumentar un poco m´s la u a libreta: Crear un fichero en la ruta especificada El que implemente DataFile contiene un FileStream con acceso al fichero creado Resolver casos en que no se puede crear Permisos insuficientes Ruta no existe Leer un fichero de texto l´nea a l´nea ı ı Integraci´n: o Crear el fichero en Windows Crear el fichero en Ubuntu Crear el fichero en MacOS El m´todo File. La frontera entre tests unitarios y tests de integraci´n o public class UserFileHandler : FileHandler { public DataFile CreateFile ( string path ) { File . Stream ). Me llama la atenci´n que no estamos haciendo nada con el ficheo ro.1. if (! File . FileStream tiene un campo Handle que es una referencia al fichero en disco. No estamos creando ning´n DataFile.Net. DataFile dataFile = handler . } Assert .Cap´ ıtulo 10 § 1 2 3 4 5 6 7 8 10. } ¦ ¥ Vamos a buscar la luz verde: § 1 2 3 public class UserFileHandler : FileHandler < UserFile > { public UserFile CreateFile ( string path ) 237 . Todo eso es parte de e la API de . IsNotNull ( dataFile . } } ¦ ¥ Luz verde. txt " .Create devuelve un FileStream. return null . Fail ( " File was not created " ). En base a esto voy a modificar mi test de integraci´n: o § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 [ Test ] public void C r e a t e F i l e W i t h W i n d o w s P a t h () { string path = @ " c :\ data .

Return ( new UserFile ()). FileHandler < UserFile > handlerMock = MockRepository . Stream = stream . } } ¦ ¥ El test ya pasa. handlerMock . Expect ( x = > x . } } public class UserFile : DataFile { FileStream _stream = null . userFile . Create ( path ).1. He decidido introducir gen´ricos en el dise˜o lo cual me ha e n llevado a hacer algunas modificaciones: § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public interface FileHandler <T > where T : DataFile { T CreateFile ( string path ). } public interface DataFile { FileStream Stream { get . public FileStream Stream { get { return _stream . } set { _stream = value . La frontera entre tests unitarios y tests de integraci´n o Cap´ ıtulo 10 4 5 6 7 8 9 10 { FileStream stream = File . UsersStorageManager manager = new UsersStorageManager ( handlerMock ). handlerMock . set . SetUsersFile ( filePath ). } } } ¦ § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ¥ namespace UnitTests { [ TestFixture ] public class UserManagementTests { [ Test ] public void ConfigUsersFile () { string filePath = " / home / carlosble / data . manager . V e r i f y A l l E x p e c t a t i o n s (). UserFile userFile = new UserFile (). } } } ¦ § 1 ¥ namespace IntegrationTests 238 .10. GenerateMock < FileHandler < UserFile > >(). CreateFile ( filePath )). txt " . return userFile .

Net. Me plantear´ utilizar m´quinas o ıa a virtuales para lanzar cada bater´ de tests.carlosble. Empty . txt " . namespace IntegrationTests { [ TestFixture ] public class FileHandlerTests { private string getPlatformPath () { System . if (! File . } } } ¦ ¥ § 1 2 3 4 5 6 7 8 9 10 Los gen´ricos funcionan pr´cticamente igual en C# que en Java.1. el unitario y el de integraci´n. por lo que no tenemos que preocuparnos de que el sistema se comporte distinto en Windows que en Linux. hace tiempo escrib´ un art´ e ı ıculo en espa˜ol sobre ello2 . OSVersion . Fail ( " File was not created " ). volvemos a tener los dos tests pasando. quiz´s deber´ o a ıamos de separar los tests de integraci´n en distintas DLL. Si quiere leer m´s sobre e o a gen´ricos en .Net se basa en un est´ndar que implementa tambi´n el a e framework Mono3 .Cap´ ıtulo 10 { 10. qu´ posibles excepciones se pueden producir y escribir e tests que se ejecuten con el mismo resultado en cualquier plataforma. OperatingSystem osInfo = System . aunque su e a sintaxis me parece m´s clara en C#. ¿Se lo hab´ planteado? ıa ıa Afortunadamente . n Bien. Tan solo tenemos que mirar la API de File para ver. DataFile dataFile = handler . UserFileHandler handler = new UserFileHandler (). string path = String . CreateFile ( path ). IsNotNull ( dataFile .com/?p=257 http://www. La frontera entre tests unitarios y tests de integraci´n o 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 [ TestFixture ] public class FileHandlerTests { [ Test ] public void C r e a t e F i l e W i t h W i n d o w s P a t h () { string path = @ " c :\ data . Environment . Stream ). otra los que son para Linux y as´ con ı cada sistema. 2 3 http://www. Vamos a arreglar nuestro test de integraci´n para que funcione tambi´n en Linux y o e MacOS.mono-project. De eso trata la integraci´n.com 239 . Si nos paramos a pensarlo bien. Exists ( path )) { Assert . No es importante que sea experto en a gen´ricos si comprende el c´digo que he escrito. } Assert . Una DLL deber´ contener los tests o ıa que son para sistemas MS Windows.

IsNotNull ( dataFile .10. } [ Test ] public void C r e a t e F i l e M u l t i P l a t f o r m () { string path = getPlatformPath (). CreateFile ( path ). } } } ¦ ¥ Perfecto. txt " . } default : { path = @ " C :\ data . break . Primero siempre prefiero abordar el test unitario antes que el de 240 . MacOSX : { path = " / tmp / data . } } return path . } case System . Todo esto es igual de aplicable a Java. La frontera entre tests unitarios y tests de integraci´n o Cap´ ıtulo 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 switch ( osInfo . break . if (! File . Unix : { path = " / tmp / data .1. UserFileHandler handler = new UserFileHandler (). Stream ). PlatformID . } Assert . DataFile dataFile = handler . Platform ) { case System . PlatformID . txt " . Fail ( " File was not created " ). Ya no importa que algunos de los desarrolladores del equipo trabajen con Ubuntu y otros con Windows 7. Exists ( path )) { Assert . txt " . break . ¿Por d´nde vamos? o Crear un fichero en la ruta especificada Resolver casos en que no se puede crear Permisos insuficientes Ruta no existe Leer un fichero de texto l´nea a l´nea ı ı Avancemos un poco m´s en los casos de posibles excepciones creando a el fichero. ya tenemos todas las plataformas que nos interesan por ahora cubiertas.

manager . GetPlatformPath ().Cap´ ıtulo 10 integraci´n: o § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 10. } ¦ ¥ Le he dicho a Rhino. CreateFile ( path ). handlerMock . No olvide que cada a test se centra en una unica caracter´ ´ ıstica del SUT.2: IntegrationTests [ Test ] [ ExpectedException ( typeof ( D i r e c t o r y N o t F o u n d E x c e p t i o n ))] public void C r e a t e F i l e D i r e c t o r y N o t F o u n d () { string path = new NotFoundPath ().3: IntegrationTests public abstract class MultiPlatform { public abstract string GetPOSIXpath (). La frontera entre tests unitarios y tests de integraci´n o 10. Expect ( x = > x .1. public abstract string GetWindowsPath (). UserFileHandler handler = new UserFileHandler (). Estamos en u o verde sin tocar el SUT. public string GetPlatformPath () { System . SetUsersFile ( " " ). N´tese o o que estoy utilizando un stub y no un mock. Vamos a la parte de integraci´n: o § 1 2 3 4 5 6 7 8 9 10 10. CreateFile ( " " )). El c´digo tras la refactorizaci´n que he aplicado. OperatingSystem osInfo = 241 . } ¦ ¥ En realidad he copiado el test desp´es de su refactorizaci´n. es o o el siguiente: § 1 2 3 4 5 6 7 8 9 10. El comportamiento que estoy expresando es que UserStorageManager no va a capturar la excepci´n sino a dejarla pasar. DataFile dataFile = handler . puesto que la verificaci´n de la o llamada al colaborador ya est´ hecha en el test anterior.1: UnitTests [ Test ] [ ExpectedException ( typeof ( D i r e c t o r y N o t F o u n d E x c e p t i o n ))] public void T r y C r e a t e F i l e W h e n D i r e c t o r y N o t F o u n d () { FileHandler < UserFile > handlerMock = MockRepository . UsersStorageManager manager = new UsersStorageManager ( handlerMock ). GenerateStub < FileHandler < UserFile > >().Mocks que lance una excepci´n cuando se invoque a o CreateFile. Throw ( new D i r e c t o r y N o t F o u n d E x c e p t i o n ()).

} } [ TestFixture ] public class FileHandlerTests { [ Test ] public void C r e a t e F i l e M u l t i P l a t f o r m () { string path = new EasyPath (). txt " . 242 . Environment . PlatformID . MacOSX : { path = GetPOSIXpath (). break . GetPlatformPath (). La frontera entre tests unitarios y tests de integraci´n o Cap´ ıtulo 10 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 System . string path = String . Platform ) { case System .10. switch ( osInfo . Empty . } } public class NotFoundPath : MultiPlatform { public override string GetPOSIXpath () { return " / asdflwiejawseras / data . } } public class EasyPath : MultiPlatform { public override string GetPOSIXpath () { return " / tmp / data . Unix : { path = GetPOSIXpath (). txt " . PlatformID . } case System . txt " .1. txt " . break . } default : { path = GetWindowsPath (). break . } public override string GetWindowsPath () { return @ " C :\ a s d f a l s d f k w j e r a s d f a s \ data . OSVersion . } } return path . } public override string GetWindowsPath () { return @ " C :\ data .

org/wiki/Mapeo objeto-relacional 243 . } } ¦ ¥ Para las dem´s excepciones que lance el sistema. al no e capturar las excepciones. Es todo lo contrario que en el a o desarrollo cl´sico. ¿Qu´ es lo e primero que haremos? Escribir un test unitario que utilice un colaborador que cumple una interfaz (contrato). Para ello asumiremos que estamos trabajando con alg´n ORM4 como Hibernate. 4 http://es. nos resulte lo m´s c´moda posible. Al trabajar primero en el test unitario nos cuidamos mucho de que la interfaz que definimos como contrato para hablar con el exterior. Dise˜o emergente con un ORM n 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 DataFile dataFile = handler . entonces escribir´ los tests correspondiene tes en su momento y lugar. GetPlatformPath (). } Assert . Dise˜ o emergente con un ORM n Vamos a irnos directamente al acceso a base de datos. if (! File . CreateFile ( path ). Esta parte queda como ejercicio pendiente para el lector.Cap´ ıtulo 10 UserFileHandler handler = new UserFileHandler (). Nos queda trabajar en la lectura del fichero l´ ınea a l´ ınea.wikipedia. 10. Django ORM o similar. tales como Unauthoa rizedAccessException o ArgumentNullException. UserFileHandler handler = new UserFileHandler ().2. 10. ActiveReu cord. CreateFile ( path ). DataFile dataFile = handler . Fail ( " File was not created " ). IsNotNull ( dataFile . } [ Test ] [ ExpectedException ( typeof ( D i r e c t o r y N o t F o u n d E x c e p t i o n ))] public void C r e a t e F i l e D i r e c t o r y N o t F o u n d () { string path = new NotFoundPath (). la cual contiene las llamadas a los m´todos e que nos devuelven datos. no voy a escribir m´s a tests puesto que s´ que el comportamiento de mis clases es el mismo.2. Exists ( path )) { Assert . Si decido m´s adelante utilizar UserStorageMaa nager desde otra clase y capturar las posibles excepciones para construir un mensaje amigable para el usuario. Stream ). cuando dise˜amos la interfaz y luego al usarla tenemos a n que hacer malabares para adaptarnos a ella.

Martin Fowler les llam´ modelos an´micos por la ausencia de o e l´gica de negocio en ellos. entonces la frontera que delimita nuestros tests unitarios y nuestros tests de integraci´n puede ser la o propia interfaz Session. En lo sucesivo hablar´ de Hibernate para referirme a la versi´n Java y a la e o versi´n . Address. Toda esa larga historia que el cliente nos cont´. Estos modelos son clases sin m´todos funcionales a e pero con atributos designados para alojar datos. ISession sessionMock = 5 6 Data Access Object Java Persistence API 7 Proyecto CastleProject 244 .Net indistintamente. con lo e cual utilizamos modelos en los tests unitarios sin ning´n problema. etc. Siento decirle que no vamos a terminar de implementar todo lo que el cliente nos hab´ ıa pedido en el cap´ ıtulo 8. create y delete. cuyos e par´metros son modelos. era o s´lo para que el lector no pensase que nuestra aplicaci´n de ejemplo no es o o suficientemente “empresarial”. tales como Name. Dise˜o emergente con un ORM n Cap´ ıtulo 10 Las dos formas m´s extendidas de manejar datos mediante un ORM son a las siguientes: Un DAO con m´todos de persistencia + modelos an´micos e e Un modelo con atributos para datos y m´todos de persistencia e Ambas tienen sus ventajas e inconvenientes. Ya sabemos que mediante un framework de mocks podemos simular que este DAO invoca a sus m´todos de persistencia. vamos a abandonar el ejemplo que ven´ ıamos desarrollando desde el cap´ ıtulo 8 y a enfocarnos sobre ejemplos puntuales. Veamos u un pseudo-c´digo de un test unitario: o § 1 2 3 4 5 [ Test ] public void U s e r M a n a g e r C r e a t e s N e w U s e r () { UserModel user = new UserModel (). El DAO es un objeto que cumple la interfaz Session a en el caso de Hibernate e ISession en el caso de NHibernate. En Hibernate y NHibernate el patr´n que se aplica viene siendo el del o DAO m´s los modelos. Con JPA6 los modelos tienen anotaciones para expresar relaciones entre ellos o tipos de datos.Net con Castle. Phone.10. Exactamente lo mismo que ocurre en .ActiveRecord 7 .2. o La segunda opci´n es la mezcla de los dos objetos anteriores en uno solo o que unifica l´gica de persistencia y campos de datos. o Por revisar los distintos casos que se nos pueden plantear a la hora de aplicar TDD. La primera opci´n se como pone de un DAO5 que tiene m´todos como save. o Si nos casamos con Hibernate como ORM.

Muchos frameworks ofrecen la posibilidad de crear una base de datos al vuelo cuando lanzamos los tests de integraci´n y o destruirla al terminar. o Para los tests de integraci´n conviene trabajar con una base de datos o diferente a la de producci´n porque si su ejecuci´n falla. sessionMock . Dise˜o emergente con un ORM n 6 7 8 9 10 11 12 13 14 MockRepository . 245 . IgnoreArguments (). Nos inventamos el DAO nosotros mismos a trav´s de una a e interfaz que hace de frontera. aprovechar´ la potencia de un modelo que e e incorpora l´gica de persistencia.2. sessionMock . o Si se fija bien ver´ que el formato es id´ntico al del acceso a ficheros del a e principio del cap´ ıtulo. UserManager manager = new UserManager ( sessionMock ). Adem´s entre un test y otro se encargan de vaciar las a tablas para obligar a que los tests sean independientes entre s´ ı. En nuestra l´gica de negocio. invocamos al DAO e pasando el modelo. GenerateMock < ISession >(). V e r i f y A l l E x p e c t a t i o n s (). ’ clave1234 ’ ). la base de datos o o puede quedar inconsistente. Pseudoo c´digo: o § 1 2 3 4 5 6 7 public class DAO () { public void Save ( Model model ) { model . SaveNewUser ( ’ Angel ’ . podr´ tomar a o ıa el modelo UserModel. Como el modelo incluye toda la l´gica de persistencia. No se trata de adoptar el patr´n del DAO para todos los casos de manera general. manager . nada ´ m´s. lo utilizar´ y donde no. ¿C´mo abordamos los tests unitarios con ORMs que unifican modelo o y DAO? F´cil.Cap´ ıtulo 10 10. se limita a invocar al modelo. Save ( user )). Para continuar ahora probando la parte de integraci´n. Save (). } ¦ ¥ Estamos diciendo que nuestro UserManager es capaz de recibir un nick y una clave y guardar un registro en la base de datos. He especificado que se ignoren los argumentos porque unicamente me preocupa que se salve el registro. } } ¦ ¥ Habr´ casos en los que quiz´s podamos trabajar directamente con el modelo a a si hablamos de operaciones que no acceden a la base de datos. o el c´digo del DAO es super sencillo. Si lo comprende le valdr´ para todo tipo de tests que a se aproximan a la frontera de nuestras clases con el sistema externo. grabarlo en base de datos y a continuaci´n pedirle al o ORM que lo lea desde la base de datos para afirmar que se guard´ bien. Expect ( x = > x . en lugar de llamar o directamente a los m´todos de persistencia del modelo. Donde o sea que quiera establecer un l´ ımite entre l´gica de negocio dise˜ada con tests o n unitarios.

. Count ). UserModel user = new UserModel (). Esto ocurre con el framework de tests de Django8 por ejemplo. evitando que se creen y se destruyan bases de datos cuando tan s´lo estamos escribiendo tests o unitarios. o por lo menos en este momento. user . . que un grupo tiene uno o m´s a usuarios. AreEqual (2 . group . me puede a servir para probar que estoy trabajando bien con el framework ORM en caso de que est´ utilizando mecanismos complejos tipo cach´.djangoproject. group . Group = group . Name = " Curris " . a 10. e e 8 9 Desarrollo web con Python (http://www. user . ManyToMany 246 . Dise˜ ando relaciones entre modelos n § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 T´ ıpicamente cuando llega el momento de dise˜ar modelos y empezar n a trazar relaciones entre unos y otros9 . FindAll (). Save (). es decir. name = " curri1 " . Demasiado tiempo. En lugar de escribir ambas clases a priori.2. Group reloaded = Group .10. entonces me ahorro la parte de tests unitarios y salto directamente a tests de integraci´n. La raz´n o o es que los tests unitarios deben ser r´pidos y crear la base de datos consume a m´s de 5 segundos. user . Assert . } ¦ ¥ Adem´s de que el test me ayuda a encontrar la API que busco. me gusta tambi´n hacerlo con un e test primero. Mi recomendaci´n en este caso es utilizar el mecanismo de tests de Django o para los tests de integraci´n y utilizar pyunit para los tests unitarios. users . Group = group . que siempre crea la base de datos al vuelo incluso aunque no accedamos a ella. name = " curri2 " .com/) OneToMany. UserModel user = new UserModel (). dise˜o su API desde un test de integraci´n para asegurarme que es as´ como n o ı los quiero utilizar: [ Test ] public void U s e r B e l o n g s I n M a n y G r o u p s () { Group group = Group (). Supongamos que mi modelo User tiene una relaci´n de uno o a muchos con mi modelo Group.2. Si mi objetivo es trabajar con modelos. Dise˜o emergente con un ORM n Cap´ ıtulo 10 Hay que tener cuidado de no abusar de estos frameworks. Probar una o relaci´n tan sencilla como esta mediante un test unitario no vale la pena. Save (). reloaded . Save (). user .1.. user .

La pr´xima vez que o o escoja una herramienta de terceros aseg´rese de que dispone de una buena u bater´ de tests que al menos cumple un m´ ıa ınimo de calidad. ıa a Es decir. Es decir. La unificaci´n de las piezas del sistema o Ya casi tenemos todas las piezas del sistema dise˜adas pero en lo que a n tests se refiere todav´ nos queda un ´rea que cubrir. si escojo Hibernate no me pongo a escribir tests para comprobar que funciona. Hago TDD cuando ı por ejemplo tengo que leer datos de un fichero de entrada y guardarlos en base de datos a trav´s de modelos o simplemente cuando no entiendo las e relaciones entre entidades sin ver (y escribir) un ejemplo primero.Net funciona. Igual que tampoco pruebo que . o o En el UserManager de antes. Son los tests de sistema. tests de integraci´n que van de extremo a extremo de la aplicaci´n. a u Los tests de sistema para aplicaciones web se pueden llevar a cabo con 247 . Tenemos que partir de la base de que el c´digo de terceros. 10. o a Para este ejemplo concreto escribir´ el test antes que el SUT al estilo TDD ıa porque todav´ es un conjunto de objetos reducido. puede ser un antipatr´n seguir haciendo TDD. Sin embargo. El dise˜o emergente para los modelos no es algo que haga siempre. cuando lo dem´s a a est´ terminado y s´lo hay que colocar la carcasa. Si n se trata de unos modelos que ya he dise˜ado mil veces como por ejemplo n usuarios y grupos. Es una buena manera de a o evitar que el dise˜o gr´fico contamine la l´gica de negocio y al mismo tiempo n a o produce c´digo menos acoplado y m´s f´cil de mantener. Hay que confiar en ello y o asumir que funciona tal cual diga su documentaci´n. Sin embargo en muchas otras ocasiones. Cuando nos iniciamos en el desarrollo de tests de integraci´n o es com´n caer en la trampa de lanzarse a escribir tests para todas las heu rramientas que estamos utilizando. no hago un test primero.3. La unificaci´n de las piezas del sistema o Es importante tener cuidado de no hacer tests para probar que el framework que estamos usando funciona. o Para esto escribimos un nuevo test de integraci´n que cubre todo el ´rea.Cap´ ıtulo 10 10. Sin embargo no hay ning´n bloque de c´digo que se ocupe u o de inyectar el objeto ISession en UserManager para operar en producci´n. tenemos tests que cubren su l´gica de negocio o y tests que cubren la integraci´n de nuestra clase que accede a datos con el o sistema exterior. cuando ıa el ´rea a cubrir es mayor. s´ que me ayuda a clarificar lo que necesito. Intento por todos los medios dejar la interfaz gr´fica para el final del todo. A a o veces conviene escribir tests a posteriori. Encuentro que es as´ cuando se trata ı de integrar la interfaz de usuario con el resto del sistema.3. En general. TDD o a a para integrar interfaces de usuario no es una pr´ctica com´n. que utilizamos es robusto.

que todas las piezas encajan pero no intentar´ hacer tests para toda la funcionalidad del sistema ıa atacando a la interfaz de usuario. Cuando sea posible a ser´ mejor opci´n escribir tests unitarios si el c´digo lo permite.10. 10 http://seleniumhq. La unificaci´n de las piezas del sistema o Cap´ ıtulo 10 herramientas como Selenium10 . Michael a o o Feathers lo describe en su libro[6].org/ 248 . porque as´ seremos avisados de los destrozos que hao ı gamos. habr´ cambios en los niveles superiores que romper´n los o a a tests de sistema y dejar´ de valer la pena mantenerlos. grosso modo. Soy de la opini´n de que no se debe abusar o de los tests que atacan a la interfaz de usuario porque son extrem´damente a fr´giles. Si a nuestra bater´ de tests unitarios contempla la mayor parte de la l´gica de ıa o negocio y nuestros tests de integraci´n de bajo nivel (como los que hemos o hecho en este cap´ ıtulo) contienen las partes clave. Cuando queremos refactorizar un c´digo legado (que no tiene tests auo tom´ticos) entonces es buena idea blindar la aplicaci´n con tests de sistema a o antes de tocar c´digo. Una vez refactorizado el c´digo y creados sus correspondientes tests unitarios o y de integraci´n. me limitar´ a escribir unos ıa cuantos tests de sistema para comprobar.3. Ante cualquier cambio se rompen y cuesta mucho mantenerlos. Por desgracia escribir todos esos tests es un trabajo de usar y tirar.

Estar´ escrito en esa lengua por todo aquel que e e a se sienta c´modo escribiendo en ingl´s pero me parece contraproducente el o e hacerlo “por deber” cuando se tiene otra lengua con la cual se sienta uno m´s a gusto. Al contrario que ´l. en Python. si la elecci´n de pruebas es distinta en distintos puntos (o el orden) el o dise˜o puede variar (aunque el resultado debe permanecer igual). a a a El objetivo de este cap´ ıtulo no es m´s que mostrar al lector que hacer a TDD con un lenguaje interpretado como Python es igual que con un lenguaje compilado o h´ ıbrido. Despu´s de varias discusiones sobre el tema.Cap´tulo ı 11 La soluci´n en versi´n Python o o Al igual que en los cap´ ıtulos anteriores. usaremos el cl´sico y est´ndar pyunit para las a a a pruebas unitarias aunque tambi´n se podr´ utilizar el sencillo doctests o e ıa el potente nosetests2 (el cual recomiendo) que es un poderoso framework para test unitarios que nos har´ la vida mucho m´s f´cil. el bueno de Carlos ha e e aceptado a que as´ fuera. una gu´ de estilo muy conocida en el a ıa mundillo Python. por o n tanto. Por supuesto.python. lo l´gico (y debido puesto que el o e o 1 2 http://www. Es posible que le sorprenda despu´s o a e de leer los cap´ ıtulos anteriores que el c´digo est´ escrito en espa˜ol en lugar o e n de ingl´s. que el contexto influye y que si a estamos en una organizaci´n internacional. n Una aclaraci´n m´s antes de empezar. esta vez. Otro punto a destacar es la elecci´n de los tests unitarios o y c´mo ´sto puede cambiar el dise˜o de la soluci´n. Esto no es m´s que la o e n o a visualizaci´n del llamado dise˜o emergente el cual “sale” de los tests. vamos a escribir la Supercalculadora (al menos una versi´n reducida) siguiendo el desarrollo orientado o por pruebas pero.com/p/python-nose/ 249 .google. en un trabajo donde hay otros o desarrolladores que s´lo hablan ingl´s.org/dev/peps/pep-0008/ http://code. huelga decir. creo que el c´digo no tiene por ı e o qu´ estar escrito en ingl´s. Adem´s. Para la escritura del c´digo vamos a o seguir el est´ndar de estilo PEP8 1 .

ya que el libro est´ enfocado a lectores hispanos y e a para evitar que alguien pueda no entender correctamente el c´digo en ingl´s. n Sin m´s dilaci´n.Cap´ ıtulo 11 idioma internacional de facto) es hacerlo en ingl´s ya que el c´digo debe e o ser entendido por todo el mundo (por cierto. todo el mundo deber´ ser capaz de manejarse sin problema ıa en ingl´s). vamos a ponernos a ello resumiendo en una frase qu´ es a o e lo que el cliente nos ped´ (para obtener la informaci´n completa. En este caso. ver Cap´ ıa o ıtulo 8 en la p´gina 121): a   Una calculadora de aritm´tica b´sica de n´meros enteros e a u . o e hemos decidido dar una “nota de color” en este cap´ ıtulo escribiendo el c´digo o en espa˜ol. que sirva como nota que en este mundillo.

Para seguir la misma filosof´ que el cap´ ıa ıtulos anteriores, vamos a usar los mismos tests de aceptaci´n que en el caso de la Supercalculadora en C# o aunque, como veremos m´s tarde, quiz´ tomemos decisiones distintas a lo a a hora de elegir los test unitarios o el orden en que los implementaremos. Para no duplicar, no nos vamos a meter a analizar los tests de aceptaci´n en este o cap´ ıtulo. En caso de que quiera revisar los motivos de por qu´ estos tests e y no otros, por favor, vuelva a releer los cap´ ıtulos anteriores. Para refrescar la memoria, a continuaci´n se muestran los tests de aceptaci´n elegidos al o o inicio del proyecto:
"2 + 2", devuelve 4 "5 + 4 * 2 / 2", devuelve 9 "3 / 2", produce el mensaje ERROR "* * 4 - 2": produce el mensaje ERROR "* 4 5 - 2": produce el mensaje ERROR "* 4 5 - 2 -": produce el mensaje ERROR "*45-2-": produce el mensaje ERROR

¿Recuerda cual era nuestra forma de trabajo en cap´ ıtulos anteriores?, s´ ı, una muy sencilla: un editor de textos por un lado para llevar las notas (al que llam´bamos libreta) y un IDE por otro para escribir el c´digo. De nuevo a o vamos a seguir esta forma de trabajar que aunque sencilla, es muy poderosa. Sin embargo, vamos a simplificar a´n m´s ya que en lugar de un IDE vamos a u a 3 con el modo python-mode 4 (vaya, ahora usar otro editor de textos, Emacs podemos usar el mismo programa como libreta, como editor de c´digo y o A X, que no se nos olvide). tambi´n como editor de LTE e
3 4

Emacs 23.1 - ftp://ftp.gnu.org/gnu/emacs/windows/emacs-23.1-bin-i386.zip http://www.rwdev.eu/articles/emacspyeng

250

Cap´ ıtulo 11 A por ello, entonces. Lo primero, creamos un directorio donde vamos a trabajar, por ejemplo “supercalculadora python”. Y dentro de este directorio creamos nuestro primer fichero que, por supuesto, ser´ de pruebas: a ut supercalculadora.py. Vamos a empezar con los mismos test unitarios procedentes del primer test de aceptaci´n. Estos son los siguientes: o
# Aceptaci´n - "2 + 2", devuelve 4 o Sumar 2 al n´mero 2, devuelve 4 u La cadena "2 + 2", tiene dos n´meros y un operador: ’2’, ’2’ y ’+’ u # Aceptaci´n - "5 + 4 * 2 / 2", devuelve 9 o # Aceptaci´n - "3 / 2", produce el mensaje ERROR o # Aceptaci´n - "* * 4 - 2": produce el mensaje ERROR o # Aceptaci´n -"* 4 5 - 2": produce el mensaje ERROR o # Aceptaci´n -"* 4 5 - 2 : produce el mensaje ERROR o # Aceptaci´n -"*45-2-": produce el mensaje ERROR o

§
1 2 3 4 5 6 7 8 9 10

Vamos a por el primer test de desarrollo:

import unittest import supercalculadora class T e s t s S u p e r c a l c u l a d o r a ( unittest . TestCase ): def test_sumar_2_y_2 ( self ): calc = supercalculadora . Supercalculadora () self . failUnlessEqual (4 , calc . sumar (2 , 2)) if __name__ == " __main__ " : unittest . main ()

¦

¥

El c´digo no compila porque todav´ no hemos creado ni el fichero supercalo ıa culadora ni la clase Supercalculadora. Sin embargo ya hemos dise˜ado n algo: el nombre de la clase, su constructor y su primer m´todo (nombre, e par´metros que recibe y el resultado que devuelve). El dise˜o, como vemos, a n emerge de la prueba. A continuaci´n escribimos el m´ o ınimo c´digo posible o para que el test pase al igual que en cap´ ıtulos anteriores. Devolver 4, que es el resultado correcto, es la implementaci´n m´ o ınima en este caso.
§
1 2 3

11.1: supercalculadora.py

class Supercalculadora : def sumar ( self , a , b ): return 4

¦

¥

§
1 2

Muy bien, ahora habr´ que refactorizar pero estamos tan al principio ıa que todav´ no hay mucho que hacer as´ que vamos a seguir con la siguiente ıa ı prueba unitaria sobre el mismo tema.
def test_sumar_5_y_7 ( self ): self . failUnlessEqual (12 , self . calc . sumar (5 , 7))

¦

¥

251

Cap´ ıtulo 11 Ejecutamos (ctrl+c ctrl+c) en Emacs y observamos que falla (en una ventana distinta dentro del editor):
§
1 2 3 4 5 6 7 8 9 10 11 12

.F =================================================================== FAIL : test_sumar_5_y_7 ( __main__ . T e s t s S u p e r c a l c u l a d o r a ) ------------------------------------------------------------------Traceback ( most recent call last ): File " < stdin > " , line 11 , in test_sumar_5_y_7 AssertionError : 12 != 4 ------------------------------------------------------------------Ran 2 tests in 0.000 s FAILED ( failures =1)

¦

¥

Vamos entonces a cambiar la implementaci´n que ser´ m´ o ıa ınima para este test:
§
1 2 3

class Supercalculadora : def sumar ( self , a , b ): return 12

¦

¥

Sin embargo, aunque este es el c´digo m´ o ınimo que hace pasar la segunda prueba, hace fallar la primera (es un fallo que podr´ ıamos haber obviado pero por motivos did´cticos lo incluimos como primer ejemplo ya que en ejemplos a m´s complejos no es f´cil ver el fallo de pruebas anteriores) por lo tanto a a debemos buscar la implementaci´n m´ o ınima que hace pasar todas las pruebas. En este caso, ser´ ıa:
§
1 2 3

class Supercalculadora : def sumar ( self , a , b ): return a + b

¦

¥

Ahora ya tenemos la luz verde (“Ran 2 tests in 0.000s OK” en nuestro caso) as´ que pasemos al paso de refactorizaci´n. Esta vez, s´ que hay ı o ı cosas que hacer ya que como vemos en las pruebas, la l´ ınea calc = supercalculadora.Supercalculadora() est´ duplicada. Subsan´moslo creando a e un m´todo setUp (las may´sculas y min´sculas son importantes) donde moe u u vemos la duplicidad de las pruebas (por consistencia, a˜adimos tambi´n el n e m´todo tearDown aunque no har´ nada ya que es el contrario a setUp) e a
§
1 2 3 4 5 6 7 8 9 10 11

class T e s t s S u p e r c a l c u l a d o r a ( unittest . TestCase ): def setUp ( self ): self . calc = supercalculadora . Supercalculadora () def tearDown ( self ): pass def test_sumar_2_y_2 ( self ): self . failUnlessEqual (4 , self . calc . sumar (2 , 2)) def test_sumar_5_y_7 ( self ):

252

Cap´ ıtulo 11
self . failUnlessEqual (12 , self . calc . sumar (5 , 7))

12

¦

¥

Voy a a˜adir un ultimo test que pruebe que el orden en los sumandos n ´ no altera el resultado (propiedad conmutativa), adem´s de a˜adirlo en la a n libreta ya que puede ser de importancia para futuras operaciones. Tambi´n, e como puede apreciar, marcaremos con HECHAS las cuestiones resueltas de la libreta por claridad.
# Aceptaci´n - "2 + 2", devuelve 4 o Sumar 2 al n´mero 2, devuelve 4 - ¡HECHO! u La propiedad conmutativa se cumple La cadena "2 + 2", tiene dos n´meros y un operador: ’2’, ’2’ y ’+’ u # Aceptaci´n - "5 + 4 * 2 / 2", devuelve 9 o # Aceptaci´n - "3 / 2", produce el mensaje ERROR o # Aceptaci´n - "* * 4 - 2": produce el mensaje ERROR o # Aceptaci´n - "* 4 5 - 2": produce el mensaje ERROR o # Aceptaci´n - "* 4 5 - 2 : produce el mensaje ERROR o # Aceptaci´n - "*45-2-": produce el mensaje ERROR o

Vamos con ello con un tests:
§
1 2 3

def t e s t _ s u m a r _ p r o p i e d a d _ c o n m u t a t i v a ( self ): self . failUnlessEqual ( self . calc . sumar (5 , 7) , self . calc . sumar (7 , 5))

¦

¥

La prueba pasa sin necesidad de tocar nada. ¡Genial!, la propiedad conmutativa se cumple. Hasta el momento y para recapitular, tenemos los siguientes tests:
§
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

class T e s t s S u p e r c a l c u l a d o r a ( unittest . TestCase ): def setUp ( self ): self . calc = supercalculadora . Supercalculadora () def tearDown ( self ): pass def test_sumar_2_y_2 ( self ): self . failUnlessEqual (4 , self . calc . sumar (2 , 2)) def test_sumar_5_y_7 ( self ): self . failUnlessEqual (12 , self . calc . sumar (5 , 7)) def t e s t _ s u m a r _ p r o p i e d a d _ c o n m u t a t i v a ( self ): self . failUnlessEqual ( self . calc . sumar (5 , 7) , self . calc . sumar (7 , 5))

¦

¥

Todo bien hasta el momento. ¡Fenomenal!

253

Cap´ ıtulo 11

# Aceptaci´n - "2 + 2", devuelve 4 o Sumar 2 al n´mero 2, devuelve 4 - ¡HECHO! u La propiedad conmutativa se cumple - ¡HECHO! La cadena "2 + 2", tiene dos n´meros y un operador: ’2’, ’2’ y ’+’ u # Aceptaci´n - "5 + 4 * 2 / 2", devuelve 9 o # Aceptaci´n - "3 / 2", produce el mensaje ERROR o # Aceptaci´n - "* * 4 - 2": produce el mensaje ERROR o # Aceptaci´n - "* 4 5 - 2": produce el mensaje ERROR o # Aceptaci´n - "* 4 5 - 2 : produce el mensaje ERROR o # Aceptaci´n - "*45-2-": produce el mensaje ERROR o

Hemos actualizado la libreta con el ultimo resultado. Recordad que es ´ muy importante que la libreta est´ en todo momento actualizada y refleje el e estado actual de nuestro desarrollo. En este punto, paremos un momento y miremos si hay algo que refactorizar. Vemos que el c´digo es bastante limpio as´ que reflexionamos sobre o ı c´mo seguir. o Hemos hecho la suma pero, sin embargo, no tenemos ning´n test de acepu taci´n sobre la resta que parece algo distinta. En el caso anterior deber´ o ıamos verificar el orden ya que ser´ importante (no se cumple la propiedad conmua tativa). Adem´s, en ciertos casos el n´mero devuelto podr´ ser negativo y a u ıa eso no lo hab´ ıamos contemplado. ¿Debe ser un error o un n´mero negatiu vo es correcto?, obviamente este caso es trivial y queremos que los valores negativos sean aceptados (un n´mero negativo es un entero y eso es lo que u hab´ pedido el cliente, ¿no?) pero, por si acaso, ¡pregunt´mosle al cliente!. ıa e Es mejor estar seguros de que quiere trabajar con negativos y que cuando dijo “entero” realmente quer´ decir “entero” y no “natural”. ıa A˜adamos en la libreta un test de aceptaci´n y unas cuantas pruebas n o unitarias para este nuevo caso.

254

Cap´ ıtulo 11

# Aceptaci´n - "2 + 2", devuelve 4 o Sumar 2 al n´mero 2, devuelve 4 - ¡HECHO! u La propiedad conmutativa se cumple - ¡HECHO! La cadena "2 + 2", tiene dos n´meros y un operador: ’2’, ’2’ y ’+’ u # Aceptaci´n - "5 - 3", devuelve 2 o Restar 5 al n´mero 3, devuelve 2 u Restar 2 al n´mero 3, devuelve -1 u La propiedad conmutativa no se cumple # Aceptaci´n - "5 + 4 * 2 / 2", devuelve 9 o # Aceptaci´n - "3 / 2", produce el mensaje ERROR o # Aceptaci´n - "* * 4 - 2": produce el mensaje ERROR o # Aceptaci´n - "* 4 5 - 2": produce el mensaje ERROR o # Aceptaci´n - "* 4 5 - 2 : produce el mensaje ERROR o # Aceptaci´n - "*45-2-": produce el mensaje ERROR o Operaciones con n´meros negativos u

Podr´ ıamos seguir con el ultimo test unitario que tenemos en la suma, ´ el de los operadores y operandos. Sin embargo, parece “algo distinto” y sabemos que requiere algo m´s que cambios en Supercalculadora, por eso a decidimos posponerlo un poco y pasarnos a la resta. Escribimos el primer test para la resta:
§
1 2 3

... def test_restar_5_y_3 ( self ): self . failUnlessEqual (2 , self . calc . restar (5 , 3))

¦

¥

§
1 2 3 4

Por supuesto, falla (la operaci´n resta no existe todav´ as´ que vamos o ıa), ı a ponernos con la implementaci´n como ya hemos hecho anteriormente. o
class Supercalculadora : ... def restar ( self , a , b ): return 2

¦

¥

§
1 2 3

¡Pasa de nuevo!. ¿Refactorizamos?, parece que todav´ el c´digo (tanto ıa o de las pruebas como el resto) es limpio, as´ que vamos a seguir con otra ı prueba.
... def test_restar_2_y_3 ( self ): self . failUnlessEqual ( -1 , self . calc . restar (2 , 3))

¦

¥

Luz roja de nuevo. Vamos a arreglarlo de tal modo que tanto esta como la prueba anterior (y por supuesto todas las dem´s que formen o no parte de a la resta) pasen. 255

la libreta). restar (5 .2": produce el mensaje ERROR o # Aceptaci´n ."* 4 5 .¡HECHO! u La propiedad conmutativa no se cumple ."5 .2 : produce el mensaje ERROR o # Aceptaci´n . ¿y el m´s e u a a peque~o? n Las nuevas cuestiones sobre l´ ımites num´ricos ata˜en a todas las operae n ciones de la calculadora y no sabemos qu´ debemos hacer en esta disyuntiva. devuelve -1 ."3 / 2".. Recuerde que pod´ ıamos recorrer el “´rbol de tests” en profundidad a o en amplitud. a . restar (3 .¡HECHO! u La propiedad conmutativa se cumple . # Aceptaci´n .¡HECHO! La cadena "2 + 2". No vamos a ir por ese camino como hemos hecho en los cap´ ıtulos anteriores. calc ."* 4 5 .. failIfEqual ( self .¡HECHO! u Restar 2 al n´mero 3. Anteriormente lo hemos hecho en amplitud as´ que esta vez ı vamos a coger el otro camino y hacerlo en profundidad. def t e s t _ r e s t a r _ n o _ p r o p i e d a d _ c o n m u t a t i v a ( self ): self . ’2’ y ’+’ u # Aceptaci´n . devuelve 2 o Restar 5 al n´mero 3."2 + 2".Cap´ ıtulo 11 11.2: supercalculadora. 5)) ¦ ¥ Sigue funcionando sin tocar nada.b ¦ ¥ Otra vez en verde. 3) . pero. devuelve 4 o Sumar 2 al n´mero 2.. Vamos a ir m´s lejos a 256 . produce el mensaje ERROR o # Aceptaci´n . b ): return a ."*45-2-": produce el mensaje ERROR o ¿Qu´ n´mero es el m´s grande que se puede manejar?."* * 4 . e Lo primero es clarificar qu´ comportamiento debe tener el software pregune tando al cliente. Y seguimos con la siguiente prueba ya que al igual que antes no vemos necesidad de refactorizar (¡pero lo mantenemos en mente!) § 1 2 3 4 .3". De ah´ podemos sacar los nuevos test de aceptaci´n para ı o las dudas que tenemos."5 + 4 * 2 / 2". self . devuelve 2 . devuelve 4 . tiene dos n´meros y un operador: ’2’.2": produce el mensaje ERROR o # Aceptaci´n . b ): return a + b def restar ( self .¡HECHO! # Aceptaci´n . calc . devuelve 9 o # Aceptaci´n .py § 1 2 3 4 5 6 class Supercalculadora : def sumar ( self . al escribir esto se nos vienen a la cabeza otra serie de preguntas (actualizamos.. a . a la vez.

sumar (2 . 7) . sumar (5 . Ser´ algo as´ o ıa ı: 257 . restar (5 . 2)) def test_sumar_5_y_7 ( self ): self . failUnlessEqual (4 . self . calc . sumar (5 . self . calc . expresion . e estabamos escribiendo los test unitarios anteriormente con algunas decisiones de dise˜o ya en la cabeza. Lo corregimos igualmente y ahora falta el m´todo parse que creamos e con la m´ ınima implementaci´n posible. Ahora bien. Por claridad. failUnless ({ ’ Operandos ’: [2 . Calculadora () def tearDown ( self ): pass def test_sumar_2_y_2 ( self ): self . calc . failUnlessEqual ( self . que la expresi´n ser´ una cadena de n o a caracteres. por ejemplo).3: ut supercalculadora. calc . calc . 7)) def t e s t _ s u m a r _ p r o p i e d a d _ c o n m u t a t i v a ( self ): self . self . restar (5 . TestCase ): def setUp ( self ): self . failUnlessEqual ( -1 . restar (3 . 3)) def test_restar_2_y_3 ( self ): self . Vamos a seguir con el test de aceptaci´n que ya ´ o ıbamos manejando. 5)) def t e s t _ e x t r a e r _ o p e r a n d o s _ y _ o p e r a d o r e s _ e n _ 2 _ m a s _ 2 ( self ): expresion = expr_aritmetica . por ejemplo. 3)) def t e s t _ r e s t a r _ n o _ p r o p i e d a d _ c o n m u t a t i v a ( self ): self . parse ( " 2 + 2 " )) ¦ ¥ Estamos en rojo porque el archivo “expr aritmetica. calc . calc .Cap´ ıtulo 11 en el camino ya andado y no movi´ndonos a otras partes del ´rbol (los nuee a vos tests que aparecer´ sobre el n´mero m´ximo y m´ ıan u a ınimo. parece que vamos a cambiar de tercio porque el test unitario que hab´ ıamos definido al principio en la suma es m´s un test de un parser a que de la calculadora en s´ Nos ponemos con ´l en el mismo sitio donde ı. sumar (7 . ponemos todas la pruebas hasta ahora: § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 11. self . failUnlessEqual (12 .py” ni siquiera existe. self . ’ Operadores ’: [ ’+ ’]} . calc . 3) . restar (2 .py import unittest import supercalculadora import expr_aritmetica class T e s t s S u p e r c a l c u l a d o r a ( unittest . ExprAritmetica () self . 5)) def test_restar_5_y_3 ( self ): self . calc = calculadora . Lo corregimos pero tambi´n falla porque la clase ExprAritmetica tampoco e existe. 2] . self . failUnlessEqual (2 . failIfEqual ( self .

makeSuite ( ut_calculadora . 2] .py) y de la clase.py import unittest import ut_calculadora import ut_expr_aritmetica if __name__ == " __main__ " : suite = unittest . addTest ( unittest .py § 1 2 3 class ExprAritmetica : def parse ( self . TextTestRunner ( verbosity =3). run ( suite ) ¦ § 1 2 3 4 5 6 7 8 9 10 ¥ 11. Tenemos los tests de la clase Calculadora y de la clase ExprAritmetica en la misma clase de tests llamada TestsSupercalculadora. TestsExprAritmetica )) unittest .4: expr aritmetica. Cambiemos el nombre del fichero (por calculadora. pensamos en el siguiente paso. TestsCalculadora )) suite . hay muchas cosas que refactorizar. addTest ( unittest . Calculadora () def tearDown ( self ): pass 258 .6: ut calculadora. o Tenemos entonces: § 1 2 3 4 5 6 7 8 9 10 11 11. En el archivo principal creamos o o una suite donde recogemos todos las pruebas de los distintos “m´dulos”. uno para cada u e “m´dulo” al igual que la implementaci´n. calculadora puesto que es ıa la clase encargada de calcular (supercalculadora ser´ la aplicaci´n en s´ o. TestSuite () suite .5: ut supercalculadora. Aunque ambos son tests de la aplicaci´n Supercalculadora creo o que es necesario tenerlos separados en tests para Calculadora y tests para ExprAritmetica para que todo quede m´s claro. vamos a separarlos tambi´n en dos ficheros de tests. la refactorizaci´n. calc = calculadora . en realidad. o a la clase supercalculadora deber´ ser.py import unittest import calculadora class TestsCalculadora ( unittest . la clase principal). Empecemos por el nombre de la clase Supercalculadora (y su fichero). o Ummm. makeSuite ( ut_expr_aritmetica . a o ı como mucho. Para alcanzar mayor claridad a a´n.Cap´ ıtulo 11 11. Parece ser ahora que tenemos dos “m´dulos” que son parte de un programa que ser´ la supercalculadora. Sigamos por los tests. TestCase ): def setUp ( self ): self . como es habitual. ’ Operadores ’: [ ’+ ’ ]} ¦ ¥ Por fin pasa las pruebas y. exp ): return { ’ Operandos ’: [2 .

sumar (7 . 7)) def t e s t _ s u m a r _ p r o p i e d a d _ c o n m u t a t i v a ( self ): self . calc . ’ Operadores ’: [ ’+ ’]} . parse ( " 2 + 2 " )) ¦ ¥ Sabemos que para ExprAritmetica tenemos que implementar mucho m´s pero por ahora lo vamos a dejar hasta que salga m´s funcionalidad a a en las otras pruebas de aceptaci´n.Cap´ ıtulo 11 def test_sumar_2_y_2 ( self ): self . calc . failUnlessEqual (2 .7: ut expr aritmetica. failUnlessEqual (12 . 2)) def test_sumar_5_y_7 ( self ): self . calc . 5)) def test_restar_5_y_3 ( self ): self . restar (3 . self . failUnlessEqual ( self .py import unittest import expr_aritmetica class TestsExprAritmetica ( unittest . calc . En este punto. failUnlessEqual ( -1 . sumar (2 . restar (2 . restar (5 . self . failUnlessEqual (4 . restar (5 . calc . 3)) def test_restar_2_y_3 ( self ): self . failUnlessEqual ({ ’ Operandos ’: [2 . TestCase ): def t e s t _ e x t r a e r _ o p e r a n d o s _ y _ o p e r a d o r e s _ e n _ 2 _ m a s _ 2 ( self ): expresion = expr_aritmetica . sumar (5 . 2] . calc . e 259 . 3)) def t e s t _ r e s t a r _ n o _ p r o p i e d a d _ c o n m u t a t i v a ( self ): self . self . sumar (5 . self . calc . ExprAritmetica () self . failIfEqual ( self . 3) . self . Esta es un poco compleja o ´ tocando muchos puntos a la vez as´ que mejor la aplazamos y vamos a pensar ı qu´ podemos hacer ahora. 5)) 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 ¦ § 1 2 3 4 5 6 7 8 9 ¥ 11. expresion . 7) . calc . self . actualicemos la libreta o y sigamos con la siguiente prueba de aceptaci´n.

failUnlessEqual ( -5 . devuelve 2 ."* 4 5 . restar ( -5 . restar ( -7 . def t e s t _ s u m a r _ n u m e r o s _ n e g a t i v o s ( self ): self . o o . sumar (2 . escribimos la prueba primero (para abreviar. devuelve 0 o Sumar 2 al n´mero -2. luego otra prueba."5 + 4 * 2 / 2".¡HECHO! u Restar 2 al n´mero 3.2 : devuelve un error o # Aceptaci´n . devuelve 9 o # Aceptaci´n . self .¡HECHO! La cadena "2 + 2". calc . luego se refactoriza. En esta ocasi´n."* * 4 .."2 + -2".¡HECHO! u La propiedad conmutativa no se cumple . failUnlessEqual ( -7 .3". self .. devuelve -5 u # Aceptaci´n . devuelve -7 u Restar -2 al n´mero -7. devuelve 4 o Sumar 2 al n´mero 2."3 / 2". Debemos seguir escribiendo pruebas unitarias que nos hagan implementar m´s la funcionalidad del software que estamos a 260 ."2 + 2".). devuelve 2 o Restar 5 al n´mero 3."*45-2-": devuelve un error o ¿Qu´ n´mero es el m´s grande que se puede manejar?.. devuelve 0 u Restar 2 al n´mero -5. devuelve 4 . -2)) ¦ ¥ Esto ha sido f´cil. tiene dos n´meros y un operador: ’2’.¡HECHO! # Aceptaci´n . ’2’ y ’+’ u ¡HECHO! # Aceptaci´n . la implementaci´n que ten´ a o ıamos ya soportaba los n´meu ros negativos por los que el test pasa sin necesidad de hacer ninguna modificaci´n en el c´digo."* 4 5 . calc . 2)) self .. hemos escrito las pruebas en dos tests (para o suma y para resta puesto que son dos comportamientos distintos en nuestra aplicaci´n) dando nombres que reflejen con claridad la intenci´n de los tests. -2)) def t e s t _ r e s t a r _ n u m e r o s _ n e g a t i v o s ( self ): self ."5 . pongo todas las pruebas juntas aunque primero se escribe una. las operaciones.Cap´ ıtulo 11 # Aceptaci´n . devuelve -1 . calc .2": devuelve un error o # Aceptaci´n .¡HECHO! u La propiedad conmutativa se cumple . self . Esto es bueno ya que tenemos m´s tests que verifican o o a el funcionamiento de la implementaci´n pero malo a la vez porque no hemos o avanzado con nuestro software. ¿y el m´s e u a a peque~o? n § 1 2 3 4 5 6 7 Nos ponemos con los n´meros negativos puesto que parece m´s relacionau a do con el mismo tema. failUnlessEqual (0 . devuelve un error o # Aceptaci´n .2": devuelve un error o # Aceptaci´n . luego el c´digo m´ o ınimo para que pase. Como siempre.

’-5’ y u ’/’ # Aceptaci´n ..¡HECHO! u La propiedad conmutativa no se cumple ."2 + 2". tiene dos n´meros y un operador: ’10’. n o Vamos a seguir con esta prueba de aceptaci´n ya que parece una buena forma o de seguir despu´s de la suma y la resta."* 4 5 .2": devuelve un error o # Aceptaci´n ."5 + 4 * 2 / 2". devuelve -7 . Actualicemos la libreta. failUnlessEqual (2 . dividir (2 .¡HECHO! u # Aceptaci´n .¡HECHO! # Aceptaci´n . 2)) self .3". devuelve 2 .¡HECHO! u La propiedad conmutativa se cumple ."2 + -2".¡HECHO! u Restar 2 al n´mero -5."*45-2-": devuelve un error o ¿Qu´ n´mero es el m´s grande que se puede manejar?. devuelve -1 . devuelve 0 o Sumar 2 al n´mero -2."5 .Cap´ ıtulo 11 escribiendo.2": devuelve un error o # Aceptaci´n . self . devuelve 4 . 5)) 261 . devuelve 4 o Sumar 2 al n´mero 2."3 / 2". dividir (10 . def t e s t _ d i v i s i o n _ e x a c t a ( self ): self . tiene dos n´meros y un operador: ’2’. devuelve -5 ."* * 4 .¡HECHO! u Restar -2 al n´mero -7. failUnlessEqual (1 .¡HECHO! La cadena "2 + 2". ’2’ y ’+’ u ¡HECHO! # Aceptaci´n .2 : devuelve un error o # Aceptaci´n . devuelve 9 o # Aceptaci´n . calc . o # Aceptaci´n .. self . e Empezamos con las pruebas: § 1 2 3 4 5 . ¿y el m´s e u a a peque~o? n Vemos que hemos a˜adido algunos test unitarios al caso de la divisi´n. devuelve 0 . calc ."* 4 5 . devuelve 2 o Restar 5 al n´mero 3. escojamos por d´nde seguir y pensemos en nuevos o test de aceptaci´n y/o unitarios si hiciera falta.¡HECHO! u Restar 2 al n´mero 3. devuelve un error o Dividir 2 entre 2 da 1 Dividir 10 entre 5 da 2 Dividir 10 entre -5 da -2 Dividir -10 entre -5 da 2 Dividir 3 entre 2 lanza una excepci´n o Dividir 3 entre 0 lanza una excepci´n o La cadena "10 / -5".

. 0) ¦ ¥ Pasa sin necesidad de tocar el c´digo ya que a / b lanza una excepci´n o o directamente. -5)) 6 7 8 ¦ ¥ Recuerde que es un test a la vez. Ahora pasaıa mos a un nuevo test unitario sobre las expresiones aritm´ticas que promete e ser un poco m´s complejo de resolver. def t e s t _ d i v i s i o n _ n o _ e n t e r a _ d a _ e x c e p c i o n ( self ): self . 3 .. b ): return a / b ¦ ¥ Seguimos con el test de divisi´n no entera: o § 1 2 3 . def dividir ( self . self . a . -5] . dividir . failUnlessRaises ( ZeroDivisionError . ı § 1 2 3 4 . Vamos primero con el test y luego con a la implementaci´n.. expresion . ¿Hace falta refactorizar?. esto es s´lo para abreviar. o § 1 2 3 4 5 6 7 8 . failUnlessEqual (2 .. failUnlessEqual ( -2 . as´ que seguimos con el siguiente. dividir (10 . -5)) self . parse ( " 10 / -5 " )) ¦ ¥ Y el c´digo m´s simple que se me ha ocurrido para pasar ambas pruebas o a es el siguiente: 262 . las o pruebas anteriores se har´ en tres pasos de “prueba .. calc . dividir ( -10 . self . as´ que escribimos la m´ ı ımima implementaci´n para hacer pasar las o pruebas: § 1 2 3 4 5 6 .. failUnlessEqual ({ ’ Operandos ’: [10 . 2) ¦ ¥ Falla. calc . def test_division_por_0 ( self ): self . dividir ..Cap´ ıtulo 11 def t e s t _ d i v i s i o n _ e x a c t a _ n e g a t i v a ( self ): self . self .c´digo . class TestsExprAritmetica ( unittest . ’ Operadores ’: [ ’/ ’]} . a . failUnlessRaises ( ValueError . El c´digo despu´s de estas pruebas..refactoriıan o zaci´n”. TestCase ): . ExprAritmetica () self .. def dividir ( self . como siempre. ser´ (recuerde que la primera o o e ıa aproximaci´n ser´ nada m´s que return 1) o ıa a § 1 2 3 . Es decir. parece que todav´ no. 3 .. def t e s t _ e x t r a e r _ o p e r a n d o s _ y _ o p e r a d o r e s _ e n _ 1 0 _ e n t r e _ m e n o s _ 5 ( self ): expresion = expr_aritmetica . b ): if a % b != 0: raise ValueError else : return a / b ¦ ¥ Parace que no hace falta refactorizar. self . calc ... calc .

atoi ( token )) except ValueError : operadores . exp ): operandos = [] operadores = [] tokens = exp .Cap´ ıtulo 11 § 1 2 3 4 5 6 7 8 9 10 11 12 13 import string class ExprAritmetica : def parse ( self . atoi ( token )) else : operadores . ExprAritmetica () def tearDown ( self ): pass 263 . no o es bueno (aunque en un caso tan simple no importe mucho).9: ut expr aritmetica. El c´digo ´ o o parece limpio pero aunque es simple. en general. § 1 2 3 4 5 6 7 8 9 11. append ( token ) return { ’ operandos ’: operandos . append ( token ) return { ’ operandos ’: operandos .py def __es_numero__ ( self .8: expr aritmetica. movemos la duplicidad al a m´todo setUp y creamos su contrario tearDown vac´ e ıo. TestCase ): def setUp ( self ): self . split ( exp ) for token in tokens : try : operandos . __es_numero__ ( token ): operandos . cadena ): try : string . § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 11. La refactorizaci´n. exp ): operandos = [] operadores = [] tokens = string . append ( string . Lo mejor es que lo arreglemos junto con los tests de la clase ExprAritmetica donde vemos que hay algo de duplicidad. append ( string . ’ operadores ’: operadore s } ¦ ¥ Un ultimo paso antes de actualizar la libreta. atoi ( cadena ) return True except ValueError : return False def parse ( self . expresion = expr_aritmetica .py import unittest import expr_aritmetica class TestsExprAritmetica ( unittest . split () for token in tokens : if self . ’ operadores ’: operadore s } ¦ ¥ Una vez m´s y como en los casos anteriores. las excepciones se usan fuera del flujo normal de comportamiento (hay l´gica en el except) y esto.

¡HECHO! o La cadena "10 / -5". -5] . devuelve 4 o Sumar 2 al n´mero 2. expresion .¡HECHO! Dividir 10 entre -5 da -2 ."2 + 2".¡HECHO! # Aceptaci´n . failUnlessEqual ({ ’ operandos ’: [2 .¡HECHO! u Restar -2 al n´mero -7.3".Cap´ ıtulo 11 10 11 12 13 14 15 16 17 18 19 def t e s t _ e x t r a e r _ o p e r a n d o s _ y _ o p e r a d o r e s _ e n _ 2 _ m a s _ 2 ( self ): self . Hag´mos algua a 264 ."* 4 5 .2 : devuelve un error o # Aceptaci´n .¡HECHO! # Aceptaci´n .¡HECHO! Dividir -10 entre -5 da 2 .¡HECHO! u Restar 2 al n´mero -5. ’-5’ y u ’/’ . tiene dos n´meros y un operador: ’10’. devuelve -1 ."* 4 5 . 2] ."*45-2-": devuelve un error o ¿Qu´ n´mero es el m´s grande que se puede manejar?. devuelve -7 ."5 .¡HECHO! # Aceptaci´n .¡HECHO! Dividir 10 entre 5 da 2 . debemos actualizar la libreta: # Aceptaci´n ."2 + -2". self . devuelve 9 o # Aceptaci´n . devuelve 0 .¡HECHO! La cadena "2 + 2". devuelve 0 o Sumar 2 al n´mero -2. expresion ."* * 4 . devuelve 4 . self . parse ( " 2 + 2 " )) def t e s t _ e x t r a e r _ o p e r a n d o s _ y _ o p e r a d o r e s _ e n _ 1 0 _ e n t r e _ m e n o s _ 5 ( self ): self . devuelve 2 . devuelve -5 .¡HECHO! u Restar 2 al n´mero 3. parse ( " 10 / -5 " )) ¦ ¥ Despu´s de todos estos cambios y de las nuevas pruebas de aceptaci´n e o que hemos creado.2": devuelve un error o # Aceptaci´n . failUnlessEqual ({ ’ operandos ’: [10 .¡HECHO! u # Aceptaci´n .¡HECHO! o Dividir 3 entre 0 lanza una excepci´n ."3 / 2".¡HECHO! Dividir 3 entre 2 lanza una excepci´n . devuelve un error o Dividir 2 entre 2 da 1 . ¿y el m´s e u a a peque~o? n Vamos a movernos un poco m´s en la ExprAritmetica. ’ operadores ’: [ ’+ ’]} . ’ operadores ’: [ ’/ ’]} ."5 + 4 * 2 / 2". devuelve 2 o Restar 5 al n´mero 3.¡HECHO! u La propiedad conmutativa se cumple .¡HECHO! u La propiedad conmutativa no se cumple . ’2’ y ’+’ u . tiene dos n´meros y un operador: ’2’.2": devuelve un error o # Aceptaci´n .

failUnlessEqual ({ ’ operandos ’: [5 ."*45-2-": devuelve un error o ¿Qu´ n´mero es el m´s grande que se puede manejar?.¡HECHO! # Aceptaci´n . expresion . devuelve 4 o Sumar 2 al n´mero 2. devuelve 9 Operandos son ’5’. devuelve 0 o Sumar 2 al n´mero -2.3". ’/ ’]} . devuelve 9 o "5 + 4 * 2 / 2". 4 .¡HECHO! o Dividir 3 entre 0 lanza una excepci´n .Cap´ ıtulo 11 nas pruebas m´s."* * 4 . devuelve -1 . devuelve un error o Dividir 2 entre 2 da 1 .¡HECHO! u Restar 2 al n´mero -5.¡HECHO! o La cadena "10 / -5". devuelve 2 o Restar 5 al n´mero 3.¡HECHO! # Aceptaci´n . Pongamos al d´ la libreta y pensemos en c´mo seguir e ıa o adelante.2": devuelve un error o # Aceptaci´n .2": devuelve un error o # Aceptaci´n . tiene dos n´meros y un operador: ’2’."* 4 5 . a def t e s t _ e x t r a e r _ o p e r a n d o s _ y _ o p e r a d o r e s _ e x p r _ s i n _ p t e s i s ( self ): self . devuelve 4 ."5 . # Aceptaci´n . devuelve -5 . self .¡HECHO! u Restar -2 al n´mero -7. ’ operadores ’: [ ’+ ’ .¡HECHO! Dividir 3 entre 2 lanza una excepci´n . ¿y el m´s e u a a peque~o? n En este momento tenemos un poco de la calculadora y un poco de la 265 . ’4’. tiene dos n´meros y un operador: ’10’.¡HECHO! Dividir 10 entre 5 da 2 .¡HECHO! u La propiedad conmutativa no se cumple . ’-5’ y u ’/’ .¡HECHO! u Restar 2 al n´mero 3."2 + 2".¡HECHO! La cadena "2 + 2". ’* ’ .2 : devuelve un error o # Aceptaci´n ."3 / 2". 2 .¡HECHO! u # Aceptaci´n . devuelve 0 . ’2’ y ’+’ u .¡HECHO! # Aceptaci´n . ’/’ ¡HECHO! # Aceptaci´n . ’*’.¡HECHO! u La propiedad conmutativa se cumple ."2 + -2"."* 4 5 .¡HECHO! Dividir -10 entre -5 da 2 . devuelve 2 .¡HECHO! Dividir 10 entre -5 da -2 . 2] . devuelve -7 ."5 + 4 * 2 / 2". ’2’ y ’2’ y operadores ’+’. ¡qu´ sorpresa!. Nuestro parser funciona para expresiones m´s come a plejas sin par´ntesis. parse ( " 5 + 4 * 2 / 2 " )) § 1 2 3 4 ¦ ¥ Vaya.

Cap´ ıtulo 11 ExprAritmetica.py y creamos de nuevo el fichero ut supercalculadora. failUnlessEqual ( " 4 " . calcular ( " 2 + 2 " )) ¦ ¥ El test falla. parse ( expresion ) if expr_descompuesta [ ’ operadores ’ ][0] == ’+ ’: return str ( self . Una vez creado sigue en rojo porque la clase Supercalculadora no existe y.py. Supercalculadora ( expr_aritmetica . posteriormente. De todas formas. El fichero supercalculadora. vamos a integrar estas dos partes.11: supercalculadora. parser . § 1 2 3 4 5 6 7 8 9 11. sumar ( expr_descompuesta [ ’ operandos ’ ][0] . expr_descompuesta [ ’ operandos ’ ][1])) ¦ ¥ Ahora tenemos la luz verde de nuevo pero nos empezamos a dar cuenta de que parse va a ser dif´ de utilizar si queremos operar correctamente ıcil con la precedencia de operadores. calc = calculadora .10: ut supercalculadora.py import expr_aritmetica import calculadora class Supercalculadora : def __init__ ( self . TestCase ): def test_sumar ( self ): sc = supercalculadora .. Decidimos de antemano que a la calculadora le vamos a pasar expresiones aritm´ticas en forma de cadena de caracteres para e que las calcule. Calculadora () self . En este punto decia a a dimos que ExprAritmetica ser´ pasada como par´metro en el constructor a a de Supercalculadora ya que tiene un comportamiento muy definido. parser = parser def calcular ( self . Corrigiendo paso por paso llegamos a una e implementaci´n final que ser´ la siguiente: o a § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 11. La primera ser´ la m´s b´sica de la suma.py a ut main. parser ): self . Comenzamos con las pruebas. La clase principal ser´ Supercalculadora que usar´ Cala a culadora para calcular el resultado de las operaciones y ExprAritmetica para evaluar las expresiones. Vamos a ver qu´ dise˜o sacamos siguiendo TDD. vayamos paso a paso y creemos m´s pruebas. calc . ExprAritmetica ()) self . a 266 .py import supercalculadora import ut_calculadora import ut_expr_aritmetica class T e s t s S u p e r c a l c u l a d o r a ( unittest .py no existe. e n Renombramos el fichero actual ut supercalculadora. sc .. expresion ): expr_descompuesta = self . Vamos a dar un giro y en vez de seguir. el m´todo calcular tampoco.

devuelve -5 .2": devuelve un error o # Aceptaci´n . tiene dos n´meros y un operador: ’10’.¡HECHO! u Restar 2 al n´mero -5.¡HECHO! # Aceptaci´n .¡HECHO! La cadena "2 + 2".¡HECHO! u La propiedad conmutativa no se cumple . tiene dos n´meros y un operador: ’2’.4".. devuelve -7 .¡HECHO! Dividir -10 entre -5 da 2 . ExprAritmetica ()) self . Supercalculadora ( expr_aritmetica .¡HECHO! u Restar -2 al n´mero -7.¡HECHO! Dividir 10 entre 5 da 2 . ’/’ ¡HECHO! # Aceptaci´n .¡HECHO! u La propiedad conmutativa se cumple ..3".¡HECHO! # Aceptaci´n ."2 + 2"."5 + 4 * 2 / 2". creemos otros a incluso m´s sencillos a´n: a u § 1 2 3 4 5 . devuelve 6 "5 + 4 / 2 . ’*’. def test_restar ( self ): sc = supercalculadora . ’2’ y ’2’ y operadores ’+’."*45-2-": devuelve un error o ¿Qu´ n´mero es el m´s grande que se puede manejar?."* 4 5 . devuelve 9 "5 + 4 ."2 + -2"."* 4 5 ."5 .¡HECHO! u Restar 2 al n´mero 3."3 / 2". devuelve 9 o "5 + 4 * 2 / 2".3 pero antes.¡HECHO! u # Aceptaci´n . failUnlessEqual ( " 0 " . devuelve 4 o Sumar 2 al n´mero 2.2 " )) ¦ ¥ 267 .¡HECHO! o La cadena "10 / -5". ¿y el m´s e u a a peque~o? n Vayamos con el test m´s sencillo 5 + 4 . devuelve -1 .2 : devuelve un error o # Aceptaci´n . devuelve 2 o Restar 5 al n´mero 3. devuelve 4 . devuelve un error o Dividir 2 entre 2 da 1 .¡HECHO! # Aceptaci´n .2": devuelve un error o # Aceptaci´n . ’4’.Cap´ ıtulo 11 # Aceptaci´n .¡HECHO! o Dividir 3 entre 0 lanza una excepci´n ."* * 4 . devuelve 0 o Sumar 2 al n´mero -2. devuelve 3 Operandos son ’5’. ’2’ y ’+’ u . ’-5’ y u ’/’ . devuelve 2 . devuelve 0 . sc .¡HECHO! Dividir 10 entre -5 da -2 .3".¡HECHO! Dividir 3 entre 2 lanza una excepci´n . calcular ( " 2 .

calc . def t e s t _ e x p r e s i o n _ c o m p l e j a _ s i n _ p a r e n t e s i s _ c o n _ p r e c e d e n c i a ( self ): sc = supercalculadora . parser . sc . . parser . Creemos la prueba para esto y veamos como funciona. ¦ § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ¥ Falla.’: res = self . expr_descompuesta [ ’ operandos ’ ][1])) § 1 2 3 4 5 6 7 8 9 10 11 ¦ § 1 2 3 4 5 ¥ Y ahora si que nos ponemos con el test que hab´ ıamos identificado antes: def t e s t _ e x p r e s i o n _ c o m p l e j a _ s i n _ p a r e n t e s i s _ s i n _ p r e c e d e n c i a ( self ): sc = supercalculadora . . parse ( expresion ) res = 0 for i in range ( len ( expr_descompuesta [ ’ operadores ’ ])): if i == 0: res = expr_descompuesta [ ’ operandos ’ ][0] if expr_descompuesta [ ’ operadores ’ ][ i ] == ’+ ’: res = self .. Supercalculadora ( expr_aritmetica . calc . restar ( expr_descompuesta [ ’ operandos ’ ][0] . expr_descompuesta [ ’ operandos ’ ][ i + 1]) return str ( res ) .. ExprAritmetica ()) self .. sumar ( res . expresion ): expr_descompuesta = self . sc . parse ( expresion ) if expr_descompuesta [ ’ operadores ’ ][0] == ’+ ’: return str ( self . failUnlessEqual ( " 6 " .4 " )) ¦ ¥ Falla. ExprAritmetica ()) self . expr_descompuesta [ ’ operandos ’ ][1])) elif expr_descompuesta [ ’ operadores ’ ][0] == ’ . expresion ): expr_descompuesta = self . calc . ¦ ¥ § 1 2 3 4 5 Otra vez funciona pero sigo preocupado por la precedencia de operadores.. calcular ( " 5 + 4 . as´ que nos ponemos con la implementaci´n. calc . Lo hao ı o cemos pasar de la manera m´s sencilla (y fea.. lo que nos tem´ ıamos. expr_descompuesta [ ’ operandos ’ ][ i + 1]) if expr_descompuesta [ ’ operadores ’ ][ i ] == ’ . Supercalculadora ( expr_aritmetica . calcular ( " 5 + 4 / 2 .’: return str ( self . failUnlessEqual ( " 3 " . Una primera idea es buscar los operadores m´s prioritarios a y hacer la operaci´n y as´ ir poco a poco simplificando la expresi´n. ı o def calcular ( self . restar ( res .. def calcular ( self ..Cap´ ıtulo 11 Falla como era de esperar as´ que lo arreglamos con la implementaci´n ı o m´ ınima. sumar ( expr_descompuesta [ ’ operandos ’ ][0] .. Ahora tenemos que pensar c´mo solucionao mos este problema.. realmente fea) que se me ha a ocurrido: 268 ..3 " )) .

Cap´ ıtulo 11
§
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

def calcular ( self , expresion ): expr_descompuesta = self . parser . parse ( expresion ) try : i = expr_descompuesta [ ’ operadores ’ ]. index ( ’/ ’) res_intermedio = self . calc . dividir ( expr_descompuesta [ ’ operandos ’ ][ i ] , expr_descompuesta [ ’ operandos ’ ][ i + 1]) expr_descompuesta = { ’ operandos ’: [ expr_descompuesta [ ’ operandos ’ ][0] , res_intermedio , expr_descompuesta [ ’ operandos ’ ][3]] , ’ operadores ’: [ expr_descompuesta [ ’ operadores ’ ][0] , expr_descompuesta [ ’ operadores ’ ][2]]} except ValueError : pass res = 0 for i in range ( len ( expr_descompuesta [ ’ operadores ’ ])): if i == 0: res = expr_descompuesta [ ’ operandos ’ ][0] if expr_descompuesta [ ’ operadores ’ ][ i ] == ’+ ’: res = self . calc . sumar ( res , expr_descompuesta [ ’ operandos ’ ][ i + 1]) if expr_descompuesta [ ’ operadores ’ ][ i ] == ’ - ’: res = self . calc . restar ( res , expr_descompuesta [ ’ operandos ’ ][ i + 1]) return str ( res )

¦

¥

Da realmente miedo... Vamos a refactorizar primero y luego a a˜adir n m´s pruebas para este caso ya que sabemos que hay muchos m´s casos a a que hay que probar. Movamos la simplificaci´n de la expresi´n (evaluaci´n o o o intermedia) a otro m´todo llamado simplificar. No es que sea mucho mejor e pero es algo m´s legible. Con los nuevos tests, el c´digo ir´ tomando mejor a o a forma... o eso espero. Empezamos con los tests, luego con el c´digo. o
§
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

11.12: ut supercalculadora.py

class T e s t s S u p e r c a l c u l a d o r a ( unittest . TestCase ): def setUp ( self ): self . sc = supercalculadora . Supercalculadora ( expr_aritmetica . ExprAritmetica ()) def tearDown ( self ): pass def test_sumar ( self ): self . failUnlessEqual ( " 4 " , self . sc . calcular ( " 2 + 2 " )) def test_restar ( self ): self . failUnlessEqual ( " 0 " , self . sc . calcular ( " 2 - 2 " )) def t e s t _ e x p r e s i o n _ c o m p l e j a _ s i n _ p a r e n t e s i s _ s i n _ p r e c e d e n c i a ( self ): self . failUnlessEqual ( " 6 " , self . sc . calcular ( " 5 + 4 - 3 " ))

269

Cap´ ıtulo 11
def t e s t _ e x p r e s i o n _ c o m p l e j a _ s i n _ p a r e n t e s i s _ c o n _ p r e c e d e n c i a ( self ): self . failUnlessEqual ( " 3 " , self . sc . calcular ( " 5 + 4 / 2 - 4 " ))

18 19

¦ §
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35

¥

11.13: supercalculadora.py

def simplificar ( self , expr_descompuesta ): expr_simplificada = {} try : i = expr_descompuesta [ ’ operadores ’ ]. index ( ’/ ’) res_intermedio = self . calc . dividir ( expr_descompuesta [ ’ operandos ’ ][ i ] , expr_descompuesta [ ’ operandos ’ ][ i + 1]) expr_simplificada = { ’ operandos ’: [ expr_descompuesta [ ’ operandos ’ ][0] , res_intermedio , expr_descompuesta [ ’ operandos ’ ][3]] , ’ operadores ’: [ expr_descompuesta [ ’ operadores ’ ][0] , expr_descompuesta [ ’ operadores ’ ][2]]} except ValueError : expr_simplificada = expr_descompuesta return expr_simplificada def calcular ( self , expresion ): expr_simplificada = self . simplificar ( self . parser . parse ( expresion )) res = 0 for i in range ( len ( expr_simplificada [ ’ operadores ’ ])): if i == 0: res = expr_simplificada [ ’ operandos ’ ][0] if expr_simplificada [ ’ operadores ’ ][ i ] == ’+ ’: res = self . calc . sumar ( res , expr_simplificada [ ’ operandos ’ ][ i + 1]) if expr_simplificada [ ’ operadores ’ ][ i ] == ’ - ’: res = self . calc . restar ( res , expr_simplificada [ ’ operandos ’ ][ i + 1]) return str ( res )

¦

¥

Seguimos con un nuevo test algo m´s complejo en el mismo ´rea para a a mejorar nuestra implementaci´n. o
§
1 2 3 4

... def t e s t _ e x p r e s i o n _ c o m p l e j a _ s i n _ p a r e n t e s i s _ c o n _ p r e c e d e n c i a ( self ): self . failUnlessEqual ( " 3 " , self . sc . calcular ( " 5 + 4 / 2 - 4 " )) self . failUnlessEqual ( " -1 " , self . sc . calcular ( " 4 / 2 - 3 " ))

¦

¥

La implementaci´n para cumplir ambos tests ha quedado como sigue: o
§
1 2 3 4

def simplificar ( self , expr_descompuesta ): expr_simplificada = {} try :

270

Cap´ ıtulo 11
i = expr_descompuesta [ ’ operadores ’ ]. index ( ’/ ’) res_intermedio = self . calc . dividir ( expr_descompuesta [ ’ operandos ’ ][ i ] , expr_descompuesta [ ’ operandos ’ ][ i + 1]) expr_simplificada = expr_descompuesta expr_simplificada [ ’ operadores ’ ]. pop ( i ) expr_simplificada [ ’ operandos ’ ]. pop ( i ) expr_simplificada [ ’ operandos ’ ]. pop ( i ) expr_simplificada [ ’ operandos ’ ]. insert (i , res_interme dio ) except ValueError : expr_simplificada = expr_descompuesta return expr_simplificada def calcular ( self , expresion ): expr_simplificada = self . simplificar ( self . parser . parse ( expresion )) res = 0 for i in range ( len ( expr_simplificada [ ’ operadores ’ ])): if i == 0: res = expr_simplificada [ ’ operandos ’ ][0] if expr_simplificada [ ’ operadores ’ ][ i ] == ’+ ’: res = self . calc . sumar ( res , expr_simplificada [ ’ operandos ’ ][ i + 1]) if expr_simplificada [ ’ operadores ’ ][ i ] == ’ - ’: res = self . calc . restar ( res , expr_simplificada [ ’ operandos ’ ][ i + 1]) return str ( res )

5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35

¦

¥

Y en el paso de refactorizaci´n podemos mejorar mucho las cosas movieno do la l´gica a simplificar mientras resolvemos la expresi´n de una manera o o recursiva. Quiz´, incluso, creemos alg´n otro m´todo donde podamos moa u e ver alguna l´gica dentro del algoritmo que estamos creando. Como tenemos o pruebas unitarias ser´ f´cil hacer cambios m´s grandes en la refactorizaci´n a a a o sin que rompamos el funcionamiento del programa. En caso de que lo hagamos sin darnos cuenta, las pruebas unitarias nos alertar´n de los errores a cometidos y as´ los corregiremos f´cilmente. ı a Aprovechamos la ocasi´n tambi´n para repasar los nombres de todos los o e m´todos y mejorarlos para que todos muestren claramente la intenci´n de la e o funcionalidad y de las pruebas. 11.14: supercalculadora.py

§
1 2 3 4 5 6 7

def __operar__ ( self , expr_descompuesta ): i = None res_intermedio = 0 if ’/ ’ in expr_descompuesta [ ’ operadores ’ ]: i = expr_descompuesta [ ’ operadores ’ ]. index ( ’/ ’) res_intermedio = self . calc . dividir (

271

Cap´ ıtulo 11
expr_descompuesta [ ’ operandos ’ ][ i ] , expr_descompuesta [ ’ operandos ’ ][ i + 1]) elif ’ - ’ in expr_descompuesta [ ’ operadores ’ ]: i = expr_descompuesta [ ’ operadores ’ ]. index ( ’ - ’) res_intermedio = self . calc . restar ( expr_descompuesta [ ’ operandos ’ ][ i ] , expr_descompuesta [ ’ operandos ’ ][ i + 1]) elif ’+ ’ in expr_descompuesta [ ’ operadores ’ ]: i = expr_descompuesta [ ’ operadores ’ ]. index ( ’+ ’) res_intermedio = self . calc . sumar ( expr_descompuesta [ ’ operandos ’ ][ i ] , expr_descompuesta [ ’ operandos ’ ][ i + 1]) else : # Es un error , tenemos que decidir que hacer en los test # siguientes # Forzamos el error para que no haya problemas luego assert False return (i , res_intermedio ) def __simplificar__ ( self , expr_descompuesta ): if expr_descompuesta [ ’ operadores ’] == []: return expr_descompuesta (i , res_intermedio ) = self . __operar__ ( expr_descompuest a ) expr_simplificada = expr_descompuesta expr_simplificada [ ’ operadores ’ ]. pop ( i ) expr_simplificada [ ’ operandos ’ ]. pop ( i ) expr_simplificada [ ’ operandos ’ ]. pop ( i ) expr_simplificada [ ’ operandos ’ ]. insert (i , res_interme dio ) return self . __simplificar__ ( expr_simplificada ) def calcular ( self , expresion ): return str ( self . __simplificar__ ( self . parser . parse ( expresion ))[ ’ operandos ’ ][0]

8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43

¦

¥

Creamos un par de test unitarios m´s para estar seguros de que la ima plementaci´n funciona para casos m´s complejos o, en el peor de los casos, o a para cambiarla hasta que todas las pruebas pasen.
§
1 2 3 4 5 6 7 8 9

11.15: ut supercalculadora.py
def t e s t _ e x p r e s i o n _ c o m p l e j a _ s i n _ p a r e n t e s i s _ c o n _ p r e c e d e n c i a ( self ): self . failUnlessEqual ( " 3 " , self . sc . calcular ( " 5 + 4 / 2 - 4 " )) self . failUnlessEqual ( " -1 " , self . sc . calcular ( " 4 / 2 - 3 " )) self . failUnlessEqual ( " 1 " , self . sc . calcular ( " 4 / 2 - 3 + 1 + 6 / 3 - 1 " )) self . failUnlessEqual ( " -8 " , self . sc . calcular ( " 4 / -2 + 3 + -1 + -6 / -3 - 10 " ))

...

¦

¥

¡Qu´ suerte!, ¡luz verde de nuevo sin que hayamos tenido que cambiar e nada! Eso nos da pie para seguir con otras nuevas pruebas para seguir incrementando la funcionalidad poco a poco. Pero, antes de eso, es el momento de actualizar la libreta otra vez m´s. a 272

Cap´ ıtulo 11

# Aceptaci´n - "2 + 2", devuelve 4 o Sumar 2 al n´mero 2, devuelve 4 - ¡HECHO! u La propiedad conmutativa se cumple - ¡HECHO! La cadena "2 + 2", tiene dos n´meros y un operador: ’2’, ’2’ y ’+’ u - ¡HECHO! # Aceptaci´n - "5 - 3", devuelve 2 o Restar 5 al n´mero 3, devuelve 2 - ¡HECHO! u Restar 2 al n´mero 3, devuelve -1 - ¡HECHO! u La propiedad conmutativa no se cumple - ¡HECHO! # Aceptaci´n - "2 + -2", devuelve 0 o Sumar 2 al n´mero -2, devuelve 0 - ¡HECHO! u Restar 2 al n´mero -5, devuelve -7 - ¡HECHO! u Restar -2 al n´mero -7, devuelve -5 - ¡HECHO! u # Aceptaci´n - "5 + 4 * 2 / 2", devuelve 9 o "5 + 4 * 2 / 2", devuelve 9 "2 - 2", devuelve 0 - ¡HECHO! "5 + 4 - 3", devuelve 6 - ¡HECHO! "5 + 4 / 2 - 4", devuelve 3 - ¡HECHO! "4 / 2 - 3 + 1 + 6 / 3 - 1", devuelve 1 - ¡HECHO! "4 / -2 + 3 + -1 + -6 / -3 - 10", devuelve -8 - ¡HECHO! Operandos son ’5’, ’4’, ’2’ y ’2’ y operadores ’+’, ’*’, ’/’ ¡HECHO! # Aceptaci´n - "3 / 2", devuelve un error o Dividir 2 entre 2 da 1 - ¡HECHO! Dividir 10 entre 5 da 2 - ¡HECHO! Dividir 10 entre -5 da -2 - ¡HECHO! Dividir -10 entre -5 da 2 - ¡HECHO! Dividir 3 entre 2 lanza una excepci´n - ¡HECHO! o Dividir 3 entre 0 lanza una excepci´n - ¡HECHO! o La cadena "10 / -5", tiene dos n´meros y un operador: ’10’, ’-5’ y u ’/’ - ¡HECHO! # Aceptaci´n - "* * 4 - 2": devuelve un error o # Aceptaci´n - "* 4 5 - 2": devuelve un error o # Aceptaci´n - "* 4 5 - 2 : devuelve un error o # Aceptaci´n - "*45-2-": devuelve un error o ¿Qu´ n´mero es el m´s grande que se puede manejar?, ¿y el m´s e u a a peque~o? n

Ya s´lo nos queda una operaci´n, la multiplicaci´n. Vamos ir con ella o o o poniendo nuestra atenci´n en el test de aceptaci´n 5 + 4 * 2 / 2. Antes o o de ponernos con esta expresi´n, vamos a ir a por algo m´s sencillo como 4 o a 273

Cap´ ıtulo 11 * 2, -4 * 2, 4 * -2 y -4 * -2. Como siempre, seguimos la forma de trabajar TDD empezando por un test, haciendo la implementaci´n, refactorizando, despu´s tomando otro test, o e etc., repitiendo el algoritmo TDD paso a paso.
§
1 2 3 4 5 6 7 8

11.16: ut calculadora.py
def t e s t _ m u l t i p l i c a r _ s i m p l e ( self ): self . failUnlessEqual (8 , self . calc . multiplicar (4 , 2)) def t e s t _ m u l t i p l i c a r _ n e g a t i v a ( self ): self . failUnlessEqual ( -8 , self . calc . multiplicar ( -4 , 2)) self . failUnlessEqual ( -8 , self . calc . multiplicar (4 , -2)) self . failUnlessEqual (8 , self . calc . multiplicar ( -4 , -2))

...

¦ §
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

¥

11.17: calculadora.py

class Calculadora : def sumar ( self , a , b ): return a + b def restar ( self , a , b ): return a - b def multiplicar ( self , a , b ): return a * b def dividir ( self , a , b ): if a % b != 0: raise ValueError else : return a / b

¦

¥

Actualizamos la libreta antes de centrarnos en la expresi´n m´s compleja o a con suma, multiplicaci´n y divisi´n. o o

274

¡HECHO! Dividir -10 entre -5 da 2 . tiene dos n´meros y un operador: ’10’.¡HECHO! u Restar 2 al n´mero -5.2": devuelve un error o # Aceptaci´n .¡HECHO! u La propiedad conmutativa se cumple .¡HECHO! "4 / -2 + 3 + -1 + -6 / -3 . devuelve 9 o "5 + 4 * 2 / 2"."5 + 4 * 2 / 2".¡HECHO! # Aceptaci´n .¡HECHO! u # Aceptaci´n . ¿y el m´s e u a a peque~o? n 275 .¡HECHO! u La propiedad conmutativa no se cumple .2".¡HECHO! "2 . devuelve 2 o Restar 5 al n´mero 3.¡HECHO! u Restar -2 al n´mero -7.¡HECHO! "5 + 4 / 2 .2 : devuelve un error o # Aceptaci´n ."2 + 2".¡HECHO! # Aceptaci´n ."5 .¡HECHO! La cadena "2 + 2". devuelve 9 "4 * 2"."2 + -2".4".10".¡HECHO! Dividir 3 entre 2 lanza una excepci´n . devuelve 3 ."3 / 2".Cap´ ıtulo 11 # Aceptaci´n . devuelve -7 .¡HECHO! o Dividir 3 entre 0 lanza una excepci´n . ’2’ y ’+’ u . tiene dos n´meros y un operador: ’2’. devuelve un error o Dividir 2 entre 2 da 1 . devuelve -8 .3". devuelve -1 .¡HECHO! "4 * -2"."* 4 5 . devuelve 8 .¡HECHO! 4 * -2".¡HECHO! # Aceptaci´n .¡HECHO! 4 * 2". devuelve 0 .3". ’/’ ¡HECHO! # Aceptaci´n ."* 4 5 ."* * 4 . devuelve -8 .¡HECHO! "4 / 2 .3 + 1 + 6 / 3 .¡HECHO! Dividir 10 entre -5 da -2 .2": devuelve un error o # Aceptaci´n . devuelve 2 . devuelve 8 .¡HECHO! u Restar 2 al n´mero 3."*45-2-": devuelve un error o ¿Qu´ n´mero es el m´s grande que se puede manejar?. devuelve 0 o Sumar 2 al n´mero -2. ’2’ y ’2’ y operadores ’+’. ’-5’ y u ’/’ .¡HECHO! o La cadena "10 / -5". devuelve -5 . ’*’.1".¡HECHO! Operandos son ’5’. devuelve -8 . devuelve 1 . devuelve 6 . ’4’.¡HECHO! Dividir 10 entre 5 da 2 .¡HECHO! "5 + 4 . devuelve 4 o Sumar 2 al n´mero 2. devuelve 0 . devuelve 4 .

Voy a creear una prueba m´s que me de m´s a a confianza. self . dividir ( expr_descompuesta [ ’ operandos ’ ][ i ] . Sin embargo no me siento muy seguro de que esta implementaci´n sea correcta. expr_descompuesta [ ’ operandos ’ ][ i + 1]) elif ’* ’ in expr_descompuesta [ ’ operadores ’ ]: i = expr_descompuesta [ ’ operadores ’ ]. self . expr_descompuesta ): i = None res_intermedio = 0 if ’/ ’ in expr_descompuesta [ ’ operadores ’ ]: i = expr_descompuesta [ ’ operadores ’ ].18: ut supercalculadora. index ( ’/ ’) res_intermedio = self . ahora pasa y no hay mucho que refactorizar.19: supercalculadora. calc . calc . expr_descompuesta [ ’ operandos ’ ][ i + 1]) else : # Es un error .py 276 .20: ut supercalculadora. Por ejemplo. sumar ( expr_descompuesta [ ’ operandos ’ ][ i ] . calcular ( " 5 + 4 * 2 / 2 " )) ¦ ¥ Falla puesto que se va por la rama que no est´ implementada en operar .’) res_intermedio = self . § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 11. Vamos con ello con el test que ser´ parte de “expresi´n compleja a o con precedencia sin par´ntesis”. tenemos que decidir que hacer en los test # siguientes # Forzamos el error para que no haya problemas luego assert False return (i . a Nos ponemos con ello. res_intermedio ) ¦ ¥ Bien. calc . expr_descompuesta [ ’ operandos ’ ][ i + 1]) elif ’+ ’ in expr_descompuesta [ ’ operadores ’ ]: i = expr_descompuesta [ ’ operadores ’ ]. failUnlessEqual ( " 9 " .Cap´ ıtulo 11 Ahora nos ponemos con la expresi´n pendiente como parte de la supero calculadora.. 4 . index ( ’* ’) res_intermedio = self .py def t e s t _ e x p r e s i o n _ c o m p l e j a _ s i n _ p a r e n t e s i s _ c o n _ p r e c e d e n c i a ( self ): . Al fin y al cabo.. index ( ’+ ’) res_intermedio = self .py def __operar__ ( self . calc .-3 * 2 / 3 + 5 § 11. restar ( expr_descompuesta [ ’ operandos ’ ][ i ] . sc . expr_descompuesta [ ’ operandos ’ ][ i + 1]) elif ’ .’ in expr_descompuesta [ ’ operadores ’ ]: i = expr_descompuesta [ ’ operadores ’ ]. o tenemos precedencia y la divisi´n puede lanzar f´cilmente una excepci´n si o a o el resultado no es exacto. multiplicar ( expr_descompuesta [ ’ operandos ’ ][ i ] . e § 1 2 3 11.. index ( ’ ..

index ( ’* ’) res_intermedio = self .. Si lo pensamos.’ in expr_descompuesta [ ’ operadores ’ ]: i = expr_descompuesta [ ’ operadores ’ ]. expr_descompuesta [ ’ operandos ’ ][ i + 1]) elif ’/ ’ in expr_descompuesta [ ’ operadores ’ ]: i = expr_descompuesta [ ’ operadores ’ ]. index ( ’+ ’) res_intermedio = self . Calculadora () self . sumar ( expr_descompuesta [ ’ operandos ’ ][ i ] .21: supercalculadora. 277 . calc = calculadora . self . index ( ’/ ’) res_intermedio = self . La divisi´n lanza una excepci´n. calcular ( " 4 . calc . El c´digo no ha cambiado o o mucho y es aceptable. tiene sentido ya que si damos prioridad a la divisi´n antes que a o la multiplicaci´n la divisi´n podr´ ser no natural cuando lo ser´ si multiplio o ıa ıa camos primero. parser = parser def __operar__ ( self . sc .. Vamos a cambiar la implementaci´n y ver si hacemos pasar o todos los tests. class Supercalculadora : def __init__ ( self . calc . 11. ¡falla!. tenemos que decidir que hacer en los test # siguientes # Forzamos el error para que no haya problemas luego assert False return (i ..Cap´ ıtulo 11 . failUnlessEqual ( " 11 " . expr_descompuesta [ ’ operandos ’ ][ i + 1]) elif ’ . res_intermedio ) . restar ( expr_descompuesta [ ’ operandos ’ ][ i ] .. lo que nos tem´ o o ıamos. Es el turno de la refactorizaci´n. expr_descompuesta [ ’ operandos ’ ][ i + 1]) elif ’+ ’ in expr_descompuesta [ ’ operadores ’ ]: i = expr_descompuesta [ ’ operadores ’ ]. dividir ( expr_descompuesta [ ’ operandos ’ ][ i ] . sin embargo. self .-3 * 2 / 3 + 5 " )) 1 2 3 4 5 ¦ ¥ Vaya. tengo mis dudas con respecto a los tests.. parser ): self . calc .py § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 . index ( ’ ... expr_descompuesta ): i = None res_intermedio = 0 if ’* ’ in expr_descompuesta [ ’ operadores ’ ]: i = expr_descompuesta [ ’ operadores ’ ]. def t e s t _ e x p r e s i o n _ c o m p l e j a _ s i n _ p a r e n t e s i s _ c o n _ p r e c e d e n c i a ( self ): . calc .. expr_descompuesta [ ’ operandos ’ ][ i + 1]) else : # Es un error .’) res_intermedio = self . multiplicar ( expr_descompuesta [ ’ operandos ’ ][ i ] . ¦ ¥ ¡Ahora pasa!.

failUnlessEqual ( " 9 " . calcular ( " 4 / 2 . sc . failUnlessEqual ( " -8 " .py def t e s t _ e x p r e s i o n _ c o m p l e j a _ s i n _ p a r e n t e s i s _ c o n _ p r e c e d e n c i a ( self ): self .-3 * 2 / 3 + 5 " )) ¦ ¥ Hemos sacado las expresiones (en este caso s´lo una.3 " )) self . sc . failUnlessEqual ( " 1 " .3 + 1 + 6 / 3 . Actualizamos la libreta una ultima vez: ´ 278 . calcular ( " 5 + 4 * 2 / 2 " )) def t e s t _ e x p r _ c o m p l e j a _ t o d a s _ o p e r a c i o n e s _ s i n _ p a r e n t e s i s ( self ): self . failUnlessEqual ( " 11 " . sc . calcular ( " 4 / 2 . calcular ( " 5 + 4 / 2 . calcular ( " 4 / -2 + 3 + -1 + -6 / -3 . failUnlessEqual ( " -1 " . self . self . calcular ( " 4 . self . failUnlessEqual ( " 3 " .1 " )) self .10 " )) self . sc . self .4 " )) self . self .Cap´ ıtulo 11 Tenemos un test con muchas pruebas dentro y quiz´ es tiempo de revisarlo a y ver si podemos separarlo un poco. self . § 1 2 3 4 5 6 7 8 9 10 11 12 13 11.22: ut supercalculadora. sc . sc . por desgracia) que o utilizan todas las operaciones sin par´ntesis y son m´s propensas a dar errores e a en otra test que prueba expec´ ıficamente este caso.

devuelve 2 . devuelve un error o Dividir 2 entre 2 da 1 .¡HECHO! Operandos son ’5’."3 / 2".¡HECHO! 4 * 2". devuelve 0 o Sumar 2 al n´mero -2. tiene dos n´meros y un operador: ’2’. devuelve -5 . devuelve 11 .¡HECHO! Dividir -10 entre -5 da 2 .¡HECHO! 4 * -2".2".10".¡HECHO! "4 / -2 + 3 + -1 + -6 / -3 . devuelve 6 .1".¡HECHO! o Dividir 3 entre 0 lanza una excepci´n . devuelve 9 o "5 + 4 * 2 / 2"."2 + -2".¡HECHO! u La propiedad conmutativa no se cumple .¡HECHO! u # Aceptaci´n .¡HECHO! # Aceptaci´n . devuelve 9 .¡HECHO! "4 .3"."*45-2-": devuelve un error o ¿Qu´ n´mero es el m´s grande permitido?."2 + 2".¡HECHO! "4 * -2".¡HECHO! # Aceptaci´n ."* 4 5 .¡HECHO! # Aceptaci´n . devuelve -8 .2 : devuelve un error o # Aceptaci´n . devuelve 4 o Sumar 2 al n´mero 2."* 4 5 .3 + 1 + 6 / 3 . devuelve 1 .¡HECHO! Dividir 10 entre 5 da 2 . devuelve 0 ."5 + 4 * 2 / 2". devuelve -7 . ¿y el m´s peque~o? e u a a n 279 . devuelve -8 .¡HECHO! "4 * 2". devuelve 2 o Restar 5 al n´mero 3. devuelve -8 .Cap´ ıtulo 11 # Aceptaci´n . ’-5’ y u ’/’ .¡HECHO! "2 ."5 . devuelve -1 .3".¡HECHO! "5 + 4 .¡HECHO! Dividir 10 entre -5 da -2 . tiene dos n´meros y un operador: ’10’.¡HECHO! La cadena "2 + 2". ’/’ ¡HECHO! # Aceptaci´n .-3 * 2 / 3 + 5".4".¡HECHO! u La propiedad conmutativa se cumple . ’2’ y ’+’ u . ’4’.¡HECHO! u Restar 2 al n´mero 3. devuelve 8 . devuelve 3 .2": devuelve un error o # Aceptaci´n . devuelve 8 . ’2’ y ’2’ y operadores ’+’.¡HECHO! "5 + 4 / 2 ."* * 4 .¡HECHO! u Restar 2 al n´mero -5. devuelve 4 .¡HECHO! u Restar -2 al n´mero -7.2": devuelve un error o # Aceptaci´n . ’*’.¡HECHO! o La cadena "10 / -5". devuelve 0 .¡HECHO! "4 / 2 .¡HECHO! Dividir 3 entre 2 lanza una excepci´n .

No voy a explicar el uso de mocks y stubs ya que se ha explicado5 y ver´ que es igual nada m´s que cambiando la sintaxis del lenguaje. validar ( " 2 ^ 3 " ). Esto lo e o podemos hacer con un stub.com/p/pymox/ 280 .py def t e s t _ v a l i d a d o r _ e x p r e s i o n _ i n v a l i d a _ s t u b ( self ): validador_stub = v a l i d a d o r _ e x p r _ a r i t m e t i c a . failUnlessRaises ( SyntaxError . Supercalculadora ( exp_aritmetica . 5 6 Ver Cap´ ıtulo 6 en la p´gina 95 a http://code. a a Simplemente quiero mostrar una posible soluci´n en Python usando pymox 6 o para que vea que en este lenguaje se puede lograr lo mismo que en .Cap´ ıtulo 11 Para terminar. V a l i d a d o r E x p r A r i t m e t i c a () validar_mock = mox . Pong´monos con las implementaci´n corrigiendo a o poco a poco los errores. validador_stub ) self . tenemos que comprobar que el m´todo a e es llamado. UnsetStubs () validar_mock . o e necesitamos tomar ciertas decisiones en el dise˜o. Sin ıa embargo. obviamente. a a a a Suponemos tambi´n que el validador va a responder con un booleano si la e expresi´n es v´lida o no.NET y Java. ReplayAll () sc = supercalculadora .. calcular . VerifyAll () . vamos a probar s´lo que la expresi´n o a o o es inv´lida pero.. como ya hemos dicho. Pensemos ahora en las expresiones aritm´ticas. En alg´n a u u momento lo necesitaremos pero todav´ es pronto para ponernos a ello. AndReturn ( False ) validar_mock . Vamos a crear un test usando pymox que simule la respuesta de validar cada vez que calculamos una expresi´n aritm´tica. ’ valida r ’) validador_stub . Para esto. ¦ ¥ Esto falla puesto que el Supercalculadora s´lo recibe un par´metro y o a no dos en su constructor y tambi´n porque no tenemos ni siquiera un esquee leto de la clase Validador. " 2 ^ 3 " ) validar_mock . vamos a crear una ultima prueba con un stub y vamos a ´ cambiar nuestro dise˜o en base a esto sin necesidad de implementar nada (o n casi nada). ExprAritmetica () . queremos ver c´mo se comportar´ nuestro c´digo si tuvi´ramos o ıa o e ese validador y que ´ste nos dijese que una expresi´n es invalida. sc . Mox () validar_mock .23: ut supercalculadora. El test quedar´ como sigue: ıa § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 11. como por ejemplo que la n clase Supercalculadora recibir´ un par´metro m´s que ser´ el validador. StubOutWithMock ( validador_stub . Por ahora suponemos e que est´n bien y no tenemos ning´n validador que detecte errores.google. En este punto.

26: supercalculadora. o § 1 2 3 4 5 6 7 8 9 10 11 12 11. parser = parser self . parser . en ut supercalculadora..24: supercalculadora. calc = calculadora . V a l i d a d o r E x p r A r i t m e t i c a ()) 281 . class Supercalculadora : def __init__ ( self .py tenemos que importar el nuevo ´ fichero de validaci´n. validar ( expresion ): raise SyntaxError ( " La expresion no es valida " ) return str ( self . expresion ): True ¦ ¥ Por ultimo. Como la llamada al constructor est´ en el setUp o a y este es llamado por todas las pruebas. o En este punto vemos que la prueba a´n falla porque la excepci´n por u o validaci´n no se produce. Calculadora () self . ¦ § 1 2 3 ¥ 11.py mox unittest v a l i d a d o r _ e x p r _ a r i t m e t i c a as validador exp_aritmetica supercalculadora import import import import import class T e s t s S u p e r c a l c u l a d o r a ( unittest . TestCase ): def setUp ( self ): self .. parse ( expresion ))[ ’ operandos ’ ][0]) .py class V a l i d a d o r E x p r A r i t m e t i c a : def validar ( self .27: ut supercalculadora. ¦ ¥ La prueba pasa ahora as´ que pong´monos a corregir las otras pruebas en ı a el paso de refactorizaci´n.. Corrijamos primero el error de la ultima prueba y despu´s ´ e pasemos a corregir (mejor dicho a actualizar) todas las dem´s. solamente tenemos que cambiar el constructor aqu´ y todas las pruebas pasar´n ya que vamos a utilizar la ı a “implementaci´n” real que siempre devuelve “True”. parser . expresion ): if not self .. ExprAritmetica () . validador ): self ... a § 1 2 3 4 5 6 7 11. validador . vemos que las dem´s pruebas se han roto o a a ya que hemos cambiado el constructor (hemos a˜adido el validador como un n nuevo argumento). Adem´s. __simplificar__ ( self . validador .Cap´ ıtulo 11 11. validador = validador .py § 1 2 3 4 5 6 7 . sc = supercalculadora . Supercalculadora ( exp_aritmetica .py def calcular ( self .25: validador expr aritmetica.

addTest ( unittest .py import unittest 282 . failUnlessEqual ( " 1 " .2 " )) def t e s t _ e x p r e s i o n _ c o m p l e j a _ s i n _ p a r e n t e s i s _ s i n _ p r e c e d e n c i a ( self ): self . calcular ( " 4 / 2 . S´ digo “nuevo” porque durante este ejercicio deber´ ı. TextTestRunner ( verbosity =3). failUnlessEqual ( " 0 " . failUnlessEqual ( " 11 " . failUnlessEqual ( " 3 " . self .py y validador expr aritmetica. calcular ( " 4 / -2 + 3 + -1 + -6 / -3 . calcular ( " 5 + 4 / 2 . failUnlessEqual ( " 6 " . makeSuite ( ut_calculadora .10 " )) self . calcular ( " 4 . una vez m´s. self . makeSuite ( ut_expr_aritmetica . § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 11. self . T e s t s S u p e r c a l c u l a d o r a )) unittest . failUnlessEqual ( " -1 " .4 " )) self . calcular ( " 5 + 4 * 2 / 2 " )) def t e s t _ e x p r _ c o m p l e j a _ t o d a s _ o p e r a c i o n e s _ s i n _ p a r e n t e s i s ( self ): self . por ejemplo cada vez que ten´ o ıamos una nueva funcionalidad en verde despu´s de unas cuantas pruebas.-3 * 2 / 3 + 5 " )) 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 ¦ ¥ Ahora es el momento de actualizar la libreta y hacer un nuevo commit al repositorio. failUnlessEqual ( " -8 " . self .28: ut main. makeSuite ( ut_supercalculadora . self .py). self . calcular ( " 2 + 2 " )) def test_restar ( self ): self . self . TestSuite () suite .29: ut calculadora. calcular ( " 5 + 4 .3 " )) def t e s t _ e x p r e s i o n _ c o m p l e j a _ s i n _ p a r e n t e s i s _ c o n _ p r e c e d e n c i a ( self ): self . sc . e Para clarificar todo el c´digo. failUnlessEqual ( " 9 " . sc .Cap´ ıtulo 11 def tearDown ( self ): pass def test_sumar ( self ): self . que no olvidemos son parte o del c´digo) frecuentemente. sc . calcular ( " 4 / 2 . self . sc .1 " )) self . sc . self . sc .py unittest ut_calculadora ut_supercalculadora ut_expr_aritmetica import import import import if __name__ == " __main__ " : suite = unittest .3 + 1 + 6 / 3 . addTest ( unittest . sc . failUnlessEqual ( " 4 " .3 " )) self . run ( suite ) ¦ § 1 ¥ 11. aqu´ est´n todos los ficheros o a ı a que hemos creado (menos las ya mencionados ut supercalculadora. calcular ( " 2 . sc . TestsCalculadora )) suite . ıamos haber subido al repositorio el c´digo (y las pruebas. TestsExprAritmetica )) suite . addTest ( unittest . sc .

self . failUnlessEqual (2 . self . failUnlessEqual ( self . restar ( -7 . failUnlessEqual ( -8 . 2) def test_division_por_0 ( self ): self . calc . failUnlessEqual (0 . dividir . self . restar (3 . failUnlessEqual (8 . 3)) def t e s t _ r e s t a _ n e g a t i v a _ n u m e r o s _ d i s t i n t o s ( self ): self . 2)) self . failUnlessRaises ( ValueError . -5)) def t e s t _ d i v i s i o n _ n o _ e n t e r a _ d a _ e x c e p c i o n ( self ): self . Calculadora () def tearDown ( self ): pass def t e s t _ s u m a r _ n u m e r o s _ i g u a l e s ( self ): self . self . 3 . self . restar (5 . self . failUnlessEqual (12 . self . restar (2 . calc . calc . calc . 2)) def t e s t _ m u l t i p l i c a r _ n e g a t i v a ( self ): self . calc . failUnlessEqual (2 . failUnlessEqual ( -1 . 5)) def t e s t _ d i v i s i o n _ e x a c t a ( self ): self . self . calc . self . multiplicar ( -4 . failUnlessEqual (4 . 5)) def t e s t _ s u m a r _ n u m e r o s _ n e g a t i v o s ( self ): self . sumar (2 . calc . 3) . self . sumar (2 . calc . -2)) 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 283 . self . 7) .Cap´ ıtulo 11 import calculadora class TestsCalculadora ( unittest . self . restar ( -5 . failUnlessEqual (8 . 2)) self . sumar (5 . failUnlessEqual ( -2 . dividir . multiplicar ( -4 . calc . multiplicar (4 . failUnlessEqual (2 . dividir (2 . 5)) def t e s t _ d i v i s i o n _ e x a c t a _ n u m e r o s _ n e g a t i v o s ( self ): self . calc . multiplicar (4 . self . self . self . self . calc . TestCase ): def setUp ( self ): self . -2)) self . dividir ( -10 . calc . 3 . 7)) def t e s t _ s u m a r _ p r o p i e d a d _ c o n m u t a t i v a ( self ): self . calc . 0) def t e s t _ m u l t i p l i c a r _ s i m p l e ( self ): self . -2)) def t e s t _ r e s t a r _ n o _ p r o p i e d a d _ c o n m u t a t i v a ( self ): self . calc . calc . calc . calc . self . 2)) self . failUnlessEqual (1 . self . failUnlessEqual ( -7 . failUnlessRaises ( ZeroDivisionError . sumar (5 . failIfEqual ( self . calc = calculadora . failUnlessEqual ( -8 . calc . calc . restar (5 . 3)) def t e s t _ r e s t a r _ n u m e r o s _ n e g a t i v o s ( self ): self . dividir (10 . -2)) def t e s t _ r e s t a _ p o s i t i v a _ n u m e r o s _ d i s t i n t o s ( self ): self . sumar (7 . -5)) self . failUnlessEqual ( -5 . calc . dividir (10 . self . 2)) def t e s t _ s u m a r _ n u m e r o s _ d i s t i n t o s ( self ): self . calc .

Cap´ ıtulo 11 ¦ § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ¥ 11. ’ operadores ’: [ ’+ ’]} . expresion = expr_aritmetica . index ( ’ . failUnlessEqual ({ ’ operandos ’: [2 . validador ): self . ’* ’ . calc = calculadora . index ( ’+ ’) res_intermedio = self . parser = parser self . 4 .py import exp_aritmetica import calculadora class Supercalculadora : def __init__ ( self . restar ( expr_descompuesta [ ’ operandos ’ ][ i ] . Calculadora () self . expr_descompuesta [ ’ operandos ’ ][ i + elif ’+ ’ in expr_descompuesta [ ’ operadores ’ ]: i = expr_descompuesta [ ’ operadores ’ ]. parse ( " 5 + 4 * 2 / 2 " )) ¦ § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 ¥ 11. index ( ’/ ’) res_intermedio = self . calc . calc .30: ut expr aritmetica. expr_descompuesta [ ’ operandos ’ ][ i + 1]) 1]) 1]) 1]) 284 . calc .31: supercalculadora. validador = validador def __operar__ ( self . self . 2] . calc .py import unittest import expr_aritmetica class TestsExprAritmetica ( unittest . index ( ’* ’) res_intermedio = self . parser . ’/ ’]} . failUnlessEqual ({ ’ operandos ’: [5 . 2] . TestCase ): def setUp ( self ): self . expr_descompuesta [ ’ operandos ’ ][ i + elif ’/ ’ in expr_descompuesta [ ’ operadores ’ ]: i = expr_descompuesta [ ’ operadores ’ ]. expresion . ExprAritmetica () def tearDown ( self ): pass def t e s t _ e x t r a e r _ o p e r a n d o s _ y _ o p e r a d o r e s _ e n _ 2 _ m a s _ 2 ( self ): self . 2 . multiplicar ( expr_descompuesta [ ’ operandos ’ ][ i ] . ’ operadores ’: [ ’+ ’ . expr_descompuesta ): i = None res_intermedio = 0 if ’* ’ in expr_descompuesta [ ’ operadores ’ ]: i = expr_descompuesta [ ’ operadores ’ ]. dividir ( expr_descompuesta [ ’ operandos ’ ][ i ] . expr_descompuesta [ ’ operandos ’ ][ i + elif ’ . sumar ( expr_descompuesta [ ’ operandos ’ ][ i ] . self .’ in expr_descompuesta [ ’ operadores ’ ]: i = expr_descompuesta [ ’ operadores ’ ]. parse ( " 2 + 2 " )) def t e s t _ e x t r a e r _ o p e r a n d o s _ y _ o p e r a d o r e s _ e x p r _ s i n _ p t e s i s ( self ): self .’) res_intermedio = self . expresion .

a . __simplificar__ ( self . parser . tenemos que decidir que hacer en los test # siguientes # Forzamos el error para que no haya problemas luego assert False return (i . res_interme dio ) return self . pop ( i ) expr_simplificada [ ’ operandos ’ ]. b ): return a * b def dividir ( self . b ): if a % b != 0: raise ValueError else : return a / b ¦ § 1 2 3 4 5 6 7 ¥ 11. parse ( expresion ))[ ’ operandos ’ ][0]) ¦ § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ¥ 11. res_intermedio ) = self . a . cadena ): try : string . a . pop ( i ) expr_simplificada [ ’ operandos ’ ]. atoi ( cadena ) return True 285 . validar ( expresion ): raise SyntaxError ( " La expresion no es valida " ) return str ( self .b def multiplicar ( self .32: calculadora. expresion ): if not self . validador . a . __operar__ ( expr_descompuest a ) expr_simplificada = expr_descompuesta expr_simplificada [ ’ operadores ’ ]. __simplificar__ ( expr_simplificada ) def calcular ( self . insert (i .py class Calculadora : def sumar ( self . expr_descompuesta ): if expr_descompuesta [ ’ operadores ’] == []: return expr_descompuesta (i . pop ( i ) expr_simplificada [ ’ operandos ’ ].py import string class ExprAritmetica : def __es_numero__ ( self . b ): return a .Cap´ ıtulo 11 else : # Es un error . res_intermedio ) 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 def __simplificar__ ( self . b ): return a + b def restar ( self .33: expr aritmetica.

. TestsCalculadora ) .. T e s t s S u p e r c a l c u l a d o r a ) . __es_numero__ ( token ): operandos .. TestsCalculadora ) . ok test_sumar_propiedad_conmutativa ( ut_calculadora . ok test_division_no_entera_da_excepcion ( ut_calculadora ... ok test_restar ( ut_supercalculadora . ok test_division_exacta_numeros_negativos ( ut_calculadora . ok test_restar_numeros_negativos ( ut_calculadora . TestsCalculadora ) . ’ operadores ’: operadore s } 8 9 10 11 12 13 14 15 16 17 18 19 20 ¦ ¥ Lo ultimo antes de acabar este cap´ ´ ıtulo...... T e s t s S u p e r c a l c u l a d o r a ) . TestsCalculadora ) . ok test_sumar_numeros_iguales ( ut_calculadora . ok test_multiplicar_negativa ( ut_calculadora . TestsCalculadora ) . ok test_sumar_numeros_distintos ( ut_calculadora .. TestsCalculadora ) .. ok test_resta_negativa_numeros_distintos ( ut_calculadora . T e s t s S u p e r c a l c u l a d o r a ) . atoi ( token )) else : operadores .. TestsCalculadora ) ...... TestsExpAritmetica ) .. TestsExpAritmetica ) . la salida despu´s de correr todos e los tests: § 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 test_division_exacta ( ut_calculadora .. TestsCalculadora ) . TestsCalculadora ) . TestsCalculadora ) . ok test_resta_positiva_numeros_distintos ( ut_calculadora . ok test_restar_no_propiedad_conmutativa ( ut_calculadora .. ok test_division_por_0 ( ut_calculadora . TestsCalculadora ) ... ok test_expr_compleja_todas_operaciones_sin_parentesis ( ut_supercalculadora .Cap´ ıtulo 11 except ValueError : return False def parse ( self .. split () for token in tokens : if self . T e s t s S u p e r c a l c u l a d o r a ) .. TestsCalculadora ) . ok test_expresion_compleja_sin_parentesis_con_precedencia ( ut_supercalculadora ..... ok test_extraer_operandos_y_operadores_en_2_mas_2 ( ut_exp_aritmetica .. ok test_expresion_compleja_sin_parentesis_sin_precedencia ( ut_supercalculadora . ok test_extraer_operandos_y_operadores_expr_sin_ptesis ( ut_exp_aritmetica . append ( token ) return { ’ operandos ’: operandos .. ok test_multiplicar_simple ( ut_calculadora ... exp ): operandos = [] operadores = [] tokens = exp .. TestsCalculadora ) . TestsCalculadora ) . ok test_sumar_numeros_negativos ( ut_calculadora .. append ( string ..... ok test_sumar 286 ..

o Todav´ hay mucho trabajo que hacer para completar la Supercalculaıa dora. o 287 . etc´tera) y lo que tengamos ser´.000 s OK 42 43 44 45 46 47 48 49 ¦ ¥ En este punto. por ejemplo. bastante distinto a lo que hemos llegado ahora.Cap´ ıtulo 11 ( ut_supercalculadora . T e s t s S u p e r c a l c u l a d o r a ) .. tenemos una calculadora que hace todas las operaciones y lanza excepciones si hay error como hab´ ıamos dise˜ado. proa e a bablemente.. tenemos n a una funcionalidad b´sica para manejar expresiones. Adem´s. ok test_validador_expresion_invalida_mock ( ut_supercalculadora . El dise˜o cambiar´ cuando haya m´s a n a a pruebas (podemos intuir que expresi´n aritm´tica tendr´ que usar un parser o e a “de verdad” y quiz´ un tokenizer. muchas m´s pruebas y casos.. ahora mismo la clase principal no sabe qu´ hacer (aunque deber´ mose ıa trar un error) si la Calculadora. a Merece la pena mencionar que aunque la calculadora sea capaz de lanzar excepciones en caso de error y de que tengamos test unitarios para ello. Sin embargo.. sin par´ntesis a e y est´ preparada para seguir con el validador de expresiones. T e s t s S u p e r c a l c u l a d o r a ) . lanzase una excepci´n. Es o decir. esperamos que este ejemplo haya mostrado como hacer TDD para crear la aplicaci´n y le haya ayudado a entender como usar TDD con Python. no hemos hecho lo mismo a nivel de aplicaci´n (para Supercalculadora). ok ------------------------------------------------------------------Ran 22 tests in 0. Esto o hay que tratarlo con nuevos tests unitarios a nivel de Supercalculadora y con el test de aceptaci´n que tenemos para este caso. por ahora.

Cap´ ıtulo 11 288 .

es el SUT. No sabemos qui´n es el SUT y qui´n es el colaborador e e En los tests de validaci´n de interacci´n. es interesante citar los errores t´ ıpicos que cometemos cuando empezamos a practicar TDD por primera vez. en otras. En el caso de un test. pensamos que el colaborador. Este cap´ ıtulo no pretende cubrirlos todos ni mucho menos. Errores del principiante El nombre del test no es suficientemente descriptivo Recordemos que el nombre de un m´todo y de sus par´metros son su e a mejor documentaci´n. o o aquel que representamos mediante un doble.Cap´tulo ı 12 Antipatrones y Errores comunes Hay una amplia gama de antipatrones en que podemos incurrir cuando estamos practicando TDD. Antes de utilizar dobles de prueba tenemos que estar completamente seguros de qui´n es e 289 . o No sabemos qu´ es lo que queremos que haga el SUT e Nos hemos lanzado a escribir un test pero no sabemos en realidad qu´ es e lo que el c´digo bajo prueba tiene que hacer. lo resolo vemos hablando con el due˜o de producto y. Antes de pasar n a a ver antipatrones. su nombre debe expresar con o total claridad la intenci´n del mismo. sino dar un peque˜o repaso a los m´s comunes. Tenga en cuenta que est´ tomando decisiones de dise˜o al a n escribir el test y que la programaci´n por parejas o las revisiones de c´digo o o nos ayudan en la toma de decisiones. En algunas ocasiones. hablando con otros n desarrolladores.

No olvide buscar y corregir c´digo duplicado despu´s de e o e hacer pasar cada test. e ´ funcionalidad del SUT. a No obstante. En e e general.Cap´ ıtulo 12 el SUT y qui´n es el colaborador y qu´ es lo que queremos comprobar. comprobamos que el SUT habla con el colaborador. a Se nos olvida refactorizar No s´lo por tener una gran bater´ de tests. usamos un mock. As´ que tenemos que conseguir ı que se ejecuten en menos de un segundo. si le hablan. At´mico. lo encontramos directamente y lo corregimos en dos minutos. Para que esto sea as´ cada m´todo debe probar una unica ı. Cuando queremos confirmar que efectivamente la llamada a un m´todo se produce. si con un poco m´s de esfuerzo puede conseguir que lo hagan. o 1 Revise el Cap´ ıtulo 6 en la p´gina 95 para m´s informaci´n a a o 290 . Esta es la raz´n fundamental para o tenerlos separados de los tests de integraci´n o R´pido. responda algo que le decimos. Preg´ntese si sus tests cumplen las reglas y. Independiente a o Si rompe alguna de sus propiedades. Si el c´digo no est´ limpio. De hecho. o bien le decimos al colaborador que. o o Confundir un mock con un stub Cuando queremos que un objeto falso devuelva una respuesta programada en caso de que se le llame. Un m´todo o e de test raramente excede las 10 l´ ıneas de c´digo. el c´digo ya es m´s f´cil o ıa o a a de mantener. en muchas ocasiones la mejor soluci´n pasa por usar un mock1 . ser´ muy costoso modificarlo. se ejecutan continuamente cuando practicamos TDD. apenas tenemos que usar el depurador. usamos un stub. Un mismo m´todo de test est´ haciendo m´ ltiples afirmaciones e a u Cuando practicamos TDD correctamente. Inocuo. en caso de que no sea as´ piense u ı. Cuando un test falla. o Los test unitarios no est´n separados de los de integraci´n a o Los tests unitarios se ejecutan frecuentemente. y o a a tambi´n sus tests. El c´digo de los tests debe estar tan limpio como el o c´digo de producci´n. A veces utilizamos varias afirmaciones (asserts) en el mismo test. Un e mock es m´s restrictivo que un stub y puede aumentar la fragilidad del test. pero s´lo si giran en torno a la misma funcionalidad. entonces no es un test unitario.

o o Puede ser c´digo de producci´n o pueden ser tests. debemos eliminarlo de la versi´n en o o producci´n. tras cambios en la l´gica de negocio.org/wiki/God object 3 2 291 . a El Gigante Aunque prueba correctamente el objeto en cuesti´n. Esto puede ser un indicador de que el sistema que estamos probando es un Objeto Dios4 http://blog.org La traducci´n ha sido mejorada para el libro porque en el blog est´ bastante mal o a 4 http://en. lo cual nos impide saber qu´ es e lo que se est´ probando debido a tanto “ruido”.wikipedia. queda c´digo en desuso. puede contener miles o de l´ ıneas y probar much´ ısimos casos de uso. el c´digo de los o tests es tan importante como el c´digo que prueban. Los nombres n o que les pusieron tienen un car´cter c´mico y no son en absoluto oficiales pero a o su contenido dice mucho. El Mentiroso Un test completo que cumple todas sus afirmaciones (asserts) y parece ser v´lido pero que cuando se inspecciona m´s de cerca. Algunos de ellos ya est´n recogidos en los errores a comentados arriba. o Antipatrones James Carr2 recopil´ una lista de antipatrones ayudado por la comunidad o TDD que traduje en mi blog3 y que ahora a˜ado a esta secci´n.Cap´ ıtulo 12 No eliminamos c´digo muerto o A veces. Puesto que disponemos o o de un sistema de control de versiones que nos permite volver atr´s si alguna a vez volviese a hacer falta el c´digo. Se suele o o e menospreciar cuando se trata de tests pero. A o veces se usan varios cientos de l´ ıneas de c´digo para configurar el entorno de o dicho test. muestra que realmente a a no est´ probando su cometido en absoluto.james-carr. como ha visto. El c´digo muerto induce a errores antes o despu´s. a Setup Excesivo Es un test que requiere un mont´n de trabajo para ser configurado. con varios objetos involucrados.

El Inspector Viola la encapsulaci´n en un intento de conseguir el 100 % de cobertura o de c´digo y por ello sabe tanto del objeto a prueba que. Un ejemplo t´ ıpico es poner rutas que son espec´ ıficas de una persona. stubs y/o falsificaciones.Cap´ ıtulo 12 El Imitador A veces. El test est´ en verdad confiando en que se lanzar´ una a a excepci´n en caso de que ocurra alg´n accidente desafortunado y que el o u framework de tests la capturar´ reportando el fracaso. el desaa rrollador se puede perder imitando los objetos colaboradores. cuando en realio dad s´lo est´ interesado en peque˜as partes de ella. El Cazador Secreto A primera vista parece no estar haciendo ninguna prueba por falta de afirmaciones (asserts). Esto se traduce en que el o a n test tiene que ser continuamente mantenido a pesar de que los cambios sean insignificantes. Ejemplo. el test que usa esos datos falla e por completo. En su lugar estamos probando lo que los mocks est´n a a devolviendo. Este es end´mico de los tests de aplicaciones web. Si el “generador” de los datos se ejecuta despu´s. usar mocks puede estar bien y ser pr´ctico pero otras. o no se llega a ejecutar. El resultado es que el test pasa en dicho entorno pero falla en cualquier otro sitio. a 292 . En este caso un test contiene tantos mocks. e comparar todo un html de salida cuando solo se necesita saber si el title es correcto. rompe el test. que el SUT ni siquiera se est´ probando. como una referencia a un fichero en su escritorio. El H´roe Local e Depende del entorno de desarrollo espec´ ıfico en que fue escrito para poder ejecutarse. Sobras Abundantes Es el caso en que un test crea datos que se guardan en alg´n lugar y u otro test los reutiliza para sus propios fines. El Cotilla Quisquilloso Compara la salida completa de la funci´n que se prueba. cualquier intento de o refactorizarlo.

o e ıa ıa El Enumerador Una bater´ de tests donde cada test es simplemente un nombre seguido ıa de un n´mero. o 293 . forzando al desarrollador a indagar o por acres de c´digo para encontrar qu´ datos se supon´ que deb´ haber. Dependencia Oculta Un primo hermano del H´roe Local. Esto supone que la misi´n del test no u o queda clara y la unica forma de averiguarlo es leer todo el test y rezar para ´ que el c´digo sea claro. El Secuenciador Un test unitario que depende de que aparezcan. El Cazador Hambriento Captura excepciones y no tiene en cuenta sus trazas. test3. en lugar de limpiarlo. incluso cuando los tests pasan. elementos de una lista sin ordenar.Cap´ ıtulo 12 El Escaqueado Un test que hace muchas pruebas sobre efectos colaterales (presumiblemente f´ciles de hacer) pero que nunca prueba el aut´ntico comportamiento a e deseado. y dem´s forraje. a veces reemplaz´ndolas con un mensaje menos informativo. un test que requiere que existan e ciertos datos en alguna parte antes de correr. de depuraci´n. ej. se est´ comprobando que dicho m´todo no e a e alter´ ciertos datos o. A veces puede encontrarse en tests de acceso a base de datos. el test falla sin dejar apenas explicaci´n. test2. A o a veces. en el mismo orden. de log. cuando se termina. es necesario mostrar salida por pantalla. se deja ah´ aunque ı ya no haga falta. durante la creaci´n de un test. lo que es lo mismo. donde el m´todo a prueba se llama. En lugar de comprobar que el m´todo hace lo que debe. o o n El Bocazas Un test o bater´ de tests que llenan la consola con mensajes de diagn´stiıa o co. pero otras incluso registrando a el suceso en un log y dejando el test pasar. que no caus´ da˜os. o y lo que ocurre en este caso es que. Si los datos no se rellenaron. test1. despu´s el test selecciona datos de la base e e de datos y hace afirmaciones sobre el resultado.

no se limpiaban a adecuadamente despu´s de la ejecuci´n. Una indicaci´n o com´n de eso es que el test tiene el mismo nombre que su clase y contiene u m´ltiples l´ u ıneas de setup y afirmaciones.Cap´ ıtulo 12 El Extra˜ o n Un test que ni siquiera pertenece a la clase de la cual es parte. Un buen ejemplo ser´ un test que usa la secuencia de nueva l´ ıa ınea de Windows en la afirmaci´n (assert). muy probablemente usado por el que se est´ probando en la clase actual (objeto Y). El Macho Chill´n o Debido a recursos compartidos puede ver los datos resultantes de otro test y puede hacerlo fallar incluso aunque el sistema a prueba sea perfectamente v´lido. usadas para guardar colecciones. o e El que Siempre Funciona Se escribi´ para pasar en lugar de para fallar primero. y no directamente. se a˜ade una nueva afirmaci´n (assert) dentro de un test existente. a menudo repercutiendo de manera e o inesperada en otros tests. Tambi´n conocido como El hu´sped no invitado. El Libre Albedr´ ıo En lugar de escribir un nuevo test unitario para probar una nueva funcionalidad. pero salt´ndose la interacci´n a a o que hay entre ambos. e El Evangelista de los Sistemas Operativos Conf´ en que un sistema operativo espec´ ıa ıfico se est´ usando para ejecua tarse. sucede que el test siempre funciona. n o El Unico Una combinaci´n de varios antipatrones. donde el objecto X deb´ funcionar en base a la salida ıa de Y. Est´ en a realidad probando otro objeto (X). donde el uso de a u variables de clase est´ticas. Esto se ha visto com´nmente en fitnesse. Tambi´n conocido como La Distancia Relativa. rompi´ndose cuando corre bajo Linux. Como desafortunado o efecto colateral. aunque debiese fallar. particularmente El Libre Alo bedr´ y El Gigante. e e 294 . Es un s´lo test unitario que contiene el conjunto entero ıo o de pruebas de toda la funcionalidad que tiene un objeto.

es que no est´ bien escrito. las reglas tienen sus excepciones. a No se tome a pecho cada uno de los patrones ni los errores. Para m´ si un test necesita comentarios. les da tiempo a ir al servicio. tomar caf´. e dejarlo corriendo y marcharse a casa al terminar el d´ ıa. El objetivo es que le a sirvan para identificar “malos olores” en su pr´ctica con TDD. ı. a 295 .Cap´ ıtulo 12 El Escabador Lento Un test que se ejecuta de una forma incre´ ıblemente lenta. Notas finales Por ultimo yo a˜adir´ como antipatr´n el hecho de ponerle comentarios a ´ n ıa o un test. Como en tantas otras ´reas. o peor. Cuando los desarrolladores lo lanzan.

Cap´ ıtulo 12 296 .

org/ 297 .´ Apendice A Integraci´n Continua (CI) o A. la IC puede practicarse sin el uso de estas. IC es mucho m´s que la utilizaci´n a o de una herramienta. cada persona integra de forma diaria. la integraci´n se lleva a cabo como un evento (cada o lunes integramos nuestro c´digo. Muchos equipos o a encuentran que este enfoque conduce a la reducci´n significativa de probleo mas de integraci´n y permite a un equipo desarrollar software cohesivo m´s o a r´pidamente” a Muchos asocian la integraci´n continua (utilizar´ IC para referirme al o e t´rmino en adelante) con el uso de herramientas como CruiseControl1 o e Hudson2 . o La IC encaja muy bien con pr´cticas como TDD dado que se centran a en disponer de una buena bater´ de pruebas y en evolucionar el c´digo ıa o realizando peque˜os cambios a cada vez.net/ http://hudson-ci. Introducci´n o ¿Qu´ es la integraci´n continua? e o En palabras de Martin Fowler.. conduciendo a m´ltiples integraciones por d´ Cada integraci´n es u ıa.). la pr´ctica de la IC elimina esta forma o a de ver la integraci´n. En algunos proyectos. o comprobada por una construcci´n autom´tica (incluyendo las pruebas) para o a detectar errores de integraci´n tan r´pido como sea posible. en realidad. ya que forma parte de nuestro trabajo diario. sin embargo..1. Aunque.sourceforge. aunque con una mayor disciplina. la metodolog´ n ıa de desarrollo no es determinante siempre y cuando se cumplan una serie de 1 2 http://cruisecontrol. entendemos la integraci´n continua como: o “Una pr´ctica del desarrollo de software donde los miembros del equipo intea gran su trabajo con frecuencia: normalmente. En otras palabras.

sin embargo. ficheros de configuraci´n. proporciona gran valor a las metodolog´ ´giles. a a Conceptos Construcci´n (Build): una construcci´n implica algo m´s que compilar. realizar an´lisis del c´digo a o o desplegar software.. a La IC es independiente del tipo de metodolog´ de gesti´n (´gil o preıa o a dictiva). entre otras cosas. librer´ de a ıas terceros.A. ya sean scripts. etc. o o a esto podr´ consistir en compilar y ejecutar una bater´ de pruebas.. a˜adimos o modificamos el c´digo necesario para rean o lizar la funcionalidad elegida (no nos olvidemos de las pruebas asociadas). Comenzamos descargando el c´digo actual del repositorio a nueso tra m´quina local. Para realizar IC tambi´n es vital disponer de un repositorio centralizado cuya localizaci´n sea e o conocida por los miembros del equipo. es que es dif´ pensar en o ıcil proyectos que no utilicen alguna herramienta de este tipo.1. A continuaci´n. La verdad. Ser´ nuestra base para realizar las a construcciones cada vez que un desarrollador suba sus cambios. Podemos tener scripts de construcciones sin tener que implementar IC. Los beneficios de hacer IC son varios. o ¿C´mo es la vida con integraci´n continua? o o Imaginemos que decidimos agregar una peque˜a funcionalidad a nuestro n software. ejecutar pruebas. En a nuestra copia local. sin embargo. lo siguiente que pensaremos es que ya estamos listos para subir e 3 http://pmd. a esta copia la llamaremos copia local o working copy. sin embargo. o Scripts de construcci´n (build script) : se trata de un conjunto de o scripts que son utilizados para compilar.sourceforge. El repositorio debe contener todo lo necesario para que nuestro proyecto pueda ser construido de forma autom´tica. o o a podr´ consistir en compilar. Empezando con IC Ya se conoce de las bondades de un SCV (Sistema de Control de Versiones) para nuestro c´digo fuente.net/ 298 . Un build puede ser entendido como o el proceso de convertir el c´digo fuente en software que funcione. por los beneficios que aporta. ayudando a tener un producto funcional en todo ıas a momento. para practicar IC son vitales. Introducci´n o Integraci´n Continua (CI) o buenas pr´cticas. desplegar. Si hemos ıa ıa tenido ´xito. usar herramientas de an´lisis ıa a de c´digo3 . testear. debemos realizar una construcci´n de manera autom´tica. se entender´n y aprea ciar´n mejor una vez que conozcamos las pr´cticas que conllevan.

M´s a a adelante entraremos en detalle. etc. cuando hablamos de construir nuestro software a partir de lo que existe en el repositorio. De esta forma. hemos acabado n a o nuestro trabajo. consiste en automatizar procesos para nuestros queridos usuarios. ahora he querido simplificar el proceso sin nuevos conceptos. Sin embargo. A. en este caso. Pr´cticas de integraci´n continua a o nuestros cambios al repositorio. en una “m´quina de integraci´n” bas´ndonos en el c´digo a o a o actual del repositorio (con nuestra nueva funcionalidad). Hudson. Si todo ha ido bien en la m´quina de integraci´n.2. Ya tenemos lo b´sico para empezar a trabajar con IC. para su tranquilidad. comentar´ que esta ultima parte en la e ´ m´quina de integraci´n podr´ ser ejecutada autom´ticamente por un servidor a o ıa a de IC como CruiseControl. podemos subir nuestros cambios al repositorio. Finalmente.1. concretamente. la integraci´n continua promueve automatizar o todo el proceso lo m´ximo posible para aumentar nuestra productividad. podr´ haber ocurrido que nos hayamos olvidado de subir un fichero y el ıa repositorio no haya sido actualizado correctamente (este problema no es extra˜o). No obstante. Entre otras cosas. sin embargo. Pero nuestro trabajo no acaba aqu´ Debemos construir una ultima vez ı. es posible que piense que es un proceso muy latoso. Nuestro proyecto en a un repositorio centralizado. Construir significa mucho m´s a que compilar. Por lo a tanto debemos bajarnos los cambios del repositorio. ´ pero. Pero esto es s´lo la o punta del iceberg. a veces nos olvidamos de que una parte de nuestras tareas podr´ ser automatizadas. debemos arreglar tan pronto como sea posible los problemas que hayamos podido ocasionar en el repositorio.2.Integraci´n Continua (CI) o A. otros desarrolladores han podido subir sus cambios mientras nosotros realiz´bamos nuestra tarea. ¿a que nos referimos?. En caso contrario. Pr´cticas de integraci´n continua a o Automatizar la construcci´n o Nuestro trabajo. al detectar que ha habido cambios en el repositorio.2. A. resolver los conflictos si los hubiera y lanzar de nuevo la construcci´n autom´tica para verificar que o a todo ha sido integrado correctamente. No se desespere. a Llegados a este punto. en parte. nuestra construcci´n podr´ descargar las ultimas fuentes del o ıa ´ 299 . toda la informaci´n necesaria en ´l para construir o e el proyecto y unas nociones sobre la manera de trabajar. las tareas neceıan sarias para obtener nuestro software a partir de ese mont´n de c´digo fuente. a continuaci´n se pasar´ a detallar una serie de pr´cticas o a a para realizar IC de forma efectiva. disponemos de una base estable en el repositorio del cual cada desarrollador partir´ para realizar su trabajo diario. o o Pero.

apache.net/ 8 http://maven. no est´ de m´s reafirmar la o a a http://svnbook. Maven8 . sin que pierda tiempo en realizar el proceso completamente. generar documentaci´n o un a o esquema de base de datos si interaccionamos con un SGBD5 .aspx 10 http://rake. Una de las etapas. El proceso de construcci´n deber´ ir tan r´pido como se pueda.apache. NAnt7 . etc. Normalmente.com/en-us/library/0k6kkbsd. podr´ compilar y ejecutar las pruebas unitarias. Los test forman parte de la construcci´n o Ya se ha hecho hincapi´.maint. de cara al trabajo de cada desarrollador. algunos desarrolladores usan un IDE para la construcci´n.com/nightly/en/svn-book.wikipedia. Las etapas que ıa llevaran m´s tiempo podr´ ser lanzadas unicamente en la m´quina a ıan ´ a de integraci´n (algunas incluso de forma paralela).sourceforge. Sin embargo. Rake10 .2. aunque sea de manera indirecta.2.red-bean. lanzar las pruebas). a Buenas pr´cticas a Divide el script de construcci´n en diferentes comandos para que cualo quiera pueda lanzar una parte de forma aislada (por ejemplo.branchmerge.. etc. Iniciar una construcci´n deber´ ser tan f´cil para cualquier desarrollador como lanzar o ıa a un unico comando desde una consola: ´ freyes@dev:/home/project$build now Existen herramientas de scripts de construcci´n libres que nos facilitan la o labor.org/ 9 http://msdn.org/wiki/Sistema de gesti %C3 %B3n de bases de datos 6 http://ant. ejecutar pruebas autom´ticas. o A.2. podr´ ıamos plantearnos dividir la construcci´n o en varias etapas.html#svn.. Nos deben facilitar la vida pero no debemos caer en la dependencia absoluta. Dedica algo de tiempo para que sea lo o m´s eficiente posible. MSBuild9 . en que los tests e forman parte de la construcci´n. Algunas de las m´s conocidas son Ant6 .A. no todo es posible en el mundo a realT M .layout http://es. muchas son usadas en diversos proyectos open-source.org/ 7 http://nant. Nadie o ıa a lanzar´ el build para comprobar que la integraci´n ha ido correctamena o te si la construcci´n es lenta.org/ 5 4 300 . est´s herramientas son de gran utilidad para nuestra productivio a dad pero es esencial poder construir nuestro software sin IDE alguno. En este caso. Sin embargo.microsoft.rubyforge. Pr´cticas de integraci´n continua a o Integraci´n Continua (CI) o trunk4 . compilar.

wikipedia. Adem´s. a o Cada diferencia es un riesgo m´s que no podremos verificar hasta la fase a de instalaci´n en producci´n. o Buenas pr´cticas a Estructura el proyecto por cada tipo de test (test/unit <-> test/integration <-> test/system) as´ podr´s ejecutar un grupo de manera ı a independiente sin muchas complicaciones u otros ficheros de configuraci´n o Escribe tambi´n pruebas para los defectos/bugs que encuentres e Realiza una comprobaci´n (assert) por test. Es muy dif´ tener una larga bater´ de ıcil ıa test que prueben todas las partes del proyecto (100 % de cobertura) o que todas estas sean perfectas. como bien dice Martin Fowler: “Pruebas imperfectas. Aunque esto no supone que debamos dejar de mejorar nuestras habilidades para desarrollar pruebas de mejor calidad. sistema. el test termina en ese punto sin dar informaci´n o de las siguientes comprobaciones. integraci´n. Tener pruebas que se ejecutan r´pidamente es lo preferible pero no siema pre podemos conseguirlo. Existen diferentes tipos de pruebas (unitarias. para ello. Es necesario automatizar la ejecuci´n de las pruebas para que formen o parte de la construcci´n.2. Adem´s de dejar claro el o a objetivo del test. Recuerda que si un assert falla.. se pierde uno de los principales beneficios de la IC. 11 http://es. Tambi´n es vital que el tiempo de ejecuci´n de o e o las pruebas sea corto (tanto en la m´quina del desarrollador. como en la a m´quina de integraci´n). Llegados a este punto. que corren frecuentemente. Uno de los beneficios de la IC es que reduce los riesgos cuando llevemos nuestro sistema al entorno de producci´n pero. son mucho mejores que pruebas perfectas que nunca se han escrito”. Pero. Pr´cticas de integraci´n continua a o importancia de estos en el proceso. es necesario que o las pruebas se ejecuten en un entorno lo m´s parecido al de producci´n.org/wiki/M %C3 %A1quina virtual 301 . si se produce una larga demora notificando a o a a las partes interesadas sobre lo que ha ocurrido en la construcci´n y los o desarrolladores se centran en otras actividades. si nuestra construcci´n se demora bastante (XP recomienda 10 minutos) podr´ o ıamos ejecutar s´lo las pruebas m´s r´pidas (como suelen ser las unitarias) cada o a a vez que el repositorio sea modificado y lanzar las restantes a intervalos. reducimos el ciclo para que los test pasen. El uso de m´quinas virtuales11 para configurar o o a estos entornos es una opci´n bastante acertada.) y todas son importantes para nuestro software pero el o tiempo de ejecuci´n suele ser m´s largo en unas que en otras (como podr´ o a ıa ser el caso de las pruebas de sistemas)..Integraci´n Continua (CI) o A.

Otro de los beneficios es que es m´s f´cil detectar o a a errores. de manera que no interfiramos en el trabajo del resto del equipo. el desarrollador debe realizar una construcci´n en su entorno local. Para ello. puesto que en ese tiempo no habremos podido escribir mucho c´digo. o a La operaci´n de forma manual es muy simple. o arreglarlo lo m´s r´pidamente posible. Es responsabilidad de quien haya roto la construcci´n del repositorio.2. no deber´ a a ıamos usar trucos para llevar a cabo nuestro objetivo. lo que proporciona a n una sensaci´n de avance. quiz´s debamos plantearnos revertir los cambios en el respositorio a y solucionarlo tranquilamente en nuestro entorno local.4. a Tampoco deber´ ıamos partir de c´digo del repositorio cuya construcci´n o o ha fallado.2. Esto puede ser desde c´digo que no compila hasta c´digo que no o o pasa las pruebas.3. o s´lo habr´ unos cu´ntos lugares donde el problema estar´ escondido. Pr´cticas de integraci´n continua a o Integraci´n Continua (CI) o A. cada vez que alguien sube sus cambios al 12 http://www. Puede ser que m´s adelante encono a tremos problemas en la m´quina de integraci´n pero.html 302 . a o a produce un repositorio m´s estable. A. advierte al equipo para que no haya interferencias. Esto podr´ llevarnos a duplicar esfuerzos (varios desarrolladores ıa solucionando el mismo problema) o a pensar que nuestros cambios han sido lo que han provocado el problema.com/blog/archives/000818. reducimos las suposiciones sobre el entorno y la configuraci´n y ayudamos a prevenir los problemas del tipo “¡En o mi m´quina funciona!”12 . Para prevenir que ocurra esto. o a a a En IC hay una m´xima que debe ser cumplida por todo el equipo cuando a se plantea subir cambios al repositorio: “Nunca se debe subir c´digo que no o funciona”. el desarrollador que sube o los cambios al repositorio. se dirige a la m´quina de integraci´n y all´ realiza la construcci´n a partir del a o ı o repositorio.A. Subir los cambios de manera frecuente Es una de las pr´cticas principales de la IC (impl´ a ıcita en la definici´n). Usando un servidor de IC.codinghorror. lo que supone una gran p´rdida de tiempo e hasta que lo descubramos. Si el problema no es f´cil de resolver y puede llevar a tiempo. seguir esta m´xima. Construir en una m´quina de integraci´n a o Cuando utilizamos una m´quina de integraci´n donde realizamos las consa o trucciones a partir del repositorio. Uno o de los beneficios de realizar cambios frecuentemente en el repositorio es que nos fuerza a dividir nuestra tarea en otras m´s peque˜as. como eliminar o comentar el c´digo de o las pruebas que no pasan. Hay 2 maneras de realizar esta tarea: ejecutar la a construcci´n autom´tica de forma manual o utilizar un servidor de IC.2.

sem´foros15 .org/pages/viewpage. etc. existen buenos argumentos IC. o Tambi´n proporciona gran valor que cada persona.com/Blog/Continuous-Integration-is-an-Attitude. ama a bient orb16 .2. Entre los mecanismos menos a ostentosos se encuentra el env´ de emails o sms al m´vil de los interesados. e 13 en contra del uso de servidores de sin embargo. etc) a a o y de los problemas concretos que ha producido cada cambio en el sistema. etc).org/display/HUDSON/Nabaztag+Plugin 303 . notificando del o a resultado del proceso (por e-mail. independientemente e de su cargo o labor en el proyecto. A. Las buenas ideas diferencian nuestro software y las cr´ ıticas constructivas nos hacen mejorar. ¡pru´balo t´ mismo! ı a e u 13 14 http://jamesshore. emisi´n de sonidos en un altavoz con el resultado de la ultima o ´ construcci´n. Los servidores de IC.cgi/Monitor/Devices/BubbleBubbleBu 15 http://wiki.hudson-ci. desde el lugar donde se o ıa encuentre. Aqu´ es donde marca la diferencia ı el uso de un servidor de integraci´n continua con respecto a realizar la inteo graci´n de forma manual.5.java.pragmaticautomation. ıo o los m´s frikies pueden hacer uso de lamparas de lava14 . pueda obtener el ultimo ejecutable y ser ´ capaz de arrancarlo sin muchos suplicios. A priori puede parecer que el enfoque manual es una p´rdida de tiempo. Nabaztag17 .html 17 http://wiki. los servidores de IC son capaces de comunicar por diferentes mea canismos lo que est´ ocurriendo en el servidor. cualquiera puede ver el estado del repositorio ojeando la m´quina a de integraci´n. entre los beneficios aportados se encuentran: Aumento del feedback a lo largo del proyecto entre todos los integrantes implicados en el mismo.com/cgi-bin/pragauto.hudson-ci. pueda ver el estado del proyecto.Integraci´n Continua (CI) o A. Pr´cticas de integraci´n continua a o repositorio. o aportan bastante informaci´n respecto a la evoluci´n del proyecto (cu´ntos o o a builds se han fallado. etc). Pero lo correcto ser´ que cualquiera. gr´ficos sobre la evoluci´n. La mayor´ de los servidores de IC proporcionan plugins o extensiones que ıa enriquecen la informaci´n de nuestros proyectos (cobertura de las pruebas. Menor n´mero de interrupciones para el equipo de desarrollo cada vez u que alguien (ajeno al desarrollo diario) nos pida ver nuestro software funcionando. se realiza la construcci´n de manera autom´tica. Ah´ est´. o generaci´n autom´tica de documentaci´n a partir del c´digo fuente.2. jabber. cu´ntos han pasado. Todo el mundo puede ver lo que est´ pasando a Uno de los beneficios de la integraci´n continua es que aporta claridad en o el proyecto. Aunque a muchos les pueda parecer arriesgado este enfoque.net/blog/kohsuke/archive/2006/11/diyorb my own e. como por ejemplo Hudson.action?pageId=38633731 16 http://weblogs.html http://www. o a o o Adem´s.

¿de qu´ tipo de riesgos estamos hablando?. donde se producen peque˜as entregas al cliente de man nera incremental a lo largo del proyecto. Automatizar el despliegue Todas las pr´cticas anteriores reducen el riesgo al llegar a la fase de a despliegue (automatizar la construcci´n.A.2. En subversion son conocidos como TAGs. sin ser el equipo de desarrollo.3. u o Tener software que funciona en cualquier momento es un beneficio enorme. etc. Si el despliegue no ha sido correcto. sin un despliegue exitoso. etc).6. Crystal. Algunas personas afirman que. a˜ade capacidades para realizar n una vuelta atr´s (roll back) y dejar la ultima versi´n que funcion´. Todas las pruebas deben ejecutarse y pasar correctamente para realizar el despliegue. Lo toman como algo eventual. estaremos aportando un valor inmenso a nuestros usuarios. automatizar las pruebas. Si somos capaces a o de desplegar software en cualquier momento. Esto se suele traducir casi siempre en entregas intensas y 304 . lo ha visto funcionando. El ´xito del despliegue pasa por eliminar e el mayor n´mero de errores mediante la automatizaci´n. a ´ o o A. pues nadie. IC para reducir riesgos Integraci´n Continua (CI) o Incremento del conocimiento sobre lo que hace nuestro software. a expensas e de angustiosos y largos documentos de ´nalisis a A. Pero.3. el software realmente no existe. tener un o entorno lo m´s parecido al entorno de producci´n. qu´ fune cionalidades cubre en cada momento y qu´ carencias tiene. IC para reducir riesgos La IC ayuda a identificar y reducir los riesgos existentes a lo largo del desarrollo de un proyecto. e Podemos apreciar que la integraci´n continua intenta minimizar el riesgo en o los siguientes escenarios Despliegues demasiado largos y costosos No es dif´ encontrar proyectos que realizan la construcci´n del software ıcil o en determinados entornos s´lo cuando faltan unos pocos d´ para la entrega o ıas del mismo. Buenas pr´cticas a Identifica cada despliegue en el repositorio. por no decir totalmente necesario. una etapa que se realiza exclusivamente al final. si usamos metodolog´ ´giles como ıas a Scrum.

sobre todo. suele ser de gran utilidad para identificar estos o problemas y solventarlos antes de que nuestro proyecto se convierta en un enorme pantano.4.wikipedia. etc. y no en los equipos de los desarrolladores. Disponer de herramientas que nos a ayuden a analizar el c´digo.Integraci´n Continua (CI) o A. Conclusi´n o dolorosas para los miembros del equipo y. pudiendo generar informes sobre asuntos tales o como c´digo duplicado.org/wiki/Code smell 305 . Falta de visibilidad del proyecto ¿Cu´ntas pruebas pasan con ´xito?. 18 http://en. no la ver´ como tal. esta afirmaci´n puede parecer gratuita para muchas personas pero o el programador que haya regresado a modificar o leer esa parte del c´digo o que no huele bien18 .4. en una fase dif´ de ıcil estimar. Algunos de los escenarios que hacen dura esta fase: No se ha podido verificar que toda la informaci´n para la construcci´n o o se encuentra disponible en el repositorio. La famosa frase “En mi equipo funciona” suele ser reflejo de este tipo de problemas Ausencia de una construcci´n autom´tica o a Descubrir defectos demasiado tarde Definir pruebas para nuestro software garantiza algo de calidad pero tan importante como ’hacerlas’ es ’ejecutarlas’ a diario con el fin de que saquen a relucir los defectos que podamos haber introducido. ¿Seguimos los est´ndares planteados a e a para el proyecto? ¿Cu´ndo se construyo el ultimo ejecutable que funcionaba a ´ correctamente? Este tipo de preguntas suelen ser dif´ ıciles de responder si son hechas en cualquier momento sin previo aviso. Reduce riesgos al integrar diariamente y genera software que puede ser desplegado en cualquier momento y lugar. Conclusi´n o La integraci´n continua es una pr´ctica que ayuda a los equipos a mejoo a rar la calidad en el ciclo de desarrollo. Baja calidad del software Desarrollar c´digo de baja calidad suele producir un elevado coste en el o tiempo. A.

aporta mayor visibilidad sobre el estado del proyecto. Dado los benea ficios que proporciona.A. 306 . vale la pena sacrificar algo de tiempo para que forme parte de nuestra caja de herramientas. Conclusi´n o Integraci´n Continua (CI) o Adem´s.4.

[11] Robert C. Agile Software Development: Principles. Prentice Hall. InfoQ. Addison-Wesley. Martin. Presuman. 1999. ıa a o Roger S. [10] Robert C. Refactoring: Improving the Design of Existing Code. Ingenier´ del Software: Un enfoque pr´ctico (3ra Edici´n). Test Driven Development: By Example. Addison-Wesley Professional.NET Development with FitNesse. Addison-Wesley. Martin. [3] Kent Beck. 307 . [5] Mike Cohn. and Practices. 2007. Bridging the Communication Gap: Specification by Example and Agile Acceptance Testing. Manning. 1956. Addison-Wesley Professional. [6] Michael C. 2002. [9] Lasse Koskela. Neuri Limited. [7] Martin Fowler. Test Driven . 2004. Test Driven. 2008. Prentice Hall. Feathers. 2006. xUnit Test Patterns. [12] Gerard Meszaros. [8] Henrik Kniberg. 2002. Working Effectively with Legacy Code. [4] Bennington. User Stories Applied. SCRUM y XP desde las Trincheras. Patterns. 2004. Neuri Limited. [2] Gojko Adzic.Bibliograf´ ıa [1] Gojko Adzic. Clean Code: A Handbook of Agile Software Craftsmanship. Prentice Hall. 2007. 2008. 2009.

com. [15] Juan Palacio. 2004. 2009. [14] Steve Freeman Nat Pryce. Starbook Editorial. 2007. Addison-Wesley Professional. Inform´tica Profesional. Manning Publications. JUnit Recipes: Practical Methods for Programmer Testing. Flexibilidad con SCRUM. Lulu. Guided by Tests. [16] J.BIBLIOGRAF´ IA BIBLIOGRAF´ IA [13] Roberto Canales Mora. 2009. 308 . Las reglas no escritas a para triunfar en la empresa. Rainsberg. B. Growing Object-Oriented Software.

You're Reading a Free Preview

Download
scribd
/*********** DO NOT ALTER ANYTHING BELOW THIS LINE ! ************/ var s_code=s.t();if(s_code)document.write(s_code)//-->