Este documento presenta los objetivos y temario de un curso sobre Test Driven Development (TDD). El curso enseñará la metodología TDD a través de ejemplos y ejercicios prácticos de desarrollo de una aplicación, y cubrirá temas como introducción a TDD, ciclo de desarrollo TDD, tipos de tests, refactorización, integración continua y más. El curso está diseñado para volver a los desarrolladores "adictos a los tests" y extender la "epidemia TDD".
1. TEST DRIVEN DEVELOPMENT IN ACTION
ACCEPTANCE TEST DRIVEN DEVELOPMENT
CONTINUOUS INTEGRATION
Pedro Ballesteros Herranz <nitroduna@gmail.com>
@pmbah
2. OBJETIVOS DEL CURSO
“To turn you into test-infected developers”1
“To spread the TDD epidemic”
“Test-Driven Development is addictive”
1 Referencias
“tests-infected”: Termino ampliamente extendido para nombrar desarrolladores que hacen uso de tdd o unit testing (búscalo en google).
http://junit.sourceforge.net/doc/testinfected/testing.htm
JUnit in Action (Manning Publications) http://www.manning.com/tahchiev/
“The statistics show that people get easily “infected” with the unit-testing philosophy. Once you get accustomed to writing tests and see how good the feeling of
someone protecting you from possible mistakes is, you will wonder how was possible to live without unit-testing before.”
“Developers who adopt these techniques self-identify as "test-infected" meaning they are quite literally addicted to writing unit tests for their code
which is something counter-intuitive to developers who have never experienced it.”
3. OBJETIVOS DEL CURSO
Aprender la metodología TDD experimentándola de forma practica
Entender que implican los distintos tipos de tests
Diferencias, cuando usar cada tipo, desarrolladores y testers.
¿Qué tests se pueden (o conviene) automatizar? ¿Qué esfuerzo suponen?
¿Qué tests se deben o no se deben utilizar desarrollando con TDD?
¿Sólo consiste en “hacer primero los tests”? NO…ES MUCHO MAS
Test-First Development vs Test-Driven Development
TDD tiene sus limites: como superarlos y cuanto cuesta.
Ventajas, beneficios, limites, inconvenientes, vulnerabilidades,
mitos, malentendidos, etc.
Practicas, Ejercicios, Ejemplos, Practicas, Ejercicios, Ejemplos, …
4. TEMARIO I
SOBRE LOS EJERCICIOS Y EJEMPLOS
INTRODUCCIÓN
INTRODUCCIÓN A EXTREME PROGRAMMING
CARACTERIZANDO TDD DESDE XP
INTRODUCCIÓN TDD Y ATDD
TEST UNITARIOS AUTOMATIZADOS
INTRODUCCIÓN CICLO DE DESARROLLO TDD
TEST DRIVEN DEVELOPMENT EXPLAINED
TIPOS DE TESTS
CICLO DE DESARROLLO DE TDD
PROPIEDADES FIRST DE LOS TESTS
PROPIEDADES SOLID PARA LA REFACTORIZACIÓN
5. TEMARIO II
UNIT TESTING FRAMEWORKS
DUMMY / FAKE / STUB / MOCK / SPY
VALIDACIÓN POR ESTADO Y POR INTERACCIÓN
TDD APLICADO A ENTORNOS ESPECÍFICOS
ANTI-PATRONES Y MALAS PRÁCTICAS
ACCEPTANCE TEST DRIVEN DEVELOPMENT EXPLAINED
INTEGRACIÓN CONTINUA
TDD – OBJETIVOS, BENEFICIOS Y CARACTERÍSTICAS
TDD – VULNERABILIDADES Y PELIGROS
TDD – MITOS Y MALENTENDIDOS
COMO EMPEZAR CON TDD
6. SOBRE LOS EJERCICIOS Y EJEMPLOS
La impartición del curso está dirigida por ejemplos y prácticas de ejercicios
TDD, que representan el desarrollo de una aplicación empresarial completa,
pasando por las capas de presentación, negocio y persistencia (aunque
sigue tratándose de un ejemplo simplificado).
Algunas partes de la teoría en lugar de estar reflejadas en esta presentación
están exclusivamente incluidas en los ejercicios.
Los ejercicios se han desarrollado enteramente aplicando la
metodología TDD.
Los ejercicios están divididos a en secciones que representan
avances de un solo paso del ciclo TDD (cada sección es una copia
del paso anterior con un incremento adicional).
Algunas veces el avance de la solución representa la aplicación
iterativa de varias iteraciones del ciclo TDD.
7. SOBRE LOS EJERCICIOS Y EJEMPLOS
MATERIAL DISPONIBLE
PRESENTACIÓN ACTUAL
Documentación y referencia de la teoría del curso. Muchas diapositivas
tienen notas asociadas a modo de apuntes de la teoría.
ENUNCIADO DE LA APLICACIÓN a desarrollar
Enunciado detallado de la aplicación sobre la que girarán los ejercicios,
con apuntes de la teoría a modo de recordatorio.
EJERCICIOS Y EJEMPLOS
Disponibles en el repositorio de github:
https://github.com/theprogrammingchronicles/tpc-tdd-exercises
DOCUMENTACIÓN DE EJERCICIOS Y EJEMPLOS
Todos los ejercicios cuentan con documentación detallada de la
problemática a resolver, teoría a revisar y pasos a realizar.
El código fuente cuenta con JavaDocs descriptivos de la problemática
propuesta en cada ejercicio y su resolución.
Documentación de ejercicios en:
http://code.theprogrammingchronicles.com/tpc-tdd-exercises/
8. SOBRE LOS EJERCICIOS Y EJEMPLOS
TECNOLOGÍAS Y LENGUAJES
Lamentablemente no es posible la enseñanza práctica de TDD sin el apoyo
sobre un lenguaje de programación o tecnología concreta.
La parte práctica de este curso se ha desarrollado con Java, pero se ha
evitado la dependencia de frameworks muy específicos, la extrapolación de
la práctica a otros lenguajes de programación debería ser sencilla(*).
MUY IMPORTANTE
Este curso no pretende ser un compendio de recomendaciones sobre el
uso de herramientas Javas aplicadas a TDD.
La teoría y práctica es completamente independiente de:
Si se selecciona ant o maven como herramienta de build.
Si se selecciona Netbean, Eclipse, IntelliJ IDEA o UltraEdit como IDE.
Si se selecciona Hudson o Luntbuild como herramienta de CI.
Etc.
* La adaptación de este curso a otros lenguajes tan solo requeriría la migración de los ejercicios y ejemplos.
10. INTRODUCCIÓN
TDD nació como una de las prácticas de eXtreme Programming:
Actualmente ya es una metodología de desarrollo independiente.
Se está impartiendo como una metodología aislada de XP.
Ya se encuentra incluida como parte de otras metodologías más
genéricas:
Rational Unified Process (RUP)
Agile Unified Process (AUP)
Open Unified Procress (Open UP)
SCRUM
11. INTRODUCCIÓN
“Test-Driven Development es una Metodología de Desarrollo”
“NO es una metodología de testing”
“En primer lugar es una metodología de diseño y desarrollo, que
como efecto lateral acaba proporcionando un código
completamente(*) probado por Unit Tests automatizados”
“La metodología proporciona un desarrollo del software a través de la
aplicación iterativa, continua e incremental de tres pasos, de los cuales el
primero es siempre la codificación de un small scaled test”
(*) ¿Quiere eso decir que el sistema está probado al 100%?: NO, TDD no es un sustito de las técnicas de testing.
TDD no elimina la necesidad de otros tipos de tests. No elimina el trabajo de un grupo de testers o QA.
TDD si proporciona una barrera importante contra defectos.
El número de bugs detectados en tests de más alto nivel disminuye, el trabajo del grupo de QA disminuye.
13. INTRODUCCIÓN A EXTREME PROGRAMMING
REFERENCIAS
http://www.extremeprogramming.org/
Guía rápida de eXtreme Programming con diagramas de flujo navegables.
http://www.xprogramming.com/
Información práctica de eXtreme Programming y TDD. Software,
herramientas, librerías para multitud de lenguajes (xUnit, mocks,
integración,…), entornos de desarrollo, etc.
Extreme Programming Explained: Embrace Change (2nd Ed), 2004,
Kent Beck, Addison-Wesley Professional.
Libro escrito por el creador de eXtreme Programming.
14. INTRODUCCIÓN A EXTREME PROGRAMING
HISTORIA
La metodología fue creada y utilizada inicialmente por Kent Beck en
1996 (con ayuda de Ward Cunninghan y Ron Jeffires) en el proyecto
de nóminas C3 (Chrysler Comprehensive Compensation) de Daimler-
Chrysler.
Tras terminar el proyecto, Beck escribió el libro Extreme
Programming Explained, con el cual ganó el premio “Software
Development Jolt Product Excelence Award”.
Desde entonces se ha ido refinando la metodología, y han ido
apareciendo variantes (Ej. Remote eXtreme Programming).
15. INTRODUCCIÓN A EXTREME PROGRAMMING
MOTIVACIONES
1968, Crisis del Software (conferencia de la OTAN sobre software).
Los proyectos no terminaban en plazo.
Los proyectos no se ajustaban al presupuesto inicial.
Baja calidad del software generado.
Software que no cumplía las especificaciones.
Código inmantenible que dificultaba la gestión y evolución del proyecto.
Nace la “Ingeniería del Software”.
Construir software aplicando procesos de ingeniería.
Metodologías:
Trabajos previos de construcción “sobre papel”: Análisis, Especificación,
Arquitectura, Diseño.
Esfuerzos invertidos en definir y fijar todo por adelantado y esfuerzos
adicionales por controlar y contener el cambio.
16. INTRODUCCIÓN A EXTREME PROGRAMMING
MOTIVACIONES
El panorama del software ha cambiado mucho desde 1968.
Ni se programa con tarjetas perforadas ni los ciclos de vida son tan largos.
Los procesos de ingeniería copiados de otras disciplinas no
funcionan bien en el software.
No es sencillo tratar el software como la abstracción del mundo real.
El software es muy diferente a otras disciplinas.
Seguimos observando los mismos defectos.
Los proyectos no terminan en plazo y no se ajustan al presupuesto inicial.
El software no cumple especificaciones, es de baja calidad.
Seguimos encontrándonos con software inmantenible.
Los equipos de desarrollo acaban trabajando sin metodología.
¿Qué desarrollador cuenta con diagramas detallados de clase, de
interacción o de estado antes de ponerse a codificar cada módulo?
17. INTRODUCCIÓN A EXTREME PROGRAMMING
LO QUE NO ES
Extreme Programming NO ES “Cowboy Coding”.
• Ausencia de metodología.
• Desarrolladores completamente autónomos y sin gestión externa.
• Hago lo que quiero y como quiero.
Extreme Programming NO ES “Death March Development”
• Trabajo a marchas forzadas.
• Estimaciones ajustadas y no realistas.
• Falta de documentación y conocimiento global del proyecto.
• Horas extras, fines de semana, vamos a meter más gente, ...
“Con Extreme Programming se trabaja a un ritmo sostenible”
(sustainable pace, las famosas 8h)
18. INTRODUCCIÓN A EXTREME PROGRAMMING
LO QUE SI ES
“Es una metodología ágil de desarrollo de software que pone más
énfasis en la adaptabilidad que en la previsibilidad”
Extreme Programming abraza el cambio,
en lugar de intentar contenerlo.
¿Y POR QUÉ EXTREME?
“Los elementos beneficiosos de las metodologías tradicionales de
ingeniería del software se llevan a niveles extremos”
19. INTRODUCCIÓN A EXTREME PROGRAMMING
OBJETIVOS DE LA METODOLOGÍA
Adaptarse a los cambios en cualquier punto de vida del proyecto.
En lugar de esfuerzos para fijar todo desde el principio y luego controlarlo.
Adoptar las mejores prácticas de desarrollo aplicándolas
dinámicamente durante el ciclo de vida del software.
Mejorar la calidad del software, la productividad y el tiempo de
respuesta a cambios de requisitos por el cliente.
“Los cambios de requisitos sobre la marcha son
un aspecto natural, inevitable e incluso deseable
del desarrollo de proyectos de software”
20. INTRODUCCIÓN A EXTREME PROGRAMMING
COMPONENTES DE LA METODOLOGÍA
Activities
Values
Principles
Practices or Rules
22. INTRODUCCIÓN A EXTREME PROGRAMMING
VALUES
Simplicity
Communication
Feedback
Respect
Courage
23. INTRODUCCIÓN A EXTREME PROGRAMMING
PRINCIPLES
Rapid Feedback
Assume Simplicity
Incremental Change
Embracing Change
Quality Work
24. INTRODUCCIÓN A EXTREME PROGRAMMING
PRACTICES OR RULES
Planning
• User Stories: User stories are written.
• Release Plan: Release planning creates the release schedule.
• Release Often: Make frequent small releases.
• Iterative: The project is divided into iterations.
• Iteration Planning: Iteration planning starts each iteration.
Managing
• Optimize Last: Give the team a dedicated open work space.
• Steady Pace: Set a sustainable pace.
• Stand-up Meeting: A stand up meeting starts each day.
• Project Velocity: The Project Velocity is measured.
• Move people around.
• Fix XP when it breaks.
25. INTRODUCCIÓN A EXTREME PROGRAMMING
PRACTICES OR RULES
Designing
• Simplicity
•System Metaphor: Choose a system metaphor.
•CRC cards: Use CRC cards for design sessions.
•Spike Solution: Create spike solutions to reduce risk.
• Nothing Early: No functionality is added early.
• Refactor: whenever and wherever possible.
Coding
•Customer On-Site: The customer is always available.
• Coding Standard: Code must be written to agreed standards.
• Test Driven Development: Code the unit test first.
•Pair Programming: All production code is pair programmed.
• Serial Integration: Only one pair integrates code at a time.
• Continuous Integration: Integrate often.
•Continuous Integration: Set up a dedicated integration computer.
• Collective Ownership: Use collective ownership.
26. INTRODUCCIÓN A EXTREME PROGRAMMING
PRACTICES OR RULES
Testing
• Unit Tests: All code must have unit tests.
• Unit Tests: All code must pass all unit tests before it can be released.
• Bug Tests: When a bug is found tests are created.
• Acceptance Tests: Acceptance tests are run often and the score is
published.
Test-Driven Development está incluido
en el área de “coding”, no de “testing”
27. CARACTERIZANDO TDD DESDE XP
CODING
“Without code, there is no work product”
SIMPLICITY
“Keep it simple, stupid” (KISS)
“You ain’t gonna needed it” (YAGNI)
TESTING
“If it's worth building, it's worth testing. If it's not worth testing,
why are you wasting your time working on it?”
DESIGNING
“Using Test Driven Development – Refactoring”
MANAGING AND PLANNING
“SCRUM”
28. CARACTERIZANDO TDD DESDE XP
CODING
El único artefacto realmente importante es el código funcionando.
Las instrucciones que el ordenador interpreta.
El código claro y simple es la mejor fuente de documentación.
Para comprender, transmitir, explicar, evaluar y discutir una solución.
Es claro y conciso, se interpreta de solo una forma.
Código colectivo (acabar con “solo lo puede tocar el que lo hizo”).
SIMPLICITY
Codificar solo lo que realmente se necesita, lo que el cliente está pidiendo.
Se programa para el “ahora” y nunca para el “futuro” (*)
No se pierde tiempo anticipando el diseño de grandes frameworks o librerías.
Nunca se escribe una línea de código pensando en el “por si acaso luego se
necesita” (al final no se necesitará).
Test-Driven Development será la herramienta de diseño.
El framework, las librerías y el código reutilizable aparecerán de forma orgánica.
Justo cuando se requieren y con un diseño modular y flexible al cambio.
29. CARACTERIZANDO TDD DESDE XP
TESTING
Tests unitarios utilizados para crear arquitectura, diseñar y codificar.
Tests de sistema o integración para aumentar y mantener la calidad.
Extreme: Si un poco de testing elimina defectos, mucho testing eliminará mas.
Los defectos, bugs, o errores son parte inherente del desarrollo de software.
Tantos tests como se puedan pensar, tantos como puedan romper el código, si
todos los tests se pasan el desarrollo se ha terminado.
UNIT TESTS: DOCUMENTACIÓN
Una librería con documentación se entiende bien, pero si incluye ejemplos
se entiende mucho mejor:
Los tests unitarios son ejemplos de cómo se usa nuestro código.
No sustituyen toda la documentación pero son una parte importante.
ACCEPTANCE TESTS: Especificación de requisitos ejecutable
Verificar que los desarrolladores han entendido los requisitos tal y como los
quiere el cliente.
Ayudar al cliente a entenderse a si mismo.
Controlar el estado de avance del sistema.
30. CARACTERIZANDO TDD DESDE XP
DESIGNING
Diseños simples, claros, modulares y desacoplados.
Los módulos se mantienen inalterados si se modifican las dependencias.
Se diseña programando, mediante el refactoring usado en TDD.
TDD es el motor de diseño: simple, claro, modular, desacoplado, flexible a
cambios y con mínimos defectos.
PLANNING & MANAGING
SCRUM: Ya estamos aplicando todos los principios XP de gestión y
planificación de los proyectos.
31. YAGNI: “YOU AIN’T GONNA NEEDED IT”
FUNDAMENTAL EN TDD (Y XP)
Codificar para el “ahora” y nunca para el “futuro”, aunque parezca
contraproducente, es clave tanto en Extreme Programming como en
Test Driven Development.(*)
Y a su vez Test-Driven Development es clave para poder programar el
ahora y evolucionar hacia el futuro sin dificultades.
La programación TDD permite evolucionar el producto “sin dolor”,
hacia lo que quiere el cliente y hacia arquitecturas robustas, flexibles
y de calidad (refactorización).
“Evitar la anticipación NO SIGNIFICA crear spaghetti code(*)”·
Ahora tenemos una metodología que nos permite obtener buenos
diseños mediante la codificación directa. Diseñamos a través de una
codificación que esta guiada y protegida por los tests.
32. TEST DRIVEN DEVELOPMENT
Acceptance Test Driven
Development
Test-Driven Development
=
Test-First Development
+
Refactoring
PRIMER VISTAZO GENERAL
33. TEST AFTER DEVELOPMENT
TRADICIONALMENTE SE HAN CODIFICADO LOS TESTS
DESPUÉS DE LA FUNCIONALIDAD
¿En serio tenemos esa tradición?
¿Cuántos habéis trabajado alguna vez en un proyecto en el que se
programen tests?
¿Cuántos participáis en algún proyecto en el que se programen
activamente los tests?
¿Qué tipos de tests y con que resultados?
34. AUTOMATED UNIT TESTS
LOS TESTS SE TIENEN PROGRAMAR
¡¿QUEEE?!¿PROGRAMAR TESTS?
¿NO QUERÍAMOS SER AGILES?
¡TARDAREMOS EL DOBLE!
35. Una tarea terminada en 1 semana
Hemos preparado la
bomba de relojería
Nada Funciona Realmente
3 semanas corrigiendo errores
¿Seguro?
¿O sólo hemos creado la falsa sensación
de la tarea se ha terminado?
¿Qué hemos conseguido?
Decir: ¡Ya está! ¡Y en una semana!
“¿Hemos tardado realmente una semanas?”
“¿Habríamos tardado más codificando pruebas unitarias?”
AUTOMATED UNIT TESTS
UN ESCENARIO HABITUAL
“NO programar tests SI añade trabajo extra”
“Y usando TDD obtendremos los tests unitarios de forma casi
orgánica, a la vez que dirigen el desarrollo de la lógica funcional”
36. AUTOMATED UNIT TESTS
YA HACEMOS PRUEBAS UNITARIAS
Actualmente todo desarrollador ya realiza pruebas unitarias(*)
¿Qué tipos de pruebas unitarias estamos haciendo?
PRUEBAS UNITARIAS
MANUALES Y VISUALES
(*) O esperamos que al menos esto sea cierto.
37. VISUAL UNIT TESTS
DEPURACIÓN LÍNEA A LÍNEA
REQUIEREN ESFUERZO Y TIEMPO ADICIONAL
¿Hemos ejercitado cada línea de código con el depurador antes de decir que está terminado?
¿Seguro?
¿Cómo podría estarlo? - depurando otra vez.
38. VISUAL UNIT TESTS
VERIFICACIÓN VISUAL DE TRAZAS
REQUIEREN ESFUERZO Y TIEMPO ADICIONAL
¿Aseguran las trazas que he ejercitado todo el código?
¿He revisado todas las trazas sin perder ninguna antes de decir que está terminado?
¿Seguro? ¿Cómo podría estarlo? – Traceando otra vez
39. VISUAL UNIT TESTS
ASSERTS EN EL CÓDIGO FUNCIONAL
REQUIEREN ESFUERZO Y TIEMPO ADICIONAL
Orientadas al establecimiento de pre-condiciones y pos-condiciones en depuración.
No orientadas al Unit Testing.
Se deben activar antes de ejecutar la aplicación.
Detienen la ejecución del sistema ante el primer fallo.
40. VISUAL UNIT TESTS
REQUIEREN ESFUERZO Y TIEMPO ADICIONAL
¿No sería mejor dedicar este mismo tiempo a su automatización?
No obstante veremos que usando TDD ese tiempo se optimiza y se
funde de forma natural con la codificación de funcionalidad, pero:
¿Estamos seguros que todos hacen pruebas visuales y que se hacen bien?
Las programación de pruebas unitarias elimina esa incertidumbre.
Repetir las pruebas unitarias programadas es hacer un sólo clic.
Repetir las pruebas visuales es volver a pasar todo el proceso manual que supone.
Los tests visuales son propensos a fallos y descuidos.
El ojo tiene que localizar los errores.
Mantenimiento: el resto de desarrolladores no sabrán qué probar, ni cómo.
Ausencia de pruebas de regresión: al modificar una funcionalidad ¿seguirá
funcionando el resto?
41. AUTOMATED UNIT TESTS
¿SE ESTÁN USANDO?
En el panorama Java Open Source prácticamente todos los
proyectos tienen Unit Tests completos.
Entiendo que en muchas otras tecnologías se da el mismo caso.
Algunos ejemplos: (*)
Tomcat
Glassfish (también usan integración continua con Hudson)
Spring Framework
Funambol SyncML Server
(*) Desconozco si usan TDD. Todos estos proyectos tienen código de tests. Los desarrolladores están obligados a incluirlos.
(*) Antes de seguir: Exercise 1 – My First Unit Test.
42. TEST DRIVEN DEVELOPMENT
BUENO, YA BASTA DE
BONDADES Y PROMESAS
VEAMOS TEST DRIVEN DEVELOPMENT EN ACCIÓN
43. TEST DRIVEN DEVELOPMENT
REFERENCIAS
http://www.agiledata.org/essays/tdd.html
Buena introducción de TDD.
Clean Code: A Handbook of Agile Software Craftsmanship, 2008, Robert C. Martin
Imprescindible
The Clean Coder: A Code of Conduct for Professional Programmers, 2011, Robert C. Martin
Diseño Ágil con TDD, 2010, Carlos Blé Jurado
Test Driven Development: By Example, 2003, Kent Beck
Del creador de Extreme Programming y Test Driven Development.
Test-Driven Development: A Practical Guide, 2003, David Astels
http://www.xprogramming.com/software
Software, herramientas, librerías para multitud de lenguajes (xUnit, mocks, integración,…), entornos de
desarrollo, etc.
44. TEST DRIVEN DEVELOPMENT
PRIMER VISTAZO AL CICLO ITERATIVO DE TDD
One Small Scaled Test
Cada iteración no más de diez
modificaciones en el código.
Diez líneas de código nuevas.
Diez correcciones en código existente.
Diez modificaciones para refactorizar.
…
45. TEST DRIVEN DEVELOPMENT
PRIMER VISTAZO AL CICLO ITERATIVO DE TDD
CODIFICAR UN TEST
NO se codifican todos los posibles tests por adelantado.
Se codifica un solo test de una funcionalidad (small scaled test)
Ej: Si un solo método requiere N tests, se itera de forma completa el ciclo TDD
uno por uno.
Son ejemplos de uso o clientes del código funcional.
Cada iteración completa debe suponer un incremento a pequeña escala, de
unas diez modificaciones (líneas nuevas, actualización de la existentes, etc.).
¿COMO SE CODIFICA EL TEST?
Debe cumplir las propiedades F.I.R.S.T
Si tardo dos minutos en una iteración (algo normal), habré ejecutado los tests
240 veces en un solo día.
Small Scaled Test: Test Unitario Aislado o Test de Integración de Lógica.
Obtener los tests: ¿Qué probaría si tengo que depurar?.
Obtener los tests: Aceptance Test Driven Development (el interruptor que
arranca el motor TDD).
(*) Exercise 1.4 – Test Code Coverage
46. TEST DRIVEN DEVELOPMENT
PRIMER VISTAZO AL CICLO ITERATIVO DE TDD
EJECUTAR EL TEST
MUY IMPORTANTE: Se debe ejecutar antes de codificar la funcionalidad.
Probamos que realmente falla. Si no falla revisamos si el test es correcto.
No saltarse nunca este paso: que se tiende a olvidar.
Aunque en la primera iteración el código ni siquiera compilará, en las
siguientes iteraciones no será así.
Seguimos refinando código que ya funciona.
Se ha codificado la funcionalidad que arregla el test anterior.
Implementamos el siguiente test este código que ya funciona y comprobamos
que el test falla para asegurar que se ha implementado bien.
CODIFICACIÓN Y EJECUCIÓN (SUBITERACIONES)
Se codifica la funcionalidad hasta que el test funciona.
“If the light is green, the code is clean” *JUnit slogan.
47. TEST DRIVEN DEVELOPMENT
PRIMER VISTAZO AL CICLO ITERATIVO DE TDD
CODIFICANDO LA FUNCIONALIDAD
MUY IMPORTANTE: Se codifican única y exclusivamente las líneas que
hacen pasar el test (al desarrollador le cuesta acostumbrarse).
No debemos codificar algo por si acaso luego se usa (“nunca se usará”).
No debemos entretenernos en diseñar y codificar un framework inicial (“al
final habrá que cambiarlo”).
Arquitecturas, diseños, frameworks, librerías: aparecerán de forma natural.
“The only thing that really matters in software development is working code”
You ain’t gonna needed it” (YAGNI)
“Keep it simple, stupid” (KISS)
“If it's worth building, it's worth testing. If it's not worth testing,
why are you wasting your time working on it?”
48. TEST DRIVEN DEVELOPMENT
CODIFICACIÓN Y REFACTORIZACIÓN (SUBITERACIONES)
Refactorizar comprobando que no se rompe el test que ya funcionaba.
FUNDAMENTAL: No se deja la factorización para el futuro, para momentos
de baja carga de trabajo (“esos momentos no existen”).
Seguramente es el paso más importante de TDD
Refactorización después de cada pequeña iteración (constantemente).
La refactorización se aplica principalmente para:
Eliminar código repetido: No hay mayor aberración que la codificación “Copy &
Paste”, que multiplica los esfuerzos de mantenimiento.
Extraer código reutilizable (XP: fijar estándares(*) en el grupo de desarrollo)
Refactorizamos guiándonos por los principios S.O.L.I.D.
SIN REFACTORIZACIÓN NO HAY METODOLOGÍA
SIN REFACTORIZACIÓN NO EXISTE TEST DRIVEN DEVELOPMENT
(sólo sería una forma de Test First Development)
(*) Estandarizar la reutilización: ver notas diapositiva
49. ACCEPTANCE TEST DRIVEN DEVELOPMENT
PRIMER VISTAZO
“Se podría decir que ATDD es una metodología de especificación de
requisitos antes que una metodología de testing”
El cliente averigua como quiere realmente su sistema.
Se hace consciente de la casuística que tienen los casos de uso.
Los desarrolladores entienden lo que quiere el cliente.
El cliente ha visto toda la casuística y el desarrollador sabe como
quiere que se resuelva cada variante.
Se codifican: Especificación de Requesitos Ejecutable.
50. ACCEPTANCE TEST DRIVEN DEVELOPMENT
PRIMER VISTAZO
El cliente tiene feedback del avance del desarrollo mediante las
pruebas de aceptación.
También llamadas “Pruebas de Cliente”: Asusta el compromiso de
aceptar el producto sin condiciones si se pasan los tests.
La especificación de requisitos ya no se escribe como un redacción
o “novela” de la funcionalidad.
User Stories (criterios de aceptación) descritos con ejemplos (tests)
El User Story equivale al título de un caso de uso tradicional.
El User Story se describe con un conjunto de ejemplos concretos y directos.
Se debe utilizar el vocabulario del negocio, y no de la implementación.
Se obtienen mediante colaboración del Analista del Negocio y el Product
Owner (o cliente), y si es posible con la participación de los desarrolladores.
Ejercicio 2: Starting Test Driven Development
VOLVEREMOS CON ATTD MAS ADELANTE
52. TEST DRIVEN DEVELOPMENT
INTRODUCCIÓN
Test Driven Development es principalmente una metodología de
desarrollo de software
La misión principal de TDD es dirigir la arquitectura, el diseño y la
codificación del software.
El efecto colateral es que se termina obteniendo una batería
completa de tests unitarios* y por tanto un software con pocos fallos.
TDD no remplaza métodos tradicionales de testing, sigue siendo
necesario el testing en niveles superiores (pero con menos bugs).
* Veremos que los tests unitarios no son suficientes para validar un sistema.
53. TEST DRIVEN DEVELOPMENT
INTRODUCCIÓN
“The act of writing a unit test is more an act of design than of verification. It is
also more an act of documentation than of verification. The act of writing a unit
test closes a remarkable number of feedback loops, the least of which is the one
pertaining to verification of function”
Robert C. Martin*
* Extreme Programming in Practice, Agile Software Development, y muchos libros más.
54. TEST DRIVEN DEVELOPMENT
ESCALABILIDAD
Se oye decir que las metodologías Agile sólo son válidas para pequeños proyectos
de un puñado de personas durante unos pocos meses, que no funcionan en
proyectos “reales” y más largos.
SENCILLAMENTE… NO ES CIERTO
Kent Beck, 2003
Un sistema Smalltalk, usando TDD, en un proyecto de 4 años, 40 personas/año.
Resultando en 250.000 líneas de código funcional y 250.000 líneas de código de
test. Se consiguieron 4000 tests ejecutándose en 20 minutos(*), con el sistema
completo corriendo varias veces al día.
Y existen testimonios similares de otros grandes proyectos.
“It is clear that TDD works for good-sized systems”
* Se refiere a grupo completo de test conseguidos, no los small scaled test usados en el día a día del desarrollo.
55. APLICANDO TESTS AL DESARROLLO
TEST AFTER DRIVEN DEVELOPMENT (TAD)
• Grupos dedicados a la programación de los tests.
• Los desarrollos no tienen en cuenta la testeabilidad del diseño y el código funcional.
• Util para la validación, verificación y detección de bugs, no para dirigir desarrollos.
• Produce menor cobertura de código testeado.
• Peligro de abandono de tests: no se mantienen, quedan obsoletos y se desactivan.
TEST FIRST DEVELOPMENT (TFD)
• Ciertos tests son difíciles de programar por adelantado.
•Normalmente para tests muy alejados del código (sistema, integración, aceptación, etc.).
• Más util para la detección de fallos que para dirigir los desarrollos (arquitecturas iniciales).
• Peligro de abandonar los tests: distan del código, son más frágiles y requieren mayor mantenimiento.
• Peligro de programar tests inútiles: El desarrollo posterior introduce cambios de diseño que invalidan los tests.
TEST DRIVEN DEVELOPMENT (TDD)
• Los tests son programados por los desarrolladores de la funcionalidad.
• Están al mismo nivel que el código.
• Produce un diseño y un código adaptado completamente al unit testing.
• Dirige los desarrollos y proporciona cobertura completa de “tests unitarios”.
• Los tests son parte del desarrollo de la funcionalidad: no se abandonan.
• Programación en incrementos muy reducidos (small scaled tests).
• Siguen siendo necesarios tests de más alto nivel para validar completamente el sistema.
56. TEST DRIVEN DEVELOPMENT
FUNDAMENTOS Y OBJETIVOS
Sólo se implementa la funcionalidad necesaria, programando
siempre el “ahora” y nunca el “mañana” (YAGNI).
Dirigir desde el código la arquitectura, diseño y codificación.
Minimizar el número de defectos que llegan a los tests de más alto
nivel o a la plataforma de producción.
Producir software modular, altamente reutilizable y preparado
para el cambio.
“El desarrollador asume roles de arquitecto, diseñador y programador”
La implementación de pequeños ejemplos en iteraciones constantes hace
emerger la arquitectura que se necesita.
Y se produce una arquitectura, diseño y código que emerge de la no
ambigüedad de los tests automatizados.
57. TIPOS DE TESTS
ALCANCE FINALIDAD
TESTS DE SISTEMA
TESTS DE INTEGRACIÓN
TESTS UNITARIOS
VISIBILIDAD
TESTS DE ACEPTACIÓN
TESTS FUNCIONALES
TESTS NO FUNCIONALES
RENDIMIENTO STRESS
CARGA . . .
TESTS DE INTERFAZ DE USUARIO
TEST DE CAJA BLANCA
TEST DE CAJA NEGRA
Definiendo un vocabulario común
Test visuales o programados.
Dificultades para su automatización.
¿Es viable e incluso recomendable la
automatización de todos los tipos?.
58. ALCANCE DE LOS TESTS
TESTS DE SISTEMA
Ejercitan el sistema desde los extremos (end-to-end).
Entran por la interfaz del usuario final y llegan hasta el extremo opuesto.
Ej. desde la Web hasta la Base de Datos.
Se ejecutan en la plataforma de pre-producción.
TESTS DE INTEGRACIÓN
Ejercitan varias unidades del sistema de forma conjunta, verificando la
correcta colaboración entre dependencias.
Se ejecutan en la plataforma de pre-producción.
Aunque son aceptables otros entornos según las intenciones de las pruebas.
Múltiples combinaciones según las colaboraciones implicadas.
La frontera que transforma test unitarios en integración a veces es difusa.
Algunos test de integración se pueden usar en las iteraciones TDD (test de
integración a pequeña escala).
59. ALCANCE DE LOS TESTS
TESTS UNITARIOS
Ejercitan una unidad de código de forma aislada.
Un test = un caso o ejemplo de uso de un método o función.
Se compilan y se enlazan con el código funcional.
Se ejecutan en el equipo de cada desarrollador.
También periódicamente en entornos de desarrollo con herramientas de
integración continua.
Pertenecen a los desarrolladores (usando TDD no hay otra opción).
A veces no es claro cual debería ser el nivel de aislamiento.
60. FINALIDAD DE LOS TESTS
TEST DE ACEPTACIÓN O DE CLIENTE
Verifican que el software cumple con los requisitos de negocio.
Pertenecen al Product Owner o Cliente.
Definidos en el Sprint Planning (ATDD: Discusión y Destilación)
Todo el equipo (desarrolladores y testers incluidos)
Al menos el Product Owner ayudado por el analista del negocio.
Tests Funcionales y No Funcionales.
Son “Tests de Sistema”(*) ejecutados en entorno de pre-producción.
Ayudan al cliente a entenderse y a los desarrolladores a entender al cliente.
Seguimiento del grado de avance: Especificación de Requisitos Ejecutable
TESTS DE CERTIFICACIÓN(**)
Los Tests de Aceptación son parecidos a los tests de certificación definidos
por un grupo de QA. En ATDD se usan como Tests de Cliente que
proporcionan un Mecanismo de Especificación de Requisitos.
* No suelen entrar por la IU, pero se suelen considerar tests de sistema.
** Certificación: ver notas.
61. FINALIDAD DE LOS TESTS
TEST DE INTERFAZ DE USUARIO
Verifican la interfaz de los usuarios finales a todos los niveles.
Son test de sistema, ejecutados en entorno de pre-producción.
Aspecto, usabilidad, interactividad, interacción H-M, flujo, textos, faltas de ortografía, ausencia
de fallos, tiempos de respuesta, legislación (ej. accesibilidad de la web, LSSICE), etc.
TEST DE ACEPTACIÓN vs INTERFAZ DE USUARIO
Los tests de aceptación no tienen por qué ejercitar la UI.
Validan la lógica de negocio o funcionalidad del sistema.
Verifican QUÉ hace el sistema, NO CÓMO se usa.
Dirigir los desarrollos mediante test de UI puede producir una arquitectura o
diseño demasiado acoplado a una UI específica.
La funcionalidad debería ser completamente independiente de la UI.
Permitir el cambio de la UI o tener varias UI sin afectar a tests de aceptación.
La conexión entre la lógica y la UI debería provocar pocos fallos si se ha
conseguido una buena cobertura de tests unitarios o de integración.
Incluir los tests de UI como tests de aceptación si es requisito de negocio.
62. FINALIDAD DE LOS TESTS
TESTS FUNCIONALES
Tests de aceptación que verifican los requisitos funcionales.
Son tests de sistema ejecutados en entorno de pre-producción.
TESTS NO FUNCIONALES
Tests de aceptación que verifican requisitos no funcionales.
Existen varios tipos de tests no funcionales:
Carga, stress, rendimiento (ej. número de altas por minuto), tolerancia a fallos
(HA, High availability), etc.
Los puede definir un grupo de QA encargado de asegurar los requisitos
mínimos de calidad de toda la organización.
El cliente puede imponer sus propios requisitos no funcionales.
63. TIPOS DE TESTS
Aceptación
Sistema UI
Unitario Integración
Unitario
Integración
64. TEST UTILIZADOS EN TDD
LO QUE NO ES TDD
¿Cuántos tests habría que codificar antes de la funcionalidad?
TESTS DE
INTERFAZ
DE
USUARIO
TESTS
FUNCIONALES
TESTS NO
FUNCIONALES
TESTS DE
INTEGRACIÓN
TESTS
UNITARIOS
CODIFICAR
TEST DE SISTEMA O INTEGRACIÓN
Habría que codificar innumerables tests antes de escribir una sola
línea de código funcional.
Existen multitud de combinaciones de integración (qué con qué).
Extreme Programming precisamente quiere reducir la carga de
trabajos previos, buscando la inmediatez.
¡Uf!
¿Repetir todo por cada iteración?
* Ver notas
65. TEST UTILIZADOS EN TDD
SPRINT PLANNING: Planificar Tests
TESTS DE ACEPTACIÓN (ATDD)
FUNCIONALES NO FUNCIONALES
INTEGRACIÓN
CARGA
Se deciden los tests a realizar
ATDD: deciden siguiente funcionalidad. Entrada de
datos del bucle TDD.
Grupo de QA: Certificación, test que aseguran la
calidad de los productos.
Visuales, automáticos, antes, después, etc.
Valorar recursos y esfuerzos de mantenimiento.
Desarrollo Incremental (minuto a minuto)
Todos utilizan TDD para los desarrollos.
Small Scaled Test: Se van desgranando tests de
aceptación en test unitarios o de integración.
Los tests se mantienen y se desarrollan de forma
paralela a la funcionalidad.
Los tests dirigen el desarrollo y las siguientes
iteraciones deciden nuevos tests.
* Ver notas
INTERFAZ DE USUARIO
SISTEMA
…
DEVELOPMENT: Desarrollar con TDD
66. DISTRIBUCIÓN DE LOS TESTS
TRADITIONAL TESTING PYRAMID
Invertir más esfuerzos en test de alto nivel y dejar
el código con pocos o ningún test unitario.
SYSTEM, GUI,
ACCEPTANCE TESTS
INTEGRATION
TESTS
UNIT
TESTS
PIRÁMIDE INESTABLE
Los test de alto nivel pesan demasiado, la pirámide se desmorona fácilmente.
La mayor carga está en los test más frágiles y más dependientes del entorno.
Esta aproximación que requiere esfuerzos específicos de desarrollo y mantenimiento.
Los tests quedan obsoletos fácilmente y se corre el riesgo de abandonarlos.
67. DISTRIBUCIÓN DE LOS TESTS
MIKE COHN’S TESTING PYRAMID
Small in number
GUI (Test de UI y de Sistema)
TESTS
At least one per story
SE INTENTA EVITAR EXCESIVA
REDUNDANCIA DE TESTS
(Test Funcionales, No Funcionales, Integración y Sistema)
Loads of them, per method
ACCEPTANCE
TESTS
UNIT TESTS
(Test Unitarios Aislados, Test de Integración de Pequeña Escala)
Automated tests were considered expensive to write and were often written months, or
in some cases years, after a feature had been programmed.
One reason teams found it difficult to write tests sooner was because they were
automating at the wrong level.
An effective test automation strategy calls for automating tests at three different
levels, as shown in the figure, which depicts the test automation pyramid.
•Mike Cohn. Uno de los fundadores de la Scrum Alliance. Profesor en el first Certified ScrumMaster course,. Varios libros publicados sobre Agile.
* http://blog.mountaingoatsoftware.com/the-forgotten-layer-of-the-test-automation-pyramid
•http://www.scrumalliance.org/
68. PROPIETARIOS DE LOS TESTS
PRODUCT OWNER
PRODUCT MANAGER
SCRUM MASTER
• Aceptación o Cliente
• Interfaz de Usuario
• Funcionales
• No Funcionales
DEVELOPER GROUP
• Desarrolla con TDD
• Unitarios
• Integración
QA GROUP
• Sistema
• Interfaz de Usuario
• Certificación
• Calidad
• Funcionales
• No Funcionales
69. RUNNING TDD
ITERATIVO E INCREMENTAL
Cada iteración del bucle un incremento muy reducido.
Unas 10 modificaciones (del código) en cada iteración.
Objetivo: avanzar de forma paralela tests y funcionalidad.
No todos los tests por adelantado (si no en incrementos)
RAPID FEEDBACK
La pequeña escala de las iteraciones permite tomar decisiones de
diseño y arquitectura lo antes posible para aplicarlas durante la
refactorización.
70. RUNNING TDD
1 - ADD A TEST
Seleccionar un test de aceptación
Incrementalmente se descompone en “Small Scaled Tests”
Test Unitarios Aislados o Tests de Integración de Pequeña Escala.
Elaboramos nuestro propio “Unit Test Backlog”
Nuestra lista informal de tests para las N siguientes iteraciones.
Small Scaled Test
Término extraído de “Test Driven Development: By Example “, Kent Beck.
Recomendable utilizar Tests Unitarios Aislados.
En ocasiones más sencillo no aislar ciertas partes. Realizar el test unitario
con las dependencias reales (integración de pequeña escala).
Valorar en función de cuánto nos alejamos de las propiedades F.I.R.S.T
Tests que deben cumplir las propiedades F.I.R.S.T (test unitarios).
71. F.I.R.S.T
Son propiedades que deben cumplir los tests utilizados en el bucle TDD, y que no
aplican a otros tipos de test como los test de integración y los test de sistema.
FAST
INDEPENDENT
REPEATABLE
SMALL
TRANSPARENT
72. F.I.R.S.T
FAST
Cada test unitario se debe ejecutar en milésimas de segundo.
Se ejecutan mientras desarrollamos en pequeños incrementos.
2 minutos por iteración: 240 ejecuciones en 8 horas.
Los tests de integración (o alto nivel) son demasiado lentos.
Hay que lanzar todos los test del modulo o unidad en desarrollo.
Si se vuelven lentos: separar las baterías de test.
Tests usados en el desarrollo y lanzados en cada iteración.
Tests a largo plazo (antes de un commit o integración).
Los “Tests Runners” pueden lanzar tests de una sola clase o un solo método.
Existen IDEs o plugins que ejecutan los tests automáticamente
mientras estamos codificando.
Cuanto más veloz mejor: el test es lo que nos permite avanzar.
73. F.I.R.S.T
INDEPENDENT
Cada test unitario completamente independiente del resto de tests.
Nunca predeterminar un orden en la secuencia de tests unitarios.
Un test es una unidad muy pequeña.
Ej: un método de una clase de test de xUnit.
Cada método de test se debe ejecutar independiente del resto.
Las librerías tipo xUnit fomentan la independencia de los tests.
No deben depender de configuraciones de despliegue.
No depender del acceso a una base de datos concreta.
No depender de datos existentes.
No depender de acceso a máquinas remotas concretas.
…
Debe ser posible su ejecución en cualquier máquina en cualquier estado.
* Los test de más alto nivel que no se usan en TDD no tienen por qué cumplir estas propiedades. Por ejemplo un test de IU que necesita
probar una secuencia predeterminada del flujo de eventos, o tests de una máquina de estados.
74. F.I.R.S.T
REPEATABLE
Un test unitario debe ser inocuo.
No debe alterar el sistema.
Debe comportarse como si nunca se hubiera ejecutado.
No alterar datos de producción, no enviar emails, mensajes, etc.
Debe producir siempre el mismo resultado.
75. F.I.R.S.T
SMALL
Probar la mínima cantidad de funcionalidad posible.
Deben ser atómicos, probar lo indivisible.
Prueban un solo caso de un método de una clase.
Permitir localizar más rápidamente los defectos.
Evitar el tener que usar el depurador para encontrar la causa del fallo.
Idealmente cada test debería tener un solo assert.
En ocasiones se puede relajar.
Ej. tests de la misma funcionalidad.
76. F.I.R.S.T
TRANSPARENT
Debe estar claro cual es el propósito de cada test.
Debe funcionar como documentación.
Ejemplo de uso de la API que estamos testeando.
Ejemplo de uso del Object Under Test o Subject Under Test.
SIMPLICITY: debe ser sencillo, simple y fácil de leer.
Un test complejo es indicio de que el diseño está fallando.
Si un test se complica: plantearnos una refactorización.
Un test debería quedar claro incluso sin usar comentarios.
77. TESTS UNITARIOS: OTRAS CARACTERÍSTICAS
El Object Under Test NO debe tener código de test.
Aunque sí se recomiendan adaptaciones que faciliten su testing.
Pero la verificación (assert) siempre debe ser externa.
Los tests usados en TDD nunca deben cruzar fronteras.
Ni comunicación entre maquinas ni entre procesos.
Dependencias externas convierten tests unitarios en tests de integración.
Si fallan las dependencias será complicado encontrar el fallo.
Peor cuanto más extensa sea la cadena de dependencias.
Evitar el uso del depurador: aislando dependencias.
¿Qué convierte un test unitario en test de integración?
Habitualmente no se consideran librerías del sistema o de terceros.
Intentar el aislamiento entre módulos del sistema en desarrollo.
El uso de dependencias reales puede simplificar los tests.
El desarrollador debe valorar (dando preferencia a test aislados).
78. RUNNING TDD
2 - RUN THE TEST
El test debe fallar: la funcionalidad aun no se ha codificado.
IMPORTANTE: evitar escribir falsos tests o código no probado.
En la primera iteración el código ni siquiera compilará.
Pero en siguientes iteraciones se estará modificando código existente.
Es necesario asegurar que falla.
Este paso se tiende a olvidar: NO SALTÁRSELO NUNCA (*)
Cuando el test no falla.
Revisar la implementación del test y del código funcional.
Violación YAGNI:
Funcionalidad anticipada en una iteración anterior.
Se había codificado más de lo necesario.
Funcionalidad implementada sin su correspondiente test.
Peligro de acabar con código funcional no testeado.
* La importancia de hacer fallar los tests: ver notas.
79. RUNNING TDD
3 – MAKE A LITTLE CHANGE
CODIFICAR
Se codifica sólo lo necesario para satisfacer el test.
Programar el “ahora” no el “mañana” (YAGNI).
NO ANTICIPAR FUNCIONALIDAD
Evitar el “por si acaso luego se necesita” (al final nadie lo usará).
Más productivo dejar para mañana lo que se necesita mañana.
La funcionalidad anticipada al final habrá que modificarla.
“El esfuerzo de futuras modificaciones será menor que el esfuerzo
de haber anticipado una funcionalidad, arquitectura o un diseño”
80. RUNNING TDD
3 – MAKE A LITTLE CHANGE
SIEMPRE DEBE EXISTIR UN TEST QUE FALLA
Codificando la solución de un test pueden aparecer nuevas necesidades.
NO SE CODIFICAN AHORA
Se apuntan en el “Unit Test Backlog” para la siguiente iteración.
Por trivial que sea: no se codifica algo sin su correspondiente test.
Se corre el riesgo de dejar código sin cobertura de test.
FLEXIBILIDAD vs ANTICIPACIÓN
Es fácil adaptarse a cambios de requisitos gracias a la cobertura de tests.
La refactorización hará emerger incrementalmente la arquitectura o el
diseño flexible, desacoplado y modular que se persigue.
COLECTIVIDAD DEL CÓDIGO
Deja de existir el “no lo voy a tocar por si acaso”.
Todos los desarrolladores pueden cambiar todo.
Los test unitarios avisarán si algo deja de funcionar.
81. CÓDIGO AUTO-EXPLICATIVO
MINIMIZACIÓN DE COMENTARIOS
Las prácticas Agile (XP o TDD) recomiendan la minimización de comentarios.
El código se evoluciona, pero los comentarios suelen acabar obsoletos.
Los comentarios no suelen tener mantenimiento: al final acaban desfasados.
Minimización, no eliminación.
Los comentarios siguen siendo importantes y deben seguir existiendo.
Comentarios referidos a la semántica que no cambia.
Responsabilidades de módulos, clases o métodos.
Ej. JavaDocs, comentarios en las cabeceras de métodos, clases, etc.
NUNCA dejar en forma de comentario el código obsoleto.
Si el código no sirve se elimina.
Si el código eliminado era necesario los tests avisarán.
La herramienta de SCM (Software Configuration Management) permite
volver hacia atrás en cualquier momento.
82. CÓDIGO AUTO-EXPLICATIVO
CÓDIGO SENCILLO, CLARO Y LIMPIO
Si hacen falta muchos comentarios, la solución se ha complicado.
Revisar la implementación o el diseño y refactorizar.
IDENTIFICADORES O SÍMBOLOS
Identificadores significativos y auto-explicativos (mejor que comentarios)
Un nombre de clase, atributo, método, variable, etc. lo debe decir todo, no
importa lo largo que sea.
Los IDEs actuales evitan tener que teclearlos (autocompletar).
La longitud no afecta al rendimiento del código compilado.
TESTS UNITARIOS COMO DOCUMENTACIÓN
Son ejemplos de cómo se usa nuestro código.
Deben ayudar a entender el código mejor que cualquier comentario.
La documentación describe una API, pero los ejemplos la clarifican.
Complementan la documentación, no la sustituyen.
(*) Ejemplos de código autoexplicativo: Proyectos OpenSource, se entienden perfectamente casi sin comentarios.
83. RUNNING TDD
4 – RUN THE TEST
Se continua ejecutando el test y codificando la funcionalidad.
Hasta que la funcionalidad de la iteración actual se completa.
Si se encuentra un bug no relacionado con la iteración actual:
Se debe implementar un tests antes de corregirlo.
El test debe reproducir el bug encontrado y fallar.
La funcionalidad se codifica para corregir el test fallido.
Reparar los bugs sin codificar los tests: dejará código sin probar.
84. RUNNING TDD
5 – REFACTOR
Modificar diseño e implementación sin alterar comportamiento.
Se plantea si es necesaria una refactorización:
Al finalizar y al empezar cada iteración.
REFACTORIZAR PARA:
Eliminar código repetido, arreglando el “copy & paste” realizado.
Extraer código reutilizable en librerías comunes.
Producir código legible, mantenible y extensible (funcionalidad y tests).
Mejorar arquitectura, diseño y codificación.
La refactorización es incremental: NO SE APLAZA.
Refactorizar el código funcional y el código de los tests.
Refactorización guiada mediante aplicación de los principios S.O.L.I.D.(*)
SIN REFACTORIZACIÓN NO EXISTE TEST DRIVEN DEVELOPMENT
85. S.O.L.I.D.
Principios básicos de programación y diseño orientado a objetos cuya aplicación
conjunta hace más probable la construcción de sistemas fáciles de extender y mantener.
SINGLE RESPONSIBILITY PRINCIPLE
OPEN/CLOSED PRINCIPLE
LISKOV SUBSTITUTION PRINCIPLE
INTERFACE SEGREGATION PRINCIPLE
DEPENDENCY INVERSION PRINCIPLE
(*) Introducidos por Robert C. Martin a principios del 2000.
86. S.O.L.I.D
SINGLE RESPONSIBILITY PRINCIPLE (SRP)
Un objeto debe tener una única responsabilidad, que debe estar
enteramente encapsulada en una clase.
Toda la funcionalidad proporcionada por una clase debe estar
estrechamente alineada con su responsabilidad.
Por extensión el mismo principio se aplica a los métodos.
Una clase o método deben resolver una sola problemática.
Una única responsabilidad = Una única razón para ser modificada.
En TDD refactorizamos para extraer responsabilidades.
Ejemplo:
Un módulo que genera, formatea e imprime un informe.
Puede haber dos razones para modificarlo.
Cambiar el contenido o cambiar el formato.
Son dos responsabilidades, lo que implica dos clases.
87. S.O.L.I.D
OPEN/CLOSED PRINCIPLE (OCP)
Las entidades de software (clases, módulos, métodos, etc.) deben
estar abiertas a extensiones pero cerradas a modificaciones.
Permitir cambios en su comportamiento sin necesidad de alterar
su código fuente.
Varias formas de resolverlo.
Herencia
Heredando se pueden sobreescribir métodos que cambian el comportamiento.
Pero la clase está cerrada: se ha finalizado su responsabilidad.
Se puede ampliar o modificar la funcionalidad heredando y sobreescribiendo.
Interfaces o clases abstractas
Dependencias sobre entidades sin implementación.
Se puede sustituir la implementación, cambiando el funcionamiento de una
colaboración sin afectar a los clientes de la interfaz.
Evitar construcciones del lenguaje que cierran a modificaciones.
Ej. métodos o clases “final” en Java.
88. S.O.L.I.D
LISKOV SUBSTITUTION PRINCIPLE (LSP)
Define la semántica de la relación “subtipo”.
Si q(x) es una propiedad demostrable sobre un objeto de tipo T. Entonces
q(y) debe ser cierto para objetos de tipo S cuando es un subtipo de T.
Garantiza la semántica de interoperabilidad entre los tipos
implicados en una jerarquía (semántica de la herencia).
Si un método acepta un tipo T, debe funcionar correctamente con objetos
de cualquier tipo heredado de T.
El código que funciona sobre clases base debe seguir funcionando sobre
clases heredadas.
Evita la violación de la semántica de las clases heredadas.
Si una clase heredada modifica la semántica de la clase base, se empezarán a
obtener fallos inesperados (normalmente a largo plazo) al usar los objetos en
métodos creados para objetos de la clase base.
89. S.O.L.I.D
LISKOV SUBSTITUTION PRINCIPLE (LSP)
Típico ejemplo de violación LSP.
Clase “Cuadrado” heredando de clase “Rectángulo”.
Un cuadrado también tiene ancho y alto, aunque con el mismo valor.
El constructor de la clase cuadrado iguala ambos valores.
El método “ZoomToFit” trabaja sobre rectángulos
y puede modificar las propiedades “length” y
“witdh” de forma independiente.
Por herencia “ZoomToFit” acepta “cuadrados”.
El método desigualará los lados del “cuadrado”,
rompiendo la semántica de la clase (o la
postcondición de igualdad de los lados).
90. S.O.L.I.D
INTERFACE SEGREGATION PRINCIPLE (ISP)
Evitar el diseño de interfaces extensas de propósito general.
Si una interfaz (o métodos públicos de una clase) ha crecido demasiado
existirán muchos objetos que sólo usan una parte de la interfaz.
Será necesario dividir en varias interfaces más pequeñas y específicas.
Mejor pequeñas interfaces específicas que una sola de propósito general.
Los clientes de la interfaz (API) solo deben depender de lo que
realmente utilizan.
Ningún objeto debe ser forzado a depender de métodos que no utiliza.
Un solo jugador no puede estar molestando el resto del equipo.
Si una interfaz es extensa surgirán muchos clientes que solo
necesitan una parte de la interfaz.
Indirectamente se estarán acoplando los objetos a la interfaz completa y
tendrán más posibilidades de ser afectados por cambios en la interfaz.
91. S.O.L.I.D
DEPENDENCY INVERSION PRINCIPLE (DIP)
Los objetos deben depender de interfaces en lugar de implementaciones.
Los objetos no crean o buscan las instancias de sus dependencias.
Los objetos permiten que las dependencias se configuren de forma externa.
APLICANDO
DIP
EJEMPLO
GlobalAddressBook está fuertemente acoplada a
la clase RadomIdGenerator.
Depende de una implementación específica y
debe crear o buscar la instancia utilizada.
EJEMPLO
CommandLineUI será encargado de crear las
instancias y configurar las dependencias, que sólo
están basadas en interfaces.
92. S.O.L.I.D
DEPENDENCY INVERSION PRINCIPLE (DIP)
Principio para el desacoplamiento de dependencias.
Las relaciones de dependencia establecidas desde los niveles más
altos a los más bajos de invierten(*).
High-level modules should not depend on low-level modules. Both should
depend on abstractions.
Abstractions should not depend on details. Details should depend on
abstractions.
Inversion of Control (IoC): Normalmente Direct Injection (DI).
IMPORTANTE: Para usar IoC no es necesario ningún framework.
Es un patrón de diseño, no hace falta Spring, JDK7, Plexus, etc.
La colaboración se basa en interfaces, y la implementación de las
interfaces se entrega de forma externa.
IoC: Hollywood Principle
“Don’t call us, we’ll call you”
* Definición formal de DIP: la abstracción utilizada suele ser una Interfaz.
Una buena explicación en: http://www.oodesign.com/dependency-inversion-principle.html
93. S.O.L.I.D
DEPENDENCY INVERSION PRINCIPLE
OBJETIVO: Dividir responsabilidades usando acoplamiento débil.
SRP: Responsabilidades que se pueden extraer en forma de nuevas clases.
DIP: Las dependencias se gestionan aplicando el principio de inversión.
REFACTORIZANDO
1. Localizar una responsabilidad que se puede extraer como una nueva clase.
2. Crear una interfaz para la nueva clase (sólo usará otras interfaces).
3. Las clases sólo dependerán de la interfaz (depender de especificación y no
de implementación).
4. Los objetos no crean o buscan sus dependencias, no es su responsabilidad.
5. Habilitar la configuración externa de dependencias (Direct Injection,
constructores, setters, getters, etc.).
6. Los clientes, contenedores o marcos de trabajo crean las instancias y
configuran las dependencias.
94. S.O.L.I.D
DEPENDENCY INVERSION PRINCIPLE
Permite crear un diseño altamente desacoplado.
Cambiar la implementación de las dependencias sin modificar o recompilar
el código fuente, incluso por configuración en la plataforma de despliegue.
PROPORCIONA UN DISEÑO:
Modular: Simplifica mantenimientos. El código se modifica en clases simples
y enfocadas en una sola responsabilidad.
Flexible: Modificar una dependencia o cambiar la implementación utilizada
en niveles superiores no afecta ni al código fuente ni al compilado.
Configurable: Permite configurar las dependencias de forma externa,
incluso en la plataforma de producción, sin recompilar el código.
Acoplamiento Débil: de módulos, unidades o clases.
EFECTO LATERAL: Facilita el aislamiento de los Unit Tests.
Test aislados mediante la inyección de Mocks en el Object Under Test.
Podemos aislar el test unitario de sus dependencias reales o dirigir su
ejecución (como si estuviéramos depurando con datos de prueba).
95. S.O.L.I.D
DEPENDENCY INVERSION PRINCIPLE (DIP)
CommandLineUI crea o busca las instancias concretas de la implementación de cada interfaz.
Puede configurar la colaboración en tiempo de despliegue, seleccionando y configurando las
dependencias. Se pueden combinar diferentes implementaciones de GlobalAddressBook para
ofrecer por ejemplo diferentes niveles de caché.
La clase CommandLineUI puede ser un contenedor genérico capaz de leer desde un fichero la
configuración de dependencias e implementaciones para un despliegue concreto.
96. DISEÑO Y ARQUITECTURA
¿CUANDO ESTAMOS DISEÑANDO CON TDD?
Principalmente durante la refactorización de cada iteración.
ANTICIPARSE A LA REFACTORIZACIÓN.
La refactorización no implica hacer las cosas mal para luego corregirlas.
Durante la codificación de la funcionalidad (después del test) podemos
directamente implementar buenos diseños, si los hemos identificado.
Si se identifican responsabilidades se implementan directamente separadas.
Las colaboraciones se implementan directamente aplicando DIP.
Se evita el copy & paste con métodos privados o clases reutilizables.
…
Los ejercicios del curso muestras un avance muy granular, para
ejercitar los principios de la refactorización.
Cuanto más rápido se trabaje y con mejor calidad mejor.
Sin olvidar que no hay que anticipar semántica y funcionalidad.
Sin olvidar que no se codifica ni una línea de código sin su test.
98. FASES DE UN TEST UNITARIO
SET UP
Preparar los Test Fixtures (REPEATABLE).
Instanciar el Object Under Test.
Preparar Test Objects (datos de ejemplo que se usan en los tests).
EXERCISE
Interactuar con el Object Under Test (ejecutar el código a testear).
VERIFY
Determinar que ha sido obtenido el resultado esperado.
TEAR DOWN
Limpiar (INDEPENDENT) o desmontar los Text Fixtures.
FIXTURE: Estado predeterminado que fija la base de los test que se ejecutarán.
Crear un conjunto de datos de prueba determinado (ej. base de datos en tests de integración).
Preparar y configurar el Object Under Test.
Preparar y configurar varios Test Objects.
Preparar y configurar Mocks, Stubs, Fakes, etc.
…
* Fixture o Test Context
99. FASES EN LIBRERÍAS xUNIT
SET UP: setUp, Before, etc.
VERIFY: assertTrue, assertEquals, expectedException, etc.
TEAR DOWN: tearDown, After, etc.
CLASS SET UP & CLASS TEAR DOWN (afterClass, beforeClass)
PELIGRO: Violación del principio INDEPENDENT (code smell).
Prepara un Fixture que se reutilizará en todos los tests.
Tener mucho cuidado, mejor evitarlo (dependencias entre tests).
Se suele encontrar en tests de integración lentos.
EJEMPLO: Tests de Integración para Data Access Objects.
Class Set Up / Class Tear Down:
Crea y destruye las tablas de la base de datos de pruebas (preferible en
memoria). Los tests serían lentos si se hiciera antes y después de cada test.
Set Up / Tear Down: begin/rollback transaction (INDEPENDENT).
El rollback permite comenzar siempre con un estado determinado, con la
base de datos vacía, con los datos del Fixture común o de cada test.
100. CICLO DE VIDA DE TESTS xUNIT
Está diseñado para cumplir las propiedades F.I.R.S.T. Suele ser el mismo en todas las
tecnologías o frameworks xUnit (JUnit, CppUnit, SUnit, PyUnit, JsUnit, etc.).
EJEMPLO: Clase TestAddressBook (contiene 3 tests, un método cada test)
// Ejecución (simplificada) que realizará JUnit
// Crea todas las instancias, una para cada test, con sus propios atributos
// independientes(INDEPENDENT)
test1 = new TestAddressBook();
test2 = new TestAddressBook();
test3 = new TestAddressBook();
TestAddressBook.setUp() // Before Class
test1.setUp()
test1.testSomething() // Ejecución del test 1 sobre la instancia 1
test1.tearDown()
test2.setUp() // Before Test (INDEPENDENT)
test2.testAllThatStuff() // Ejecución del test 2 sobre la instancia 2
test2.tearDown() // After Test (REPEATABLE)
test3.setUp()
test3.testAnythingElse() // Ejecución del test 3 sobre la instancia 3
test3.tearDown()
TestAddressBook.tearDown() // After Class
* http://en.wikipedia.org/wiki/XUnit#xUnit_Frameworks (arquitectura librerías xUnit)
* http://en.wikipedia.org/wiki/List_of_unit_testing_frameworks (lista completa de librerías xUnit)
101. UNIT TEST CODE COVERAGE
Existe una teoría extensa de cobertura de tests (*)
Líneas de código funcional ejecutado.
Evaluación de expresiones en puntos condicionantes (branch coverage).
Herramienta para el seguimiento (SCRUM masters).
Herramientas para informes de cobertura: Cobertura, Quilt, Clover, etc.
Establecer los niveles mínimos de cobertura exigidos.
Decidir el código que se cubre con test de alto nivel en lugar de unitarios (QA).
Decidir código al que no se le exige unit testing.
No es necesario exigir 100% de cobertura en los tests.
Ej. propiedades respaldadas por atributo (generación automática, lenguajes con
soporte directo de properties, etc.)
Descubrimiento de código funcional innecesario, el test no lo ejecuta debido a
que el código sobra (a veces sucede en condicionantes).
En ocasiones mucho código repetitivo y simple no necesita tests.
Programando con TDD se consigue amplia cobertura de forma natural.
* http://en.wikipedia.org/wiki/Code_coverage: Introducción y referencias sobre teoría de cobertura.de tests.
103. DUMMY / FAKE / STUB / MOCK / SPY
Dependency is the key problem in software
development at all scales…. Eliminating duplication in
programs eliminates dependency.
— Kent Beck, Test-Driven Development: By Example
104. DUMMY / FAKE / STUB / MOCK / SPY
DUMMY
Se entrega como argumento pero nunca se utiliza.
Sirve para cubrir requerimientos de parámetros.
FAKE
Exhibe una implementación completa pero simulada de un objeto.
Ej. HttpServletRequest que permite configurar parámetros de
entrada, un DAO usando datos en memoria, etc.
STUB
Respuestas prefijadas con argumentos de entrada específicos.
Carece de la implementación “real” que tiene un FAKE, tan sólo
devuelve respuestas programadas para llamadas concretas.
Permite dirigir el comportamiento del Object Under Test para forzar
la ejecución del código a testear.
Ej. searchContact(“Pedro”): Contact / searchContact(“Edu”): null
105. DUMMY / FAKE / STUB / MOCK / SPY
MOCK
Evolución de un STUB que permite realizar verificación por interacción.
Definir expectativas en las llamadas a sus métodos.
Prueba si el Object Under Test utiliza las dependencias como se espera.
PELIGRO: No conviene abusar de la verificación por interacción.
Ej. verificar que únicamente se usa un método de la clase, que se llama N
veces, que no se llama nunca, etc.
SPY
Partial Mock.
Utiliza una implementación real modificando alguno de sus métodos.
Aplicando el principio Open/Closed (abierto a extensiones, cerrado a
modificaciones) se puede heredar y sobrescribir los métodos simulados.
PELIGRO: Mejor evitarlo, síntoma de code smell.
Útil para tests unitarios dentro de Heavyweight Frameworks.
Frameworks rígidos no preparados para unit testing.
Suele ser necesario para modificar comportamientos heredados desde el
framework por el Object Under Test (Ej. Servlets).
(*) Mock: Se utilizará esta nomenclatura para nombrar de forma genérica todos los tipos.
106. DUMMY / FAKE / STUB / MOCK / SPY
PROPÓSITO
AISLAR TEST UNITARIOS (*)
Para ajustar el test a las propiedades F.I.R.S.T.
Para evitar interferencias de las dependencias en el código testeado.
Una cadena extensa de dependencias dificulta la localización de los bugs.
El test unitario debería validar exclusivamente el código del Object Under Test.
Evitar que un test unitario falle por errores en las dependencias.
Fallos que deberían ser detectados en los tests unitarios de dichas dependencias.
Evitar el uso del depurador para localizar el origen de los fallos.
FORZAR EJECUCIÓN DE CÓDIGO
Forzar la ejecución del código que se quiere ejercitar en el test.
Evitar la dependencia sobre código con alta aleatoriedad (REPEATABLE).
Dirigir el comportamiento del Object Under Test en la ejecución del test.
VERIFICAR COLABORACIONES
Probar que el Object Under Test usa correctamente sus dependencias.
PELIGRO: La verificación por interacción se debe minimizar.
* Aislamiento de Test Unitarios: ver notas
107. TIPOS DE VALIDACIÓN
VALIDACIÓN POR ESTADO
Verificar que se devuelve el valor esperado o que el Object Under
Test termina en un estado determinado (estado de las propiedades)
después de la ejecución del test.
No se hacen suposiciones de cómo el Object Under Test utiliza sus
dependencias.
Suele producir test de caja negra (menos frágiles).
Puede proporcionar menos cobertura de código testeado al no
adaptarse a detalles específicos de la implementación.
108. TIPOS DE VALIDACIÓN
VALIDACIÓN POR INTERACCIÓN
Verificar que el Object Under Test utiliza correctamente sus dependencias.
Suele producir test de caja blanca (más frágiles).
Revelan detalles de la implementación.
Permiten capturar los parámetros de las llamadas a las dependencias.
Posibilitando una forma de validación por estado en entornos poco adaptados al
unit testing (Heavyweight Frameworks).
Usando mocks suele ser más sencillo idear validaciones por interacción.
Aunque conviene evitarlo ya que hacen el test más difícil de entender (menos
claridad) y más acoplado a la implementación.
Los tests de caja blanca si son válidos en TDD (dirige el desarrollo).
Si el test se rompe tras cambios en la implementación, sólo hay que arreglarlo.
En la mayor parte de los casos se puede convertir una validación por interacción
en una validación por estado, programando la respuesta adecuada del stub.
109. VALIDACIÓN POR INTERACCIÓN
PELIGROS Y RECOMENDACIONES
EVITAR LA EXCESIVA VALIDACIÓN POR INTERACCIÓN
EVITAR VERIFICACIONES DE INTERACCIÓN REDUNDANTES
VER EJERCICIOS Y EJEMPLOS(*)
El desarrollo de la capa de lógica de negocio mediante TDD
requiere test unitarios aislados de la capa de persistencia (DAOs).
Es necesario utilizar mocks/stubs de las dependencias (DAOs).
Es fácil caer en el error de programar los tests de la lógica de
negocio como verificaciones rigurosas de toda interacción (o falta
de interacción) con la capa de persistencia.
* Ejercicios Mocking Avanzado: Los controladores de la web se comenzaron con test de interacción (lo más inmediato)
y al final se acabaron convirtiendo en test de validación por estado (con menos código y más legibles).
* Ejercicios Persistencia: implementación del servicio GlobalAddressBook basado en el uso de DAOs.
110. VALIDACIÓN POR INTERACCIÓN
EVITAR LA EXCESIVA VERIFICACIÓN DE INTERACCIONES
Convertir validación por interacción en validación por estado.
La misión principal de un mock/stub es proporcionar datos de
ejemplo para las dependencias del Object Under Test.
Misión del test unitario:
Únicamente verificar que la lógica del Object Under Test es correcta.
No debe verificar la lógica de las dependencias: antipatrón.
No debe verificar como utiliza sus dependencias, validando toda interacción
o falta de interacción.
No es importante verificar que el resultado del código bajo prueba
se produce utilizando ciertas dependencias.
111. VALIDACIÓN POR INTERACCIÓN
TIPOS DE INTERACCIONES
Asking an object of data: El resultado del código bajo prueba se genera
mediante datos devueltos por las dependencias.
STUBBING:
Las dependencias devuelven datos que permiten la ejecución del código.
Assert: Correcto si devuelve el valor esperado.
Una verificación por interacción sería redundante.
El verify sería una copia exacta de la programación del stub.
Si el test no falla el resultado habrá usado el stub: interacción implícita.
Telling an object to do something: El código bajo prueba no devuelve un
resultado, su misión es invocar alguna dependencia.
MOCKING:
Las dependencias no devuelven datos, sólo ejecutan acciones.
Verify: Correcto si termina invocando la dependencia con los datos adecuados.
Un Test = Un único Assert o un único Verify.
El verify será el equivalente al assert, que ahora no podemos usar.
El verify es tan sólo un medio para realizar la afirmación.
Ver: http://monkeyisland.pl/2008/04/26/asking-and-telling/
112. DEPENDENCIAS VS SUSTITUTOS
¿Cuándo utilizar objetos falsos o dependencias?
Lo debe decidir el desarrollador en función de la complejidad.
Intentar cumplir las propiedades F.I.R.S.T.
Usar las dependencias reales puede requerir menos trabajo.
Se convierten en test de integración, si la cadena se alarga serán más frágiles.
Pueden complicar la localización y corrección de los errores.
El objeto falso permite aislar y realizar verificaciones por interacción.
Pueden requerir más esfuerzo, pero aíslan de fallos de las dependencias.
La verificación por interacción acopla el test a la implementación.
El test unitario no realiza pruebas de integración (lo que se persigue en TDD).
RECOMENDACIÓN
Usar el objeto real si la cadena de dependencias no es muy profunda.
Si cumple FIRST aunque sea de integración: no modifica datos, rápido, etc.
Usar las dependencias si el uso de mocks supone mucho código de test.
Mocks: más fácil cuanta más experiencia, las librerías lo convierten en un juego.
113. GENERACIÓN AUTOMÁTICA DE MOCKS
Las librerías de generación de mocks convierten la creación y uso
de mocks en un juego.
Java: JMock, EasyMock, Mockito, etc.
Mockito: Simplicidad y documentación de pocas páginas (en XP y TDD la
simplicidad siempre debe tener un peso importante).
Otros lenguajes: mockcpp, MockitoPP, OCMock, NMock, etc.
Facilidades:
Todas las librerías de mocks en cualquier lenguaje o tecnología se
suelen fundamentar sobre la misma teoría y paradigmas, se usan
prácticamente igual.
* http://en.wikipedia.org/wiki/List_of_mock_object_frameworks (lista de mock objects frameworks)
* http://www.xprogramming.com/software (lista librerías y herramientas para XP y TDD)
114. GENERACIÓN AUTOMÁTICA DE MOCKS
PELIGROS Y RECOMENDACIONES
Las librerías de generación de mocks y frameworks de unit testing suelen
contener características muy avanzadas.
TDD y XP promueven la simplicidad: conveniente restringirse siempre a lo más
básico (creación de tests simples y claros).
No hay que convertirse en un experto de las librerías (con dos o tres cosas es suficiente).
Más del 90% del unit testing se resuelve con menos del 10% de las facilidades de los
frameworks.
La propia documentación de las librerías avisa de características peligrosas.
Facilidades que cubren cualquier posible necesidad (testing de código legacy).
Avisan que el uso de algunas facilidades (ej. spy) produce malos diseños (code smell).
Indicador de la necesidad de refactoring
Si los tests se complican o se necesitan características complejas de las técnicas o librerías
de mocks, quizás se está avanzando hacia un mal diseño (del código funcional), indicador
de que se necesita refactorizar.
SOLID: Separando responsabilidades, usando la inversión de dependencias, etc.
se pueden obtener mejores diseños y más fáciles de probar.
116. CONTENEDORES / COMPONENTES
HEAVYWEIGHT FRAMEWORKS
La mayoría de los frameworks “legacy” son rígidos, pesados y no están
optimizados para el Unit Testing.
LIGHTWEIGHT FRAMEWORKS
Actualmente la comunidad de desarrolladores propone y busca entornos ágiles
basados en conceptos como:
Don’t Repeat Yourself (DRY).
Convention Over Configuration (CoC).
Lightweight Frameworks over Heavyweight Frameworks.
Plain Objects (clases simples débilmente acopladas con el framework).
EJEMPLOS EN JAVA
Frameworks MVC con acoplamiento débil para aplicaciones web: SpringMVC.
Inversión of Control se está incluyendo de forma nativa en lenguajes y librerías de
sistema: JDK7 (Direct Injection nativo), Spring IoC, Plexus, etc.
EJB 2.1 se simplifica en EJB 3.0 (conversión de frameworks heavyweight en lightweight)
EJB 3.0 adopta conceptos de frameworks ligeros como Spring Framework.
Uso de Plain Old Java Objects (POJO)
Entornos que además de proporcionar arquitecturas y diseños independientes, flexibles,
desacopladas y ágiles, proporcionan mayor ayuda al unit testing.
117. DECIDIENDO: UNIT TESTING VS INTEGRATION TESTING
Las capas de presentación y persistencia siempre representan un problema para su
desarrollo usando TDD, que se reduce a decidir entre Unit Tests o Integration Tests.
UNIT TESTS *
Cumplen las propiedades FIRST: se pueden usar en TDD.
Los test se deben lanzar continuamente en el equipo de cada desarrollador para poder
avanzar en los desarrollos con escala reducida de incrementos que propone TDD.
La misión de un Test Unitario es validar el código bajo prueba de forma aislada.
El aislamiento se consigue utilizando mocks (o sus variantes).
Se puede utilizar test de integración de pequeña escala.
CUIDADO: no convertir el test en un test de las dependencias del Object Under Test.
El test unitario prueba únicamente el código del Object Under Test.
Se asume que las dependencias funcionan correctamente, se codificaron usando TDD.
El aislamiento es clave para no convertir el Unit Testing en una carga adicional.
Probar que de forma aislada el código acepta los datos de entrada, ejecuta el
código sin errores y devuelve los datos esperados.
* Unit Test: Small Scaled Tests, test unitarios aislados o test de integración de pequeña escala.
118. DECIDIENDO: UNIT TESTING VS INTEGRATION TESTING
INTEGRATION TESTS *
No cumplen las propiedades FIRST: no se pueden usar en TDD.
No se pueden lanzar continuamente avanzando en pequeños incrementos.
No dirigen los desarrollos desde la codificación de forma tan directa.
Se pueden automatizar por adelantado: Test First Development.
Se ejecutarán de forma más espaciada (más lentos).
Cada x horas o minutos, en cada commit, etc.
Tareas configuradas en la herramienta de Integración Continua.
Suelen ser propiedad del grupo de calidad, testing o QA.
NECESARIOS: validan las colaboraciones y detectan los fallos que no se
pueden detectar con test unitarios.
Test de Integración en TDD.
Se pueden usar test de integración de pequeña escala para simplificar código.
El desarrollador puede mantener una batería paralela de test de integración.
* Integration Test: O también test funcionales, no funcionales, de aceptación de sistema, de interfaz de usuario, etc.
119. CAPA DE PRESENTACIÓN Y PERSISTENCIA
RECOMENDACIONES
Evitar la excesiva redundancia en el código de los tests.
Muchos test unitarios se pueden reutilizar como test de integración con solo
cambiar las dependencias en la plataforma de preproducción.
Valorar si es más productivo usar test de integración.
Los test unitarios podrían requerir un uso intensivo de mocks.
Los test de integración podrían ser una repetición de los test unitarios.
Programando con TDD se intenta aligerar código de las capas de
presentación y persistencia.
¿Merece la pena crear test unitarios para capas tan ligeras?
¿Es más productivo si directamente programamos test de integración?
¿Existe un grupo de QA programando buenos test de sistema?
¿Podemos aprovecharlo para evitando la redundancia en capas complicadas?
No existe receta mágica, se debe valorar la situación específica.
* Ver notas de la diapositiva.
120. CAPA DE PRESENTACIÓN Y PERSISTENCIA
RECOMENDACIONES
Minimizar la lógica de las capas difíciles de testear.
PERSISTENCIA (código de acceso a datos): Usar Data Access Objects.
Se extrae y se centraliza el código de acceso a base de datos (SRP).
PRESENTACIÓN: Usar arquitecturas MVC, delegando la “C” sobre la capa de
negocio.
El controlador delega todo el trabajo sobre la capa de negocio.
El controlador solo contiene código repetitivo de validación de entrada, acceso a
la capa de negocio, publicación del modelo y redirección a la vista adecuada.
Plantear cambio de test unitarios por integración (o sistema).
El código de estas capas será muy simple y repetitivo.
Será sencillo localizar fallos en estas capas aunque solo cuenten con test de
integración o sistema.
121. CAPA DE PRESENTACIÓN
Minimizar el código existente en la capa de presentación.
Nunca debe contener lógica de negocio.
La funcionalidad especificada mediante los test de aceptación (ATDD).
Solo debe contener código directamente relacionado con la Interfaz de Usuario.
Dividir responsabilidades: Arquitecturas MVC.
VISTAS
Renderizan el modelo que han construido los controladores.
No suelen admitir Unit Testing: Muchas veces usan lenguajes declarativos.
MODELO
Clases simples que solo contienen propiedades (o los objetos del dominio).
Unit Testing si contienen lógica específica: por ejemplo propiedades generadas en lugar de
respaldadas por atributos.
CONTROLADOR
Delegar toda la lógica sobre los objetos de la capa de negocio.
Tareas repetitivas: Procesar la entrada del usuario, exportar un modelo a las vistas, seleccionar
la vista y entregarla al usuario.
El desarrollador debe decidir si crear test unitarios o depender de test de más alto nivel.
Muy repetitivo: seguro que se puede reutilizar código de test en todos los controladores
La lógica de presentación en conjunto quedará probada por test de integración,
de sistema o de interfaz de usuario (si se dedican recursos de QA).
122. CAPA DE PRESENTACIÓN
Extraer responsabilidades aplicando el principio SRP
Independizar o extraer la lógica compleja de los controladores.
Extracción de datos entregados por el usuario, validación y conversión de los
datos, etc.
Responsabilidades con lógica específica de presentación pero que
puede ser extraída y reutilizada.
Ej. Validación y conversión de una fecha en forma de texto.
Lógica que debe ser desarrollada con TDD (con sus tests unitarios).
Se puede testear fácilmente si se extrae de los controladores.
Ejercicios
La lógica de extracción, verificación y conversión de los datos introducidos en
un formulario se ha extraído desde AddContactController en la nueva clase
ContactCommandConverter que ahora tiene tests específicos de la lógica.
123. CAPA DE PRESENTACIÓN
APLICACIONES WEB
La programación de pruebas unitarias presenta dificultades cuando se
depende de un entorno de contenedor no preparado para el Unit
Testing (Heavyweight Framework).
Dificultades para cumplir con las propiedades FIRST (uso exhaustivo
de mocks).
Es preferible contar con un framework MVC ligero (Lightweight
Framework) que permita aislar fácilmente los controladores, y
proporcione utilidades especificas para el testing.
124. APLICACIONES WEB
UNIT TESTING
Parámetros de entrada y salida: uso de fake/stub/mock/spy.
El contenedor es el encargado de crear los parámetros de entrada y salida.
Ej. En un servlet la entrada y salida es el request y response, y el código
puede alterar el estado de objetos compartidos como el context y session.
Creación de fakes o librerías de terceros con fakes completos.
Ej. El HttpServletRequest no proporciona un setParameter. Mocks de Sprint
Tests Utilities o HttpServlet Unit.
IN-CONTAINER UNIT TESTING
Ejecutar los test unitarios directamente en el contenedor de preproducción.
Evita el uso de mocks usando los objetos reales del contenedor.
Los tests unitarios son exactamente iguales, pero sin utilizar mocks.
Ej. Java: CACTUS.
Permite ejecutar Unit Tests de Serlvets, Filters, JSPs, EJBs, etc en cualquier
contenedor J2EE.
125. APLICACIONES WEB
INTEGRATION TESTING
Herramientas que permiten la programación test de sistema o integración.
Independientes de la tecnología utilizada en el desarrollo de la aplicación web.
Lanzan peticiones HTTP y proporcionan diferentes herramientas de verificación.
EJEMPLOS
HTTP Client Library: cualquier cliente HTTP permite la ejecución de pruebas de
integración sobre una aplicación web, una api rest, una api soap, etc.
Ej. curl, httperf, Apache HttpClient, etc.
Programación y simulación manual del entorno del browser y la verificación de respuestas.
HtmlUnit: Los test se deben programar en Java (no la aplicación web).
Emula navegadores específicos sobre la máquina virtual de Java.
Selenium: La programación de los test se puede realizar con: HTML, Java, C#, Perl,
PHP, Python, y Ruby. Proporciona un IDE de generación automática de tests.
Ejecuta los test sobre un navegador real en un sistema operativo determinado.
Ambos permiten lanzar las peticiones simulando eventos sobre el DHTML. Ofrecen
facilidades de verificación del contenido de las páginas web a cualquier nivel de
detalle, estructura de navegación, Test de Aceptación, etc.
“¿Qué se debería verificar en los test de integración?”
“¿Es conveniente incluso la verificación de un CSS?”
126. APLICACIONES DESKTOP
La interfaz de usuario de una aplicación de escritorio (o una
aplicación para móvil) se puede desarrollar usando TDD.
Si existe código ejecutable se puede programar un test unitario.
Las aplicaciones para móviles se desarrollan sobre un equipo, por
tanto se pueden ejecutar los test unitarios en el IDE de desarrollo.
UNIT TESTING
Se aplican las ideas comentadas para la capa de presentación.
Utilización de arquitecturas MVC: centralización de la lógica del GUI.
Delegación en la lógica de la capa de negocio.
Extracción de la lógica compleja y específica en forma de responsabilidades.
INTEGRATION TESTING
Herramientas para la programación de test de sistema y de integración.
Permiten la manipulación de la Interfaz de Usuario desde el código.
Herramientas específicas, limitadas y complejas.
Ej. UIAutomation, NUnitForms, etc.
127. CAPA DE PERSISTENCIA
Desde un punto de vista purista, los test que ejercitan el código de
acceso a base de datos no pueden ser considerados test unitarios, ya
que dependen de una entidad externa, la base de datos.
¿Son test de Integración? ¿Test funcionales? ¿Test No Unitarios?
No hay receta mágica: la categoría dependerá del contexto.
Aplicar las ideas ya expuestas para la capa de persistencia.
Centralización de la lógica de acceso a datos en Data Accesss Objects (DAO).
Los sistemas de Object Relational Mapping (ORM) alivian el unit testing.
DECIDIR: Integration Testing vs Unit Testing.
EJEMPLO DE TÉCNICAS MÁS COMUNES
Frameworks de testing de base de datos (Ej. DbUnit).
Bases de datos embebidas o en memoria (Standard SQL)
Base de datos de desarrollo o preproducción (rollback al finalizar cada test
para inicializar los datos de prueba).
128. EXPLORATORY TESTING - SPIKE SOLUTIONS
A style of software testing that emphasizes the personal freedom and
responsibility of the individual tester to continually optimize the quality of
his/her work by treating test-related learning, test design, test execution,
and test result interpretation as mutually supportive activities that run in
parallel throughout the project.
— Cem Kaner (*)
Acceptance Test Driven Development (ATDD) propone actividades de
Exploratory Testing al finalizar la fase de desarrollo.
Extreme programming propone la creación de Spike Solutions: pequeños tests
que permiten la exploración de alternativas.
Reducción del riesgo: Desarrollo de pequeñas alternativas para la acotación de
riesgos (test de rendimiento, de carga, etc.).
Diseño basado en el aprendizaje del sistema a producir mediante la exploración
de alternativas a través del testing.
Aprendizaje de nuevas tecnologías mediante la experimentación.
No recomendable la aplicación de nuevas tecnologías, frameworks o arquitecturas sin un
conocimiento básico de su funcionalidad y las repercusiones sobre el sistema.
Discusión de ideas: Explicar soluciones mediante el código y sus tests.
* Cem Kaner: Profesor de Ingeniería del Software en el Florida Institute of Technology, Director del Florida Tech's Center for Software Testing
Education & Research.
130. TDD ANTI-PATTERNS
Extraído del blog: http://blog.james-carr.org/2006/11/03/tdd-anti-patterns/
— James Carr.
THE LIAR
•EL MENTIROSO. Todos los asserts se cumplen pero el test no está probando la funcionalidad
que realmente se quería probar.
EXCESSIVE SETUP
•EXCESIVA CONFIGURACIÓN . Demasiado código en los Fixtures o configuración, que impide ver
lo que se está probando o la finalidad del test.
THE GIANT
•EL GIGANTE. El test es correcto pero contiene demasiado código, probando muchos casos de
uso. O una clase de test con una cantidad enorme de tests. Indica necesidad de aplicar SRP.
THE MOCKERY
•EL EMULADOR. Se abusa de los mocks, stubs o fakes. El sistema no acaba siendo testeado, se
están validando los datos devueltos por los mocks. Demasiada verificación por interacción. El
test es un reflejo del código funcional, que tan solo prueba cómo se usan las dependencias.
131. TDD ANTI-PATTERNS
THE INSPECTOR
• EL INSPECTOR. Test que viola la encapsulación (partes privadas) o desvela demasiados
detalles de implementación para conseguir 100% de cobertura. Cualquier
refactorización posterior romperá el test.
GENEROUS LEFTOVERS
• SOBRAS ABUNDANTES. Tests que violan la propiedad de independencia. Los datos de un
test se mantienen y no se limpian, aprovechándose o interfiriendo en otros tests.
THE LOCAL HERO
• EL HÉROE LOCAL. Los tests solo funcionan en una plataforma específica o con una
configuración específica. No sirven para TDD, los tests se deben ejecutar en el equipo de
cualquier desarrollador.
THE NITPICKER
• EL QUISQUILLOSO. El test valida la salida completa o demasiados detalles no
significativos. Cada pequeño cambio requiere el mantenimiento del test. (Ej. Comparar
el title o el css de las páginas html de una aplicación web).
132. TDD ANTI-PATTERNS
THE SECRET CATCHER
•CAZADOR SECRETO. Parece que no realiza verificaciones por ausencia de asserts, pero en
realidad verifica que no se lanza alguna excepción. Conviene aclarar la finalidad capturando la
excepción y finalizar con un fail.
THE DODGER
•EL DESERTOR. Test que prueba muchos efectos colaterales, pero no termina de probar la
autentica funcionalidad el Object Under Test. Ej. Verificar que no se alteran datos, o no se
ejecutan ciertos métodos de las dependencias, etc.
THE LOUDMOUTH
•EL ALBOROTADOR. Llena la consola de demasiados datos de diagnostico, trazas, mensajes, etc.
incluso cuando se pasan los tests. Algunas veces se crean estas trazas durante la codificación
de los test, y al final se dejan aunque no sean necesarios (enturbiando los resultados).
THE GREEDY CATCHER
•CAZADOR GLOTÓN. Captura e ignora las excepciones (aunque escriba trazas), dejando pasar
los tests. Problemática muy habitual en aplicaciones Java (incluso en código funcional).
133. TDD ANTI-PATTERNS
THE SEQUENCER
• EL SECUENCIADOR. Fija el orden de los resultados. Los asserts esperan que los
resultados se reciban siempre en el mismo orden (métodos que devuelven listas).
HIDDEN DEPENDENCY
• DEPENDENCIA OCULTA. Test que presupone la existencia de ciertos datos de test en la
plataforma de ejecución. Otros desarrolladores deben descubrir que datos se supone
que deberían existir.
THE ENUMERATOR
• EL ENUMERADOR. Los nombres de los métodos de test son sólo una enumeración como
test1, test2, test3, en lugar de utilizar nombres significativos que indiquen la intención
de cada uno de los tests.
THE STRANGER
• EL EXTRAÑO. Test que está realizando pruebas sobre un objeto que no pertenece al
Object Under Test de la clase de test. Una clase de test se debería encargar de los test de
una sola clase del código funcional. También El Pariente Lejano (Distant Relative).
134. TDD ANTI-PATTERNS
THE OPERATING SYSTEM EVANGELIST
•EVANGELISTA DEL SISTEMA OPERATIVO. Los test que dependen de caracteristicas del sistema
operativo. Ej. Suposiciones de los caracteres de nueva línea, comandos de la shell, etc.
SUCCESS AGAINST ALL ODDS
•A PESAR DE TODO FUNCIONA. Se escribe el código funcional antes de hacer fallar al test
aunque este se escribiera antes, o se escriben test sobre código que ya existe. Un efecto lateral
es que el test puede pasar aunque el código tenga fallos.
THE FREE RIDE
•DOS POR UNO. En lugar de escribir un nuevo test para probar una nueva funcionalidad, se
añade un nuevo assert en un test ya existente.
THE ONE
•EL ÚNICO. Combinación de varios patrones (Free ride and Giant). Un solo método de test que
prueba todos los casos de uso del objeto en secuencia. Contiene múltiples fixtures y asserts.
135. TDD ANTI-PATTERNS
THE PEEPING TOM
• EL MIRÓN. Debido a recursos compartidos un test puede ver los resultados de otro test
y puede hacer que el test falle incluso cuando el sistema testeado es válido. Suele
producirse con el uso de variables de clase (miembros estáticos). También conocido
como The Uninvited Guests.
THE SLOW POKE
• EL TORTUGA. Un test increíblemente lento. No apto para el desarrollo con TDD, si no
para tests ejecutados en la herramienta de integración continua.
136. MALAS PRACTICAS
IGNORAR LAS PROPIEDADES F.I.R.S.T.
Propiedades ideadas para la aplicación de TDD como una metodología de
desarrollo, iterativa, incremental, a pequeña escala y dirigida por unit tests.
No están ideadas para su utilización en metodologías de testing.
NO DEJAR CLARO CUAL ES EL OBJECT UNDER TEST
Test en los que se confunde el Object Under Test entre el resto de Test
Objects, mocks, etc.
El Object Under Test debería ser lo más sencillo de identificar.
NO REFACTORIZAR en cada iteración
No se estaría usando la metodología TDD.
La refactorización no se aplaza (los momentos de descanso no existen).
TEST DIFÍCILES DE ENTENDER
Deben funcionar como documentación o ejemplos de nuestra API.
Nombres de tests y código auto-explicativo (fijar estándares).
137. MALAS PRACTICAS
VARIOS CASOS DE PRUEBA EN UN SOLO TEST
Se debe separar en diferentes métodos de test, cada test solo un assert.
Excepto cuando son asserts que pertenecen a una misma funcionalidad.
MEZCLAR TEST DE INTEGRACIÓN CON TEST UNITARIOS
Se deben mantener en carpetas o proyectos independientes aunque ambos
se puedan escribir usando librerías xUnit.
CONVERTIR TEST UNITARIOS EN TEST DE DEPENDENCIAS
El test unitario debe probar única y exclusivamente el Object Under Test.
Aunque se usen dependencias reales, se asume que tienen sus tests.
ABUSAR DE LA VERIFICACIÓN POR INTERACCIÓN
Problemática vista en el tema de mocks y antipatrones.
Si los tests tan solo contienen verify sobre lo mocks, revisar los tests.
139. ACCEPTANCE TEST DRIVEN DEVELOPMENT
REFERENCIAS
ATDD es una metodología relativamente reciente comparada con el resto de
metodologías Agile, aun no hay mucha literatura disponible.
http://www.dosideas.com/wiki/Acceptance_Test_Driven_Development
Pequeña introducción y enlaces a herramientas de automatización.
http://www.methodsandtools.com/archive/archive.php?id=72
Acceptance TDD Explained (Artículo de diez páginas)
http://www.methodsandtools.com/archive/archive.php?id=23
Using Customer Tests to Drive Development
Practical TDD and Acceptance TDD for Java Developers, 2007, Lasse
Koskela, Manning Publications
http://www.manning.com/koskela/
Acceptance Test Engineering Guide, 2009, Grigori Melnik, Gerard
Meszaros, Jon Bach, Microsoft
140. ACCEPTANCE TEST DRIVEN DEVELOPMENT
“Se acerca más a una metodología de especificación de
requisitos que una metodología de testing”
NOMENCLATURA
Acceptance Test Driven Development (ATDD)
Story Test Driven Development (STDD)
Customer Test Driven Development (CTDD)
141. ACCEPTANCE TEST DRIVEN DEVELOPMENT
OBJETIVOS
Permitir al cliente obtener un feedback del avance del desarrollo
mediante las pruebas de aceptación.
Pruebas de cliente: el cliente puede ser reacio a validar unos tests que
comprometen la aceptación del producto.
Obtener una especificación de requisitos mas concreta y menos
ambigua mediante el uso de ejemplos.
Los Users Stories se describen mediante ejemplos concretos de uso.
Los User Stories ya no se describen mediante la redacción de descripciones.
Ayudar al cliente a entender lo que realmente quiere
Haciéndolo consciente de la casuística de todos los User Stories
Ayudar a los desarrolladores a entender lo que quiere el cliente.
Concretando el comportamiento de cada User Story y evitando la libre
interpretación de la casuística no contemplada en la especificación.
ESPECIFICACIÓN DE REQUISITOS EJECUTABLE
142. ACCEPTANCE TESTS VS UNIT TESTS
ACCEPTANCE TESTS
• Escritos por los “clientes”.
• Qué hace el sistema.
• Tests de Features (Backlog).
• Vocabulario del negocio.
• Sin detalles implementación.
UNIT TESTS
• Escritos por desarrolladores.
• Cómo se hace el sistema.
• Tests del código.
• Vocabulario de la solución.
• Detalles de implementación.
143. ACCEPTANCE TESTS
PROPORCIONAN
CRITERIO DE FINALIZACIÓN
Proporcionado por el “cliente” a los desarrolladores.
Especificando cada User Story mediante ejemplos.
FOMENTA LA COMUNICACIÓN
Fuerza a clientes, testers y desarrolladores a trabajar juntos.
RAPID FEEDBACK
Herramienta para el Project Management.
Datos reales para medir el avance del desarrollo.
Total de test de aceptación, total fallidos y total cumplidos.