La primera versión de este simple auto a control remoto no tenía ningún test. Usándolo me di cuenta que no era muy cómodo manejarlo, ya que para moverlo hacia adelante había que apretar la tecla “arriba” y luego “espacio” para frenar. Sería mucho más “usable” si se frenara al soltar la tecla “arriba” (y lo mismo para las demás direcciones). A veces, una nueva necesidad puede ser la fuerza para introducir tests donde no los hay.

Para mejorar la usabilidad necesitamos dos commandos para cada acción (antes teníamos solo uno). Veamos el primer paso de TDD (rojo), que nos permite agregar el comando start:

describe('invoker', ()=> {

  describe('given invoker with up command', ()=> {
    var invoker;
    var upCommand;
    beforeEach(() => {
      upCommand = {
        start: () => {}
      };
      spyOn(upCommand, 'start');
      invoker = new Invoker({
        up: upCommand
      });
    });

    describe('when start up', ()=> {
      beforeEach(() => invoker.start('up'));
      it('then start up command is called', ()=> {
        expect(upCommand.start).toHaveBeenCalled();
      });
    });

  });
});

y el segundo paso (verde):

class Invoker {

  constructor(commands) {
    this.commands = commands;
  }

  start(commandName) {
    this.commands[commandName].start();
  }
}

module.exports = Invoker;

Y así podemos seguir, hasta encontrar una oportunidad de refactor y finalizar los escenarios que necesitamos:

Con esto logramos una clase Invoker, agnóstica de la tecnología (más adelante veremos porque esto es importante).

Lo siguiente será introducir esta clase en nuestro código, haciendo que keyboardInvoker use invoker:

Y agregar un test manual, para poder probar que funciona ok, sin necesidad de tener el bot andando / enchufado. Después encontré que me fue muy útil hacer esto, ya que no es fácil encontrar una librería que emule los eventos keyUp, keyDown en un programa de consola nodejs. Y el test manual me permitió fácilmente probar distintas formas de emular esos eventos.

Y que ganamos con esto?

Confianza, un punto de automatización y mejora continua (sobre esto podemos crecer en cobertura, métricas, feedback, etc.), entrenamiento (tiempo de práctica haciendo TDD) y posiblemente varias cosas más que se me escapan. De todas ellas me gustaría detenerme en las siguientes:

Estabilidad

Hay un principio de diseño conocido como The Stable Dependencies Principle, que dice “Depend in the direction of stability”. La estabilidad es la dificultad de cambiar una pieza: las piezas más estables son las más difíciles de cambiar. Hay varias fuerzas que pueden hacer que una pieza cambie, pero principalmente podemos clasificarlas en dos (tomado de (1) y (2), que son lo mismo):

  • necesidades (business)
  • tecnología

Las piezas basadas en soluciones probadas (patrones), serán más difíciles de cambiar (por haber sido probadas a lo largo del tiempo en diferentes contextos) y por lo tanto más estables. Lo mismo sucede con las piezas que son agnósticas de la tecnología y no dependen de por ejemplo de un framework, sino puramente del lenguaje de programación. Podemos encontrar un ejemplo de ambos casos en la clase Invoker, que aplica el patrón command y que es javascript puro. Entonces el camino de estabilidad queda algo así:

  • El cliente (main.js) depende de KeyboardInvoker
    Que depende de node-keyboard para acceder a los eventos del teclado y solo funciona en linux (no es agnóstico de la tecnología).
  • KeyboardInvoker depende de Invoker
    Que es agnóstico de la tecnología. No depende de detalles de implementación, es independiente (los detalles de implementación están encapsulados en cada command). A su vez otros invokers podrían depender de él, eso lo haría una pieza responsable y por lo tanto más difícil de cambiar y más estable aún.

Siempre me parecieron interesante las ideas de Bob Martin (autor del principio), me gusta la simplicidad con que define estabilidad y esta idea de como se propagan las propiedades estructurales (3) a través de las dependencias, tiene mucho sentido. Si nuestro código depende de algo poco estable, será poco estable también (recuerdo los primeros tiempos de las apps para facebook). Gracias Uncle Bob por compartir estas ideas!

Flexibilidad

Otra manera de interpretar el principio anterior sería viendo la otra cara de la misma moneda. Acá es donde la mirada oriental nos puede ayudar. La estabilidad es yin, ya que tiene que ver con la solidez (como la piedra). El lado yang sería la flexibilidad, que tiene que ver con la elasticidad (como el barro). La flexibilidad es la dificultad de romper una pieza: las piezas más flexibles son las más difíciles de romper. Acá también actúan las mismas fuerzas para romper una pieza: necesidades y tecnología. Y para propagar esta propiedad estructural, también podríamos pensar en “depender en la dirección de la flexibilidad”.

Las piezas con test manual serán más difíciles de romper que las piezas sin test y por lo tanto más flexibles. Las piezas con tests unitarios serán más difíciles de romper que las piezas con test manual. Y las piezas hechas con TDD serán más difíciles de romper que las piezas con test unitarios (no solo por su cobertura, sino también por su diseño evolutivo). Veamos como queda el camino de dependencias:

  • El cliente (main.js) depende de KeyboardInvoker (probado con un test manual)
  • KeyboardInvoker depende de Invoker (hecho con TDD)

Estabilidad y Flexibilidad = Adaptabilidad

De la interacción de las dos surge la adaptabilidad: capacidad de resistir a las fuerzas de cambio sin romper (flexibilidad) o sin ser afectados (estabilidad). Podemos encontrar una analogía similar en los huesos, donde su alta resistencia se debe a la combinación de calcio (dureza) y colágeno (elasticidad) (4).

Al incrementar estas mejoraremos posiblemente también otros aspectos de la calidad interna del código, como por ejemplo:

  • de difícil de re-usar (un pieza depende de detalles no deseables que dificultan su separación) -> más reusable (podríamos fácilmente re-usar Invoker en JoystickInvoker)
  • más robusto y confiable
  • más fácil de comunicar la intención de cada pieza

 

(1) Using the Agile Testing Quadrants

(2) Let’s break the Agile Testing Quadrants

(3) El término propiedades estructurales viene de Anthony Giddens y su teoría de estructuración que dice que los sistemas no tienen estructura, sino propiedades estructurales. La diferencia parece sutil, pero me gusta porque nos lleva a pensar que algo no “es”, sino que “está” y por lo tanto lo podemos cambiar 😉

(4) ¿Qué podemos aprender los huesos?

 

compartir...Tweet about this on TwitterShare on FacebookShare on Google+Email this to someone