Diseño ágil con TDD

3.1. Las historias de usuario

Una historia de usuario posee similitudes con un caso de uso, salvando ciertas distancias. Por hacer una correspondencia entre historias de usuario y casos de uso, podríamos decir que el título de la historia se corresponde con el del caso de uso tradicional. Sin embargo, la historia no pretende definir el requisito. Escribir una definición formal incurre en el peligro de la imprecisión y la malinterpretación, mientras que contarlo con ejemplos ilustrativos, transmite la idea sin complicaciones.

En ATDD cada historia de usuario contiene una lista de ejemplos que cuentan lo que el cliente quiere, con total claridad y ninguna ambigüedad. El enunciado de una historia es tan sólo una frase en lenguaje humano, de alrededor de cinco palabras, que resume qué es lo que hay que hacer.

Ejemplos de historias podrían ser:

  • Formulario de inscripción
  • Login en el sistema
  • Reservar una habitación
  • Añadir un libro al carrito de la compra
  • Pago con tarjeta de crédito
  • Anotar un día festivo en el canlendario
  • Informe de los artículos más vendidos
  • Darse de baja en el foro
  • Buscar casas de alquiler en Tenerife

Breves, concretas y algo estimables. Son el resultado de escuchar al cliente y ayudarle a resumir el requisito en una sola frase. Muy importante: Están escritas con el vocabulario del negocio del cliente, no con vocabulario técnico. Por sí misma una historia aislada es difícil de estimar incluso con este formato. Lo que las hace estimables y nos hace ser capaces de estimarlas cada vez mejor, es el proceso evolutivo que llamamos ágil. Esto es: a base de iterar, estimar en cada iteración y hacer restrospectiva al final de la misma, vamos refinando la habilidad de escribir historias y estimarlas.

Cada historia provoca una serie de preguntas acerca de los múltiples contextos en que se puede dar. Son las que naturalmente hacen los desarrolladores a los analistas de negocio o al cliente.

  • ¿Qué hace el sistema si el libro que se quiere añadir al carrito ya está dentro de él?
  • ¿Qué sucede si se ha agotado el libro en el almacén?
  • ¿Se le indica al usuario que el libro ha sido añadido al carrito de la compra?

Las respuestas a estas preguntas son afirmaciones, ejemplos, los cuales transformamos en tests de aceptación. Por tanto, cada historia de usuario tiene asociados uno o varios tests de aceptación (ejemplos):

  • Cuando el libro X se añade al carrito, el sistema devuelve un mensaje que dice: El libro X ha sido añadido al carrito
  • Al mostrar el contenido del carrito aparece el libro X
  • El libro X ya no aparece entre los libros a añadir al carrito

Cuantas menos palabras para decir lo mismo, mejor:

  • Añadir libro X en stock produce: El libro X ha sido añadido al carrito
  • Libro X está contenido en el carrito
  • Libro X ya no está en catálogo de libros

Las preguntas surgidas de una historia de usuario pueden incluso dar lugar a otras historias que pasan a engrosar el backlog o lista de requisitos: Si el libro no está en stock, se enviará un email al usuario cuando llegue.

Los tests de aceptación son así; afirmaciones en lenguaje humano que tanto el cliente, como los desarrolladores, como la máquina, entienden. ¿La máquina? ¿cómo puede entender eso la máquina? Mágicamente no. El equipo de desarrollo tiene que hacer el esfuerzo de conectar esas frases con los puntos de entrada y salida del código.

Para esto existen diversos frameworks libres y gratuitos que reducen el trabajo. Los más conocidos son FIT, Fitnesse, Concordion, Cucumber y Robot. Básicamente lo que proponen es escribir las frases con un formato determinado como por ejemplo HTML, usando etiquetas de una manera específica para delimitar qué partes de la frase son variables de entrada para el código y cuales son datos para validación del resultado de la ejecución.

Como salida, Concordion por ejemplo, produce un HTML modificado que marca en rojo las afirmaciones que no se cumplieron, además de mostrar las estadísticas generales sobre cuántos tests pasaron y cuántos no. Veamos un ejemplo de la sintaxis de Concordion:

<html xmlns:concordion="http://www.concordion.org/2007/concordion">
    <body>
        <p>
            El saludo para el usuario
            <span concordion:set="\#firstName">
                Manolo
            </span>
            será:
            <span concordion:assertEquals="greetingFor(\#firstName)">
                ¡Hola Manolo!
            </span>
        </p>
    </body>
</html>

Lógicamente, no le pedimos al cliente que aprenda la sintaxis de Concordion y escriba el código HTML. Le pedimos que nos ayude a definir la frase o que nos la valide y luego, entre analistas de negocio, desarrolladores y testers (equipo de calidad), se escribirá el HTML.

Lo interesante para el cliente es que el renderizado del HTML contiene el ejemplo que él entiende y es una bonita tarjeta que Concordion coloreará con ayuda de la hoja de estilos, subrayando en verde o en rojo según funcione el software.

Concordion sabe dónde buscar la función greetingsFor y reconoce que el argumento con que la invocará es Manolo. Comparará el resultado de la ejecución con la frase ¡Hola Manolo! y marcará el test como verde o rojo en función de ello.

Un test de cliente o de aceptación con estos frameworks, a nivel de código, es un enlace entre el ejemplo y el código fuente que lo implementa. El propio framework se encarga de hacer la pregunta de si las afirmaciones son ciertas o no. Por tanto, su aspecto dista mucho de un test unitario o de integración con un framework xUnit.

Para cada test de aceptación de una historia de usuario, habrá un conjunto de tests unitarios y de integración de grano más fino que se encargará, primero, de ayudar a diseñar el software y, segundo, de afirmar que funciona como sus creadores querían que funcionase. Por eso ATDD o STDD es el comienzo del ciclo iterativo a nivel desarrollo, porque partiendo de un test de aceptación vamos profundizando en la implementación con sucesivos test unitarios hasta darle forma al código que finalmente cumple con el criterio de aceptación definido.

No empezamos el diseño en torno a una supuesta interfaz gráfica de usuario ni con el diseño de unas tablas de base de datos, sino marcando unos criterios de aceptación que nos ayuden a ir desde el lado del negocio hasta el lado más técnico pero siempre concentrados en lo que el cliente demanda, ni más ni menos. Las ventajas son numerosas.

En primer lugar, no trabajaremos en funciones que finalmente no se van a usar. En segundo lugar, forjaremos un código que está listo para cambiar si fuera necesario porque su diseño no está limitado por un diseño de base de datos ni por una interfaz de usuario. Es más fácil hacer modificaciones cuando se ha diseñado así, de arriba a abajo, en vez de abajo a arriba.

Si el arquitecto diseña mal la estructura de un edificio, será muy complejo hacerle cambios en el futuro pero si pudiera ir montando la cocina y el salón sin necesidad de estructuras previas, para ir enseñándolas al cliente, seguro que al final colocaría la mejor de las estructuras para darle soporte a lo demás. En la construcción de viviendas eso no se puede hacer pero en el software, sí. Y, además, es lo natural, aunque estemos acostumbrados a lo contrario. ¡Porque una aplicación informática no es una casa!.

Dada esta metáfora, se po- dría interpretar que deberíamos partir de una interfaz gráfica de usuario para la implementación pero no es cierto. Ver el dibujo de una interfaz gráfica de usuario no es como ver una cocina. Primero, porque la interfaz gráfica puede o no ser intuitiva, utilizable y, a consecuencia de esto, en segundo lugar, no es el medio adecuado para expresar qué es lo que el cliente necesita sino que la interfaz de usuario es parte del cómo se usa.


Copyright (c) 2010-2013 Carlos Ble. La copia y redistribución de esta página se permite bajo los términos de la licencia Creative Commons Atribución SinDerivadas 3.0 Unported siempre que se conserve esta nota de copyright.