Desarrollo de Software conducido por Pruebas Test-Driven Development Aportes de: Agustín Goñi – Microsoft Cono Sur Temario • • • • • Testeo de aplicaciones Desarrollo conducido por las pruebas Impacto en el proceso de desarrollo Herramientas para TDD Buenas prácticas y recomendaciones Desarrollo conducido por pruebas • Procesos de desarrollo tradicionales – Etapas de análisis, diseño, implementación y testeo claramente separadas. – Asumen un diseño correcto al primer intento. – Se espera a tener el componente o pieza de software implementado para hacer pruebas. – Fallas en la etapa de testing significan tener que rehacer muchas trabajo. – Alto costo e impacto. Desarrollo conducido por la prueba • Procesos de espiral – Se construye por iteración. – Las etapas están separadas dentro de cada iteración, pero no en el proyecto completo. – Se reduce el riesgo de detectar tardíamente errores de muy alto costo. – A pesar de todo es necesario construir antes de comenzar el testing. Inconvenientes y problemas • Degradación del diseño – Las pruebas se dejan para el final. – Los errores pequeños no son detectados a tiempo y se propagan. – Los errores del componente se arreglan “extraoficialmente”. – El diseño no corresponde a la realidad; es el código el que manda. Temario • • • • • Testeo de aplicaciones Desarrollo conducido por las pruebas Impacto en el proceso de desarrollo Herramientas para TDD Buenas prácticas y recomendaciones Desarrollo conducido por la prueba • ¿Qué es el desarrollo conducido por la prueba? – Una técnica para la construcción de software que consiste en producir pruebas unitarias automáticas antes de escribir el código. – Proviene de la filosofía de Extreme Programming, buscando un proceso de desarrollo más ágil. Desarrollo conducido por la prueba • ¿Por qué antes? – Es una forma de prevenir defectos. – Permite analizarlos y contenerlos antes que se introduzcan en el código. – Busca detectar los errores antes en vez de arreglarlos después. Desarrollo conducido por la prueba ¿Cómo funciona? Æ Esquema Red/Green/Refactor 1. El desarrollador escribe tests unitarios para el componente que está por construir. 2. Compila (lo cual debería fallar: no se ha implementado) 3. Escribe código sólo para que compile. 4. Corre el test (lo cual debería fallar). 5. Arregla el código para que pase el test. 6. Corre el test y ve que pasa OK. 7. Hace refactoring del código para eliminar la duplicación y hacerlo más legible y mantenible 8. Vuelve a repetir. Desarrollo conducido por la prueba • ¿Cómo se realiza la prueba unitaria? – Los desarrolladores ejecutan el código para verificar que cumple los requisitos del diseño. – Si se encuentran defectos se hace debug. – Estas actividades se alternan con la codificación. – Muchas veces se agrega código de prueba para ejecutar el componente. Desarrollo conducido por la prueba • Código de prueba – A veces luego de terminado el desarrollo, se descarta el código de prueba. – A veces se guarda, pero mezclado con el código de producción (ej. en un método de prueba). – Como la prueba unitaria es responsabilidad del desarrollador, en general no se define un estándar para ordenar el código de prueba. Desarrollo conducido por la prueba • ¿Porque es un conductor del desarrollo? – El conjunto de prueba define una especificación programática del componente. – Hacerlo antes del desarrollo permite tener un criterio de satisfacción de los requisitos de diseño. – Es una guía de lo que se debe implementar. – Más importante, es un mecanismo que permite al diseño evolucionar. – Se postergan las decisiones hasta tener información más completa. Desarrollo conducido por la prueba • Proceso simple – El desarrollador hace la prueba con las mismas herramientas que usa y en el mismo lenguaje de programación. – Ordena prácticas que ya están presentes y ayuda a darle foco a la prueba. Temario • • • • • Testeo de aplicaciones Desarrollo conducido por las pruebas Impacto en el proceso de desarrollo Herramientas para TDD Buenas prácticas y recomendaciones Impacto en el proceso de desarrollo • El desarrollo conducido por la prueba afecta varias áreas del desarrollo: – Prevención de defectos. – Mantenimiento. – Diseño simple. – Confianza del desarrollador. – Documentación. – Trabajo en equipo. Impacto en el proceso de desarrollo • Prevención de defectos – Es el impacto directo de la técnica. – Se produce código con menos defectos, se previenen y corrigen muy temprano. – El conjunto de casos de prueba unitaria hace que el software sea auto-verificable. Impacto en el proceso de desarrollo • Mantenimiento – Repetir las pruebas muchas veces es fácil, agiliza el ciclo de desarrollo y el mantenimiento. – Cuando tenemos que realizar un cambio es menos riesgoso romper algo. – Contamos con una especificación programática del comportamiento. Impacto en el proceso de desarrollo • Diseño simple – Guía al desarrollador a un diseño simple. – Generalmente un diseño fácil de probar es un buen diseño. – Conduce a una interfaz simple y evita el acoplamiento. – Al implementar la interfaz se hace lo mínimo necesario para satisfacer el test. Impacto en el proceso de desarrollo • Confianza del desarrollador – El método incentiva los incrementos pequeños fácilmente verificables. – Se puede hacer una modificación y saber inmediatamente si todo sigue funcionando. – Refactoring del código con muy poco peligro de romper algo. Impacto en el proceso de desarrollo • Documentación – Los casos de prueba constituyen una documentación muy completa del comportamiento del componente. Impacto en el proceso de desarrollo • Trabajo en equipo – Relacionado con las prácticas de XP – La facilidad de cambiar la funcionalidad se traslada fácilmente a todos los miembros. – Lo importante es que se cumplan las pruebas sin importar quien modifique el componente. – No hay propietarios. Temario • • • • • Testeo de aplicaciones Desarrollo conducido por las pruebas Impacto en el proceso de desarrollo Herramientas para TDD Buenas prácticas y recomendaciones Herramientas para TDD • Para poner todo esto en práctica necesitamos una herramienta que permita: – Definir tests unitarios. – Ejecutarlos de manera automática. – Mantenerlos independientes del código de la aplicación. – Hacer cambios y volver a correr los tests de manera transparente. – Proveer una interfaz de usuario clara e informativa. Herramientas para TDD • Se han desarrollado varias opciones: – Originalmente el framework XUnit para C/C++ sobre Unix. – JUnit para Java basado en ese framework, creado por Erich Gamma y Kent Beck – Nunit, MbUnit para .NET. – Varios otros para lenguajes/ambientes específicos. – Algunas herramientas de desarrollo lo están incorporando en la IDE. Herramienta NUnit • Definiendo NUnit – Cumple con las condiciones que se busca en una herramienta de TDD. – Es un conjunto de clases (framework) que se extienden por herencia. – Es una herramienta de diseño muy simple. – Las pruebas se pueden codificar en cualquier lenguaje .NET. Herramienta NUnit • Radiografía de un caso de prueba: – Estímulo – Respuesta – Evaluación • Ejemplo (C#) [Test] public void PushOne() { stack.Push(“first element”); Assert.IsFalse(stack.IsEmpty); } Herramienta NUnit • Ejecución utilizando TestRunners Herramienta NUnit • Atributos en NUnit 2.x – A partir de la versión 2 se utilizan atributos. – Nos permiten atribuir características al código, en un nivel de abstracción diferente al de la funcionalidad. Herramienta NUnit • Ejemplo de atributos en NUnit – <Test()> Define un método de prueba – <TestFixture()> Define una clase que contiene métodos de prueba – <ExpectedException(Excepcion)> Cuando el resultado esperado es una excepción – <Ignore(“Mensaje”)> No se corre la prueba, pero da una alerta amarilla. – <SetUp()> Método de inicialización del fixture – <TearDown()> Método de finalización Demo NUnit y VisualStudio.NET Definición de Stack • Estructura de datos que almacena información en forma LIFO. Top • Operaciones: Push – Push – Pop – Top – IsEmpty IsEmpty? Pop Tests para un Stack 1. 2. 3. 4. 5. 6. 7. 8. 9. Crear un Stack y ver que IsEmpty() es true. Push un único elemento y verificar que IsEmpty() es false. Push un único elemento, luego Pop, y verificar que IsEmpty() es true. Push un único objeto, recordándolo, luego Pop y verificar que ambos son iguales. Push tres objetos, recordándolos y luego Pop, uno por uno, verificando su igualdad y orden correcto. Pop un Stack sin elementos. Push un único elemento, usar Top y verificar IsEmpty es false. Push un elemento, recordándolo y luego usar Top y verificar que es el mismo elemento. Llamar a Top con un Stack vacío. Conclusiones • How much of the intended code (Stack) is covered by one or more tests? Æ 100% Coverage • How much code was implemented in Stack.cs that didn’t serve for the required functionality? Æ None (Implement only what is needed) • How certain are we that the code works? Æ 100% • If we need to change Stack’s implementation to use (for example) a linked list, would it be easy to ensure we don’t break current version? Æ Yes! • If someone new needs to find out what the implemented class is intended to do, how can he/she learn? Æ Read unit tests. They serve as formal documentation. (Therefore, store Unit Tests with the official code) Extensiones de NUnit • Existen extensiones de la herramienta, orientados a ciertos modelos de aplicación • Estas herramienta facilitan las pruebas específicas, permitiendo un mayor nivel de abstracción. • Ejemplo: Win Forms, ASP, XML, etc. Extensiones de NUnit • NUnitASP y NUnitForms – Periten realizar pruebas funcionales que ejecutan acciones sobre la GUI. – Proveen de clases helper para • Ubicar controles en el form • Analizar el contenido • Disparar eventos. Extensiones de NUnit • Ejemplo NUnitForms (C#) Form form = new Form(); form.Show(); ControlTester button = new ControlTester(“nombreBoton"); button.FireEvent("Click"); ControlTester textBox = new ControlTester(“nombreTxtBox"); textBox[“texto"] = “texto"; Assertion.AssertEquals("defaultText", textBox[“texto"]; Extensiones de NUnit • Ejemplo NUnitASP (C#) LabelTester label = new LabelTester(“label", WebForm); LinkButtonTester link = new LinkButtonTester("linkBtn", WebForm); Browser.GetPage("http://localhost/ejemplo.aspx"); // Probar objetos de la página link.Click(); AssertEquals(“un click", label.Text); Visual Studio 2005 Visual Studio Visual Studio Visual Studio Team Architect Team Developer Team Test Application Modeling Dynamic Code Analyzer Load Testing Logical Infra. Modeling Static Code Analyzer Manual Testing Deployment Modeling Code Profiler Test Case Management Unit Testing Code Coverage Class Modeling Visio and UML Modeling Team Foundation Client Visual Studio Professional Edition Visual Studio Team Foundation Change Management Reporting Integration Services Work Item Tracking Project Site Project Management Visual Studio Industry Partners Process and Architecture Guidance Visual Studio Team System Temario • • • • • Testeo de aplicaciones Desarrollo conducido por las pruebas Impacto en el proceso de desarrollo Herramientas para TDD Buenas prácticas y recomendaciones Buenas prácticas y recomendaciones • Recomendaciones prácticas – Pensar en todos los casos de prueba necesarios; abordarlos uno a uno. – Mantener los casos de prueba simples – No confiar en un orden particular de ejecución – No dejar efectos laterales luego de la prueba – Si se encuentra un defecto, agregar el caso que lo muestra primero, luego corregir Buenas prácticas y recomendaciones • ¿Se puede escribir la prueba después o por un equipo independiente? – Lo podría hacer un equipo independiente, pero se pierde el feedback de las pruebas. – Se puede codificar la prueba luego de terminado el componente pero no se obtienen los mismos beneficios. – No sirve como prevención, ni como análisis del problema. Buenas prácticas y recomendaciones • Automatización – No intentar automatizar todo el proceso de prueba, puede no ser viable ni práctico. – La prueba debe ser estratégica en la búsqueda de defectos. – Utilizar distintos niveles y técnicas, los defectos son evasivos. Buenas prácticas y recomendaciones • Prueba funcional vs. TDD – Se ejecuta el software con el objetivo de encontrar defectos, con la perspectiva del usuario. – Prueba el sistema o divisiones funcionales como los casos de uso, a través de la IU. – Puede ser costosa de automatizar. – La prueba funcional sigue siendo necesaria y es una actividad complementaria al desarrollo conducido por la prueba. Resumen Resumen • TDD es una técnica para desarrollar basado en las pruebas. • TDD puede mejorar significativamente el proceso de desarrollo. • Existen herramientas que permiten llevarlo a cabo en forma automática. Referencias • James Newkirk; Test Driven Development in Microsoft .NET • Kent Beck; Test Driven Development, by example • Will Stott, James Newkirk; Test-Driven C#, Improve the Design and Flexibility of Your Project with Extreme Programming Techniques http://msdn.microsoft.com/msdnmag/issues/04/04/ExtremeProgrammi ng • Visual Studio 2005 Team System http://lab.msdn.microsoft.com/vs2005/teamsystem/ • • Herramientas www.nunit.org - www.junit.org – www.testdriven.net Noticias, recursos www.testdriven.com