Por poner el broche de oro a esta mini-serie de artículos sobre TDD, vamos a ver un caso práctico con algo muy sencillo que todos conocéis: una pila. Por si queda algún despistado o despistada, una pila es una lista ordenada que permite almacenar y recuperar valores. El acceso a estos datos sigue el patrón LIFO (del inglés Last In, First Out, "el último en entrar es el primero en salir"). Básicamente, podéis pensar en la típica pila de platos, en la que el último plato que se pone encima, será el primero que quitemos.
Para este caso, se usará JavaScript estándar, haciendo uso de clases. De todas formas, si alguien tiene dudas sobre algo del lenguaje, podéis preguntarme en comentarios que responderé con todo gusto.
Tabla de Contenidos
Primer test. Crear una instancia.
Como estamos usando TDD, lo primero de todo será definir el test a probar:
De aquí en adelante, ya no os pondré la salida de error y de passed por consola 😉.
Segundo test. Null al quitar un elemento de la pila vacía.
En este segundo test, vamos a verificar que si ejecutamos la operación de quitar un elemento de la pila, y esta se encuentra vacía, se devuelve el valor null.
test("should return null when pop an empty stack", () => {conststack=newStack();expect(stack.pop()).toBeNull();});
Para hacer que el test pase, como tenemos que escribir el código mínimo, simplemente crearemos un método pop() que devolverá null al ejecutarlo.
classStack {pop() {returnnull; }}
Tercer test. Añadir un valor y eliminarlo.
Ahora tenemos que probar que si añadimos un valor y lo eliminamos se obtiene ese mismo valor.
test("should add value in the top of the stack and return this value when do a pop", () => {conststack=newStack();stack.push("hello");expect(stack.pop()).toBe("hello");});
Ahora el código de la clase debería seguir haciendo lo mínimo posible, pero vamos a ir un paso más allá (para no eternizar el ejemplo), y nos vamos a la fase de refactor, tras la que tendríamos:
Cuarto test. Devolver null si no quedan elementos por quitar
test("should return null after pop the last element", () => {conststack=newStack();stack.push("value");stack.pop();expect(stack.pop()).toBeNull();});classStack {#lastValue=null;push(value) {this.#lastValue=value; }pop() {constlastValue=this.#lastValue;this.#lastValue=null;returnlastValue; }}
Obsérvese que como aún no hemos gestionado más de un objeto en la cola, no estamos controlando que haya varios, es por ello que en este paso, asignaremos a null el valor, en cuanto llamemos a pop(). Y esto es por la regla de escribir el mínimo código posible.
Quinto test. Añadir y eliminar varios valores a la pila
test("should add several values to the top and remove then until arrive to null", () => {conststack=newStack();stack.push("value1");stack.push("value2");stack.push("value3");expect(stack.pop()).toBe("value3");expect(stack.pop()).toBe("value2");expect(stack.pop()).toBe("value1");expect(stack.pop()).toBeNull();});
Es necesario hacer una aclaración en esta parte, ya que si en el test, sólo hubiésemos añadido la última comprobación, omitiendo el resto de expects, no se hubiera podido verificar la pila nos devuelve cada valor eliminado, y según el código que ya teníamos, el test pasaría. Es por ello, que tenemos que dar mucha importancia a qué se quiere probar, porque un test que no esté bien escrito, es como si no existiera. En estos casos también es importante tener en cuenta, que si escribimos un test, y pasa sin tocar nada de código, puede que no estemos probando lo que queremos.
Tras un refactor, en este caso tendremos lo siguiente:
Se ha incluido una nueva clase, y en este caso, no hemos añadido tests para dicha clase, ya que es un getter/setter. Añadir tests en estos casos no tendría mucho sentido. Sin embargo, si hubiésemos necesitado una clase más compleja. Si que en este momento pausaríamos el test actual, para comenzar el desarrollo de la nueva clase con TDD. Una vez que hiciera todo lo que necesitamos, volveríamos a este test.
Sexto test. Devolver longitud 0 cuando la pila está vacía.
test("should return zero when stack is empty", () => {conststack=newStack();expect(stack.Length).toBe(0);});classStack {#lastElement=null;getLength() {return0; }// El resto del código no tiene cambios}
Séptimo test. Obtener el número de elementos añadidos.
test("should return the number of elements in stack after add some values", () => {conststack=newStack();stack.push(1);stack.push(2);stack.push(3);expect(stack.Length).toBe(3);});classStack {#lastElement=null;#length=0;getLength() {returnthis.#length; }push(value) {this.#lastElement=newStackElement(value, this.#lastElement);++this.#length; }// El resto del código no tiene cambios}
En este punto, como el test sólo ha probado el añadir elementos, no tenemos que tocar el resto del código, sólo la parte de añadir. Aunque sea cansino, recordad que vamos a mínimos en cada paso.
Último test. Obtener el número de elementos tras añadir y eliminar varios elementos.
test("should return the number of elements in stack after add and remove some values", () => {conststack=newStack();stack.push(1);stack.push(2);stack.pop();stack.push(3);stack.pop();expect(stack.Length).toBe(1);});classStack {#lastElement=null;#length=0;getLength() {returnthis.#length; }push(value) {this.#lastElement=newStackElement(value, this.#lastElement);++this.#length; }pop() {if (!this.#lastElement) {returnnull; }constlastValue=this.#lastElement.Value;this.#lastElement=this.#lastElement.PreviousElement;--this.#length;returnlastValue; }}
Conclusiones.
Después de haber visto un caso de uso de TDD, creo que podemos concluir que esta técnica es una herramienta valiosa para garantizar la calidad del software y mejorar la eficiencia del proceso de desarrollo. Al escribir las pruebas antes de escribir el código, se obliga a pensar cuidadosamente en los requisitos del software y en cómo se comportará en diferentes situaciones. El enfoque incremental de TDD también significa que el software se desarrolla en pequeños pasos, lo que facilita la detección de errores y la resolución de problemas antes de que se conviertan en problemas mayores.
¿Os ha parecido interesante? ¿tenéis dudas o sugerencias? Pues ya sabéis, podéis dejar un mensajito en la caja de comentarios.
Por poner el broche de oro a esta mini-serie de artículos sobre TDD, vamos a ver un caso práctico con algo muy sencillo que todos conocéis: una pila. Por si queda algún despistado o despistada, una pila es una lista ordenada que permite almacenar y recuperar valores. El acceso a estos datos sigue el patrón LIFO (del inglés Last In, First Out, "el último en entrar es el primero en salir"). Básicamente, podéis pensar en la típica pila de platos, en la que el último plato que se pone encima, será el primero que quitemos.
Para este caso, se usará JavaScript estándar, haciendo uso de clases. De todas formas, si alguien tiene dudas sobre algo del lenguaje, podéis preguntarme en comentarios que responderé con todo gusto.
Tabla de Contenidos
Primer test. Crear una instancia.
Como estamos usando TDD, lo primero de todo será definir el test a probar:
Si ahora ejecutamos el test, fallará como cabe espera:
Tenemos que escribir el código mínimo para que el test pase, así que, crearemos la clase:
Y tendremos nuestro test pasado:
De aquí en adelante, ya no os pondré la salida de error y de passed por consola 😉.
Segundo test. Null al quitar un elemento de la pila vacía.
En este segundo test, vamos a verificar que si ejecutamos la operación de quitar un elemento de la pila, y esta se encuentra vacía, se devuelve el valor
null
.Para hacer que el test pase, como tenemos que escribir el código mínimo, simplemente crearemos un método
pop()
que devolveránull
al ejecutarlo.Tercer test. Añadir un valor y eliminarlo.
Ahora tenemos que probar que si añadimos un valor y lo eliminamos se obtiene ese mismo valor.
Ahora el código de la clase debería seguir haciendo lo mínimo posible, pero vamos a ir un paso más allá (para no eternizar el ejemplo), y nos vamos a la fase de refactor, tras la que tendríamos:
Cuarto test. Devolver null si no quedan elementos por quitar
Obsérvese que como aún no hemos gestionado más de un objeto en la cola, no estamos controlando que haya varios, es por ello que en este paso, asignaremos a
null
el valor, en cuanto llamemos apop()
. Y esto es por la regla de escribir el mínimo código posible.Quinto test. Añadir y eliminar varios valores a la pila
Es necesario hacer una aclaración en esta parte, ya que si en el test, sólo hubiésemos añadido la última comprobación, omitiendo el resto de
expects
, no se hubiera podido verificar la pila nos devuelve cada valor eliminado, y según el código que ya teníamos, el test pasaría. Es por ello, que tenemos que dar mucha importancia a qué se quiere probar, porque un test que no esté bien escrito, es como si no existiera. En estos casos también es importante tener en cuenta, que si escribimos un test, y pasa sin tocar nada de código, puede que no estemos probando lo que queremos.Tras un refactor, en este caso tendremos lo siguiente:
Se ha incluido una nueva clase, y en este caso, no hemos añadido tests para dicha clase, ya que es un getter/setter. Añadir tests en estos casos no tendría mucho sentido. Sin embargo, si hubiésemos necesitado una clase más compleja. Si que en este momento pausaríamos el test actual, para comenzar el desarrollo de la nueva clase con TDD. Una vez que hiciera todo lo que necesitamos, volveríamos a este test.
Sexto test. Devolver longitud 0 cuando la pila está vacía.
Séptimo test. Obtener el número de elementos añadidos.
En este punto, como el test sólo ha probado el añadir elementos, no tenemos que tocar el resto del código, sólo la parte de añadir. Aunque sea cansino, recordad que vamos a mínimos en cada paso.
Último test. Obtener el número de elementos tras añadir y eliminar varios elementos.
Conclusiones.
Después de haber visto un caso de uso de TDD, creo que podemos concluir que esta técnica es una herramienta valiosa para garantizar la calidad del software y mejorar la eficiencia del proceso de desarrollo. Al escribir las pruebas antes de escribir el código, se obliga a pensar cuidadosamente en los requisitos del software y en cómo se comportará en diferentes situaciones. El enfoque incremental de TDD también significa que el software se desarrolla en pequeños pasos, lo que facilita la detección de errores y la resolución de problemas antes de que se conviertan en problemas mayores.
¿Os ha parecido interesante? ¿tenéis dudas o sugerencias? Pues ya sabéis, podéis dejar un mensajito en la caja de comentarios.
jose