• Home
  • Las 5W de ChileAgil
  • Políticas
  • Contacto
  • Síguenos en Twitter

ChileÁgil

Comunidad Ágil y Lean de Chile

  • AgileDay
    • AgileDay 2012
    • Agile Day 2011
      • Concilio ágil
      • Documentos
    • AgileDay 2010
      • Chile Ayuda. Un terremoto, cientos de voluntarios y 6 días para levantar un portal
      • Implementación de un modelo ágil de gestión por valor de negocio de un portafolio de proyecto
      • Introducción a la Agilidad y el Lean Thinking
      • La Agilidad en 3 décimas por Pablo Morales
      • Lo que odiamos de la Agilidad
      • Los desafíos de la Agilidad en Chile
      • Software Craftmanship
    • AgileDay 2009
      • Agile Perú y Agiles 2010
      • Casos de Éxito – NameAction
      • Casos de Éxito – Proyecto Proaud
      • Lean y Kanban
      • MicroCharlas
      • ScrumManager
      • Web 3: Innovación descentralizada
      • The HashRocket Way
      • Palabras finales
    • AgileDay 2008
  • Eventos
  • Tutoriales
  • Cursos
  • Lenguajes
  • Metodologías
    • Customer Development
    • eXtreme Programming
    • Scrum
    • TDD
  • Publicaciones
  • Opinión
  • Rescata un computín
  • Scrum
You are here: Home / Metodologías / TDD / Introducción al Desarrollo Ágil: Test Driven Development

Introducción al Desarrollo Ágil: Test Driven Development

19/11/2010 by vj 1 Comment

Introducción

En Extreme Programming (XP) el Unit Testing es una práctica fundamental del ciclo de desarrollo al llevar a cabo un proyecto al permitir al equipo de desarrollo modificar y refactorizar el código existente sin miedo a romper funcionalidades, pues cada una de ellas tiene un test asociado que debe cumplirse y que, en principio, verifican la validez de toda la “superficie de contacto” de cada uno de los módulos que conforman el sistema.

Esta capacidad de adaptarse al cambio es fundamental en proyectos ágiles, donde el cambio no es percibido como algo peligroso o nocivo, sino intrínseco a un proyecto y como un factor que debe ser tomado en cuenta en todo el proceso de gestión y desarrollo.

A pesar de esto, el Unit Testing requiere de mucha disciplina para llevarse a cabo, pues para cada funcionalidad que se desee agregar se debe escribir, antes que nada, uno o varios test que comprueben que la funcionalidad está implementada correctamente. Esta necesidad de escribir las pruebas antes de la funcionalidad propiamente tal nos obliga a escribir pruebas que no dependen del código que hayamos escrito (pues aún no existe) volviéndonos jueces más imparciales a la hora de evaluar nuestro trabajo.

Finalmente, el estar forzados a escribir tests antes de implementar funcionalidades nos obliga a considerar con más detenimiento las responsabilidades y diseño de éstas, lo que generalmente se traduce en código más robusto y entendible para otros desarrolladores.

El ímpetu por agregar funcionalidades constantemente o por deadlines inminentes son fuertes tentaciones para no escribir unit tests, pero debemos ser capaces de racionalizar que el escribir previamente tests de unidad ya estamos subconscientemente definiendo el comportamiento de nuestro nuevo método o función sin preocuparnos del código particular detrás de él, lo que nos da claridad al momento de definirlo, además que escribir código de buena calidad en este momento nos ahorra dolores de cabeza al tener que extenderlo o mantenerlo a futuro.

En este artículo damos una pincelada inicial al desarrollo guiado por tests (Test Driven Development) con un ejemplo práctico, mediante el cual nos podemos desvincular de las particularidades del lenguaje de programación o del problema y concetrarnos en el flujo del desarrollo. En particular tampoco nos vamos a concentrar en el círculo de gestión de proyecto o de generación de valor para el cliente.

Nuestro ejemplo

En este ejemplo, y como parte de un sistema más grande, se nos solicita implementar un “stack”, una estructura de datos que representa una pila de objetos, en el que podemos insertar elementos (“push”) y sacar elementos (“pop”) uno a uno y en orden predefinido, en el que cada vez que realizamos un “pop” sacamos el último elemento que fue insertado al stack.

Para este caso definimos las siguientes funcionalidades:

  • Se deben poder crear stacks
  • Se debe poder insertar elementos en el stack mediante “push”
  • Se deben poder extraer elementos mediante “pop”
    • Si en este caso el stack está vacío, se debe gatillar un error o excepción
  • Se debe poder consultar el tamaño (número de elementos) de un stack en cualquier momento

Definición de la plataforma

Para llevar a cabo nuestro proyecto usaremos Python gracias a su flexibilidad y popularidad, además de requerir muy poco código inicial para definir funcionalidades.

Aquellos que no son familiares con Python no debieran tener mayores problemas siguiendo la guía pues el código es muy intuitivo, sólo se tiene que bajar el intérprete de Python si se ejecuta en Windows, pues en Linux y OS X viene incluido con el sistema operativo.

En principio podemos hacer unit testing escribiendo un programa común y corriente que llame los métodos de la clase que nos interesa y verifique manualmente sus resultados, pero dicha alternativa es engorrosa y de automatización no trivial. En vez de eso usaremos unittest, una suite de testing automatizado para Python inspirada en JUnit, por lo que su comportamiento debiera ser familiar para quienes han hecho testing antes.

Apegándonos a la convenciones, implementaremos el stack como una clase llamada Stack y definida en el archivo stack.py, los tests correspondientes a esta misma clase los definiremos en una clase llamada TestStack y definida en el archivo test_stack.py

Primera iteración

Test

En principio tenemos que escribir un test para cada una de las funcionalidades que vamos a implementar, inclusive el hecho de que nuestra clase Stack esté definida por nosotros, por lo que la primera versión de nuestro archivo test_stack.py es como sigue:

from stack import Stack

Para ejecutar nuestro test simplemente ejecutamos

python test_stack.py

De lo que obtenemos

Traceback (most recent call last):
  File "test_stack.py", line 1, in
    from stack import Stack
ImportError: No module named stack

¿Parece un test muy trivial? Imaginémosnos que Python ya define una clase Stack como parte del lenguaje y también en el paquete stack, en ese caso el test no habría arrojado ningún error y habríamos notado de inmediato que algo anda mal, ahorrándonos el proceso de cambiar el nombre de la clase para no tener colisiones con la definición de Python.

Lo único criticable del test es que debiera “reportar” que no hay una clase Stack disponible en vez de caerse, algo que se puede corregir, pero que preferimos no hacerlo pues complicaríamos innecesariamente los test posteriores.

Implementación

Para hacer que nuestro test “pase” necesitamos definir la clase Stack en el archivo correcto (stack.py)

class Stack:
    pass

Necesitamos agregar el “pass” dentro de la clase pues aún está vacía, algo que es inválido en Python, por lo que se usa esa palabra clave para que no lance errores al momento de ejecutarse.

Si ejecutamos nuevamente el test ejecuta sin decir nada, lo que nos indica que aprobamos el primer test.

Segunda iteración

En esta ocasión vamos a testear e implementación la creación de stacks

Test

En esta ocasión nuestro test es más concreto que el anterior y aprovecha la biblioteca unittest. Para usarla, cada una de nuestras clases que se dediquen a la ejecución de tests tiene que heredar de la clase unittest.TestCase.

Una vez creada la clase, definimos los tests, que son todos los métodos dentro de la clase cuyo nombre empiece con “test” y que son llamados automáticamente cuando se ejecuta el test.

import unittest
from stack import Stack

# Tests para la clase Stack
class TestStack(unittest.TestCase):
    # Test para verificar el constructor del stack
    def test_stack_creation(self):
        s = Stack()

if __name__ == '__main__':
    unittest.main()

Cuando se carga el test se define la clase TestStack y, si es que llamamos explícitamente el test (“python test_stack.py”) la condición al final del archivo (que está fuera de la clase) es cierta y se ejecuta unittest.main(), que carga y ejecuta todos los test definidos en la clase TestStack y muestra los resultados.

Como no hemos definido un constructor para Stack es natural que esperemos que nuestro nuevo test falle, pero al ejecutarlo nos damos cuenta que, si bien el test carga, no hay ningún error

.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

Esto se debe a que Python crea un constructor vacío para sus nuevas clases si estas no lo definen, así que, lamentablemente, tendremos que aceptar que el test no tuvo la oportunidad de “fallar”

Implementación

No hay cambios a la clase Stack

Tercera iteración

Ahora testearemos e implementaremos la inserción (“push”) y remoción (“pop”) de elementos del stack. Como en el test dependen una de la otra crearemos ambas pruebas simultaneamente, quizás no es muy estricto pero nos ahorra complicar innecesariamente el código.

Test

Crearemos un test que inserta algunos elementos y después los extrae, verificando que estén en el orden correcto. Además crearemos un segundo test que verifica que se lance un error o excepción si tratamos de quitar un elemento de un stack vacío.

Ejecutar estos tests generan dos errores, ambos apuntando a que aún no están definidos los métodos push y pop.

import unittest
from stack import Stack

# Tests para la clase Stack
class TestStack(unittest.TestCase):
    # Test para verificar el constructor del stack
    def test_stack_creation(self):
        s = Stack()

    # Test para verificar que la insercion y remocion
    # se hacen en el orden correcto
    def test_insert_and_remove_elements(self):
        s = Stack()
        s.push('a')
        s.push('b')
        s.push('c')

        self.assertEqual(s.pop(), 'c')
        self.assertEqual(s.pop(), 'b')
        self.assertEqual(s.pop(), 'a')

    # Test para verificar que la remocion de un stack
    # vacio gatilla una excepcion
    def test_exception_on_pop_empty_stack(self):
        s = Stack()
        self.assertRaises(Exception, s.pop)

if __name__ == '__main__':
    unittest.main()

Para probar la inserción y remoción de elementos en orden, usamos los métodos push para rellenar un stack para luego desapilarlos uno por uno, verificando que sean los que esperamos. En el segundo test inicializamos un stack vacío y tratamos de extraer un item, ante lo cual esperamos que la implementación arroje un error de tipo Exception, lo cual hace pasar el test.

Implementación

Para aprobar los tests, implementamos los métodos conflictivos

# Clase que implementa una lista LIFO
class Stack:
    # Constructor vacio
    def __init__(self):
        self.list = list()

    # Metodo que agrega un nuevo elemento al final de la lista
    def push(self, element):
        self.list.append(element)

    # Metodo que extrae el ultimo elemento de la lista y lo retorna
    def pop(self):
        try:
            return self.list.pop()
        except IndexError:
            raise Exception()

Si ejecutamos nuevamente los test estos aparecen OK

Cuarta iteración

Para terminar, implementamos un método size() que retorna el tamaño actual del stack sobre el que es llamado.

Test

Para esta funcionalidad definimos un único test, que verifica los cambios de tamaño en un stack a medida que insertamos y extraemos elementos.

import unittest
from stack import Stack

# Tests para la clase Stack
class TestStack(unittest.TestCase):
    # Test para verificar el constructor del stack
    def test_stack_creation(self):
        s = Stack()

    # Test para verificar que la insercion y remocion
    # se hacen en el orden correcto
    def test_insert_and_remove_elements(self):
        s = Stack()
        s.push('a')
        s.push('b')
        s.push('c')

        self.assertEqual(s.pop(), 'c')
        self.assertEqual(s.pop(), 'b')
        self.assertEqual(s.pop(), 'a')

    # Test para verificar que la remocion de un stack
    # vacio gatilla una excepcion
    def test_exception_on_pop_empty_stack(self):
        s = Stack()
        self.assertRaises(Exception, s.pop)

    # Test para verificar el tamano de un stack
    def test_stack_size(self):
        s = Stack()
        self.assertEqual(s.size(), 0)
        s.push('a')
        self.assertEqual(s.size(), 1)
        s.push('b')
        self.assertEqual(s.size(), 2)
        s.pop()
        self.assertEqual(s.size(), 1)

if __name__ == '__main__':
    unittest.main()

Como nos esperamos, el test falla porque el método size no está definido

Implementación

Para pasar este último test solo necesitamos definir el método size en nuestra clase

# Clase que implementa una lista LIFO
class Stack:
    # Constructor vacio
    def __init__(self):
        self.list = list()

    # Metodo que agrega un nuevo elemento al final de la lista
    def push(self, element):
        self.list.append(element)

    # Metodo que extrae el ultimo elemento de la lista y lo retorna
    def pop(self):
        try:
            return self.list.pop()
        except IndexError:
            raise Exception()

    # Metodo que retorna el tamano del stack
    def size(self):
        return len(self.list)
Filed Under: TDD Tagged With: tdd, testdriven, unit-test

Comments

  1. Max says:
    21/06/2012 at 3:00 pm

    Me gusta como vas desarrollando la idea todo muy ordenado, buen ejemplo gracias!.

Speak Your Mind Cancelar respuesta

*

*

Últimos artículos

  • Invitación a vivenciar Scrum
  • Pruebas parametrizadas en JUnit
  • Capacitación en SCRUM MANAGER
  • Mapa mental del trabajo colaborativo del AgileDayChile2012
  • Vuelve el podcast de ChileAgil: Entrevista Sobre LeanStartup en Subela radio

Últimos comentarios

  • Rafael Mendez en Sobre la Asociación de Emprendedores de Chile
  • Natalia en Sobre la Asociación de Emprendedores de Chile
  • Sergio López C. en Rescata un computin: Como renunciar
  • Talleres prácticos de iniciación a la Cultura Ágil y Kanban, y AgileDay en Concepción y el Norte en Workshop de Extreme Programming en Puerto Montt (Viernes 29 de abril, Universidad Austral)
  • Talleres prácticos de iniciación a la Cultura Ágil y Kanban, y AgileDay en Concepción y el Norte | LeanSight en Workshop de Extreme Programming en Puerto Montt (Viernes 29 de abril, Universidad Austral)

Suscríbete al Grupo

Grupos de Google
Aquí donde la comunidad ágil se reune a discutir ideas, dudas y buscar apoyo.

Correo electrónico:

Google Group

Fwd: [agileperu] Inscripciones abiertas Early Bird para Agiles 2013 y seguir en redes sociales
18 May 2013 - Roberto Moreno P.
Donde puedo encontrar mas informacion respecto a Story Mapping???
17 May 2013 - Fernando Molina P.
Meetup Bitcoin
17 May 2013 - Philippe Camacho
Fwd: Radiotón: un día de potenciar la radio con tecnología lo-fi
16 May 2013 - Agustin Villena
Charla Ágil- Jueves 16 de Mayo a las 16:50 con Agustín Villena :D
15 May 2013 - Hanna Back Pyo

Fail Fast

Tecnicas/ metodologias para 1 o 2 desarrolladores
23 April 2013 - dcarreroc
Material o Buenas prácticas sobre el concepto "Entendimiento Común"
22 April 2013 - rodrigo muñoz
TDD, clientes conocidos, experiencia y predicciones
17 April 2013 - matías toro ipinza
Ejemplo de proyecto de software que no haya utilizado un método ágil y tendió al fracaso
15 April 2013 - rocio
Como postular a Startup Chile
1 April 2013 - philippe.camacho

Twitter

ChileAgil: RT @javrusso: En taller de pensamiento visual y agilidad con el master @agustinvillena (@ FACOM Universidad Central de Chile) http://t.co/l…
18 May 2013
ChileAgil: RT @MadBrowser: @ChileAgil Why the lean entrepreneur cross the death valley? How the lean StartUp framework changes everything. http://t.co…
18 May 2013
MadBrowser: @ChileAgil Why the lean entrepreneur cross the death valley? How the lean StartUp framework changes everything. http://t.co/vUEVDiC7Pu
18 May 2013
ChileAgil: RT @IncubaUC: RT La comunidad de @ChileAgil presente en la charla "Ventas efectivas+Desarrollo ágil" de @100SantiagoCL http://t.co/IdcNLFNc…
17 May 2013
IncubaUC: RT La comunidad de @ChileAgil presente en la charla "Ventas efectivas+Desarrollo ágil" de @100SantiagoCL http://t.co/IdcNLFNcAs
16 May 2013
100SantiagoCL: La comunidad de @ChileAgil presente en la charla "Ventas efectivas+Desarrollo ágil" de @100SantiagoCL http://t.co/Wpa7CVnTka
16 May 2013
ChileAgil: RT @GOOD: 15 amazingly creative themed office spaces http://t.co/Kf8xogxLB2
16 May 2013
HannaIsBack: @mluc14 ya te metiste en http://t.co/1by5wm5Ng1?
15 May 2013
ChileAgil: RT @agustinvillena: #diccionarioagil Cardwall, visualización de compromisos en un flujo de trabajo. No es tablero kanban si no limita traba…
15 May 2013
ChileAgil: RT @agustinvillena: #diccionarioagil Tablero Kanban. Modelo visual de compromisos que representa un Sistema kanban, limitando cantidad de i…
15 May 2013

Return to top of page

©2012 ChileÁgil