Diseño ágil con TDD

8.3. Comenzando la implementación

Abrimos un nuevo proyecto con nuestro IDE favorito por un lado y un editor de textos sencillo por otro. Le recomiendo encarecidamente que lo haga tal cual, literalmente, que vaya escribiendo todos los fragmentos de código fuente que se exponen mientras lee; así irá practicando desde ya.

Antes de copiar directamente del libro intente pensar por usted mismo cuál es el siguiente paso a dar en cada momento. Este será su primer proyecto TDD si lo tiene a bien. Sucede que las decisiones de diseño las estoy tomando yo y pueden diferir de las que usted tome, lo cual no significa que las suyas sean inapropiadas, sino simplemente diferentes. No se desanime por ello.

La limitación de un libro impreso es su ausencia de interactividad y no podemos hacer nada para solventar este inconveniente. Si cree que una determinada refactorización o decisión de diseño es inapropiada no dude en compartirlo mediante la web del libro.

A la pantalla del editor de texto le vamos a llamar libreta y es donde apuntaremos los ejemplos que nos quedan por hacer y todo lo que se nos ocurre mientras estamos escribiendo tests o lógica de negocio: normalmente cuando estamos concentrados en el test que nos ocupa, es común que vengan a la mente casos de uso que no estaban contemplados o bien dudas sobre la lógica de negocio.

Esto no debe distraernos de lo que estamos haciendo ni tampoco influenciarnos, por eso lo anotamos en la libreta con una frase breve que nos permita volver a retomarlo cuando acabemos. Tenemos que centrarnos en una sola cosa cada vez para llevarla a cabo como es debido. Abrimos también la herramienta con que se ejecuta la batería de tests (NUnit gráfico o una consola donde invocar a nunit-console o el script Python que inicia unittest).

Típicamente creamos dos módulos, uno para la aplicación y otro para su batería de tests. Si estamos con C# serán dos DLLs y si estamos con Python serán dos paquetes distintos. Para no mezclar los dos lenguajes continuamente vamos a escribir primero en C# y luego veremos su contrapartida en Python

Los seguidores de uno de los lenguajes no deben saltarse los capítulos en los que se usa el otro porque en ambos capítulos existen explicaciones válidas para los dos lenguajes y referencias de uno a otro.

Ahora toca escoger uno de los tests de aceptación y pensar en una lista de elementos que nos hacen falta para llevarlo a cabo. Hacer un pequeño análisis del ejemplo y tratar de definir al menos tres o cuatro ejemplos más sencillos que podamos convertir en tests de desarrollo (unitarios y de integración o sólo unitarios) para empezar a hacer TDD.

Estamos combinando ATDD con TDD. Los dos elementos que se me ocurren ahora mismo son una clase que sea capaz de operar enteros y un analizador que sea capaz de identificar los distintos elementos de una expresión contenida en una cadena de texto. Si no tuviera que escribir este capítulo con fines docentes seguramente empezaría por trabajar en el analizador pero los ejemplos de la clase que hace cálculos van a ser más fáciles de asimilar inicialmente.

Generalmente los tests de aceptación se guardan por separado, al margen de la libreta que contiene las cuestiones relativas a desarrollo. Si empleamos un framework tipo Fit o Concordion los tests de aceptación tendrían su propio lugar pero por simplificar y de nuevo confines docentes, mantendremos ambas cosas en la libreta. Vamos a agregarle los tests unitarios que nos gustaría hacer para resolver el primero de los tests de aceptación.

# Aceptación - "2 + 2", devuelve 4
* Sumar 2 al número 2, devuelve 4
* Restar 3 al número 5, devuelve 2
* La cadena "2 + 2" tiene dos números y un operador que son '2', '2' y '+''
# Aceptación - "5 + 4*2 / 2", devuelve 9
# Aceptación - "3 / 2", produce ERROR
# Aceptación - "* *4 - 2": produce ERROR
# Aceptación - "*4 5 - 2": produce ERROR
# Aceptación - "*4 5 - 2 : produce ERROR
# Aceptación - "*45-2-": produce ERROR

Vamos a por el primer test de desarrollo:

using System;
using System.Collections.Generic;
using System.Text;
using NUnit.Framework;
using SuperCalculator;

namespace UnitTests
{
    [TestFixture]
    public class CalculatorTests
    {
        [Test]
        public void Add()
        {
            Calculator calculator = new Calculator();
            intresult = calculator.Add(2, 2);
            Assert.AreEqual(4, result);
        }
    }
}

El código no compila porque todavía no hemos creado la claseCalculator. Sin embargo, ya hemos diseñado algo, casi sin darnos cuenta: hemos pensado en el nombre de la clase, en cómo es su constructor y en cómo es su primer método (cómo se llama, los parámetros que recibe y el resultado que devuelve). Estamos diseñando la API tal cual la necesitamos, sin limitaciones ni funcionalidad extra.

A continuación escribimos el mínimo código posible para que el test pase. Intente imaginar cuál es literalmente el mínimo código posible para que el test pase. Seguro que es más largo que el que de verdad es mínimo:

namespace SuperCalculator
{
    public class Calculator
    {
        public int Add(int arg1,int arg2)
        {
            return 4;
        }
    }
}

¡Qué código más simplón!, ¡No sirve para nada!. Pues sí, sí que sirve. Al seguir el algoritmo TDD y escribir literalmente el mínimo código posible para que el test pase, descubrimos una cualidad de la función: para valores de entrada diferentes presenta resultados diferentes. Vale vale, todos sabíamos eso, en este caso es muy evidente pero ¿y si no tuviéramos un conocimiento tan preciso del dominio?.

Cuando no tenemos experiencia con TDD es muy importante que sigamos el algoritmo al pie de la letra porque de lo contrario no llegaremos a exprimir al máximo la técnica como herramienta de diseño. Ya que el test pasa, es decir, luz verde, necesitamos otro test que nos obligue a terminar de implementar la funcionalidad deseada, puesto que hasta ahora nuestra función de suma sólo funciona en el caso 2 + 2.

Es como si estuviésemos moldeando una figura con un cincel. Cada test es un golpe que moldea el SUT. A este proceso de definir el SUT a golpe de test se le llama triangulación.

[Test]
public void AddWithDifferentArguments()
{
    Calculator calculator = new Calculator();
    int result = calculator.Add(2, 5);
    Assert.AreEqual(7, result);
}

Ejecutamos, observamos que estamos en rojo y acto seguido modificamos el SUT:

public int Add(int arg1, int arg2)
{
    return arg1 + arg2;
}

¿Por qué no hemos devuelto 7 directamente que es el código mínimo? Porque entonces el test anterior deja de funcionar y se trata de escribir el código mínimo para que todos los tests tengan resultado positivo.

Una vez hemos conseguido luz verde, hay que pensar si existe algún bloque de código susceptible de refactorización. La manera más directa de identificarlo es buscando código duplicado. En el SUT no hay nada duplicado pero en los tests sí: La llamada al constructor de Calculator, (véase línea 4 del último test). Advertimos que la instancia de la calculadora es un fixture y por lo tanto puede ser extraída como variable de instancia de la clase CalculatorTests. Eliminamos la duplicidad:

public classCalculatorTests
{
    private Calculator _calculator;

    [SetUp]
    public void SetUp()
    {
        _calculator = new Calculator();
    }

    [Test]
    public voidAdd()
    {
        int result = _calculator.Add(2, 2);
        Assert.AreEqual(result, 4);
    }

    [Test]
    public void AddWithDifferentArguments()
    {
        int result = _calculator.Add(2, 5);
        Assert.AreEqual(result, 7);
    }
}

Nota Usar el SetUp no siempre es la opción correcta. Si cada uno de los tests necesitase de instancias de la calculadora distintas para funcionar (por ejemplo haciendo llamadas a diferentes versiones de un constructor sobrecargado), sería conveniente crearlas en cada uno de ellos en lugar de en la inicialización.

Algunos como James Newkirk son tajantes en lo que respecta al uso del SetUp y dice que si por él fuera eliminaría esta funcionalidad de NUnit. El color de este libro no es blanco ni negro sino una escala de grises: haga usted lo que su experiencia le diga que es más conveniente.


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.