Guía para principiantes: Testear en JavaScript funcional

Autor: Adriana
28 Junio, 2017
1096 Visitas

Programación y pruebas funcionales. Tal vez tu has dado una prueba en forma aislada, pero de alguna manera nunca hizo una parte de su práctica regular.

 

Puede sonar inocente por sí mismos, pero juntos pruebas y programación funcional puede crear una tentación irresistible, casi obligando a escribir un código más limpio, más apretado, más fácil de mantener.

Bueno, la buena noticia es que trabajar con ambas técnicas en conjunto puede ofrecer algunas ventajas reales. De hecho, una vez que has probado lo dulce que puede ser esta combinación, puede que te encuentres tan adicto a ella como yo, y estaría dispuesto apostar que volverás para más.

En este artículo, te presentaré los principios de probar JavaScript funcional. Le mostraré cómo ponerse en marcha con el framework Jasmine y crear una función pura usando un enfoque basado en pruebas.




¿POR QUE PROBAR?

La prueba consiste en asegurarse de que el código de la aplicación hace lo que usted espera que haga y sigue haciendo lo que espera que haga cuando realice cambios para que tenga un producto de trabajo cuando haya terminado. Escribes una prueba que define tu funcionalidad esperada bajo un conjunto definido de circunstancias, ejecuta esa prueba contra el código, y si el resultado no es lo que la prueba dice que debería ser, obtienes una advertencia. Y sigues recibiendo esa advertencia hasta que hayas arreglado tu código.

Entonces usted consigue la recompensa.

Y sí, te hará sentir bien.

La prueba viene en muchos sabores, y hay espacio para un sano debate sobre dónde se dibujan las fronteras, pero en pocas palabras:

  • Las pruebas unitarias validan la funcionalidad del código aislado
  • Las pruebas de integración verifican el flujo de datos y la interacción de los componente
  • Las pruebas funcionales examinan el comportamiento de la aplicación general

Nota: No te distraigas por el hecho de que hay un tipo de prueba llamada prueba funcional. No es en lo que nos centraremos en este artículo sobre la prueba de JavaScript funcional. De hecho, el enfoque que usará para las pruebas funcionales del comportamiento general de una aplicación probablemente no cambiará tanto si está utilizando técnicas de programación funcional en su JavaScript o no. Donde la programación funcional realmente ayuda a salir es cuando usted está construyendo sus pruebas de unidad.

Puedes escribir una prueba en cualquier momento del proceso de codificación, pero siempre he encontrado que es más eficiente escribir una prueba de unidad antes de escribir la función que estás planeando probar. Esta práctica, conocida como desarrollo controlado por pruebas (TDD), le anima a analizar la funcionalidad de su aplicación antes de comenzar a escribir y determinar qué resultados desea de cada sección de código, escribiendo primero la prueba y luego codificando para producir ese resultado.

Un beneficio secundario es que TDD a menudo le obliga a tener conversaciones detalladas con las personas que le están pagando para escribir sus programas, para asegurarse de que lo que está escribiendo es en realidad lo que están buscando. Después de todo, es fácil hacer un solo pase de prueba. Lo difícil es determinar qué hacer con todos los posibles insumos que vas a encontrar y manejar todos ellos correctamente sin romper las cosas.

¿Por que Funcional?

Como usted puede imaginarse, la manera que usted escribe su código tiene mucho hacer con cómo es fácil es probar. Hay algunos patrones de código, como el acoplamiento estrecho del comportamiento de una función a otra, o depender fuertemente de variables globales, que pueden hacer que el código sea mucho más difícil para la prueba unitaria. A veces puede que tenga que usar técnicas inconvenientes como “burlarse” del comportamiento de una base de datos externa o simular un entorno de ejecución complicado para establecer parámetros y resultados probables. Estas situaciones no siempre se pueden evitar, pero normalmente es posible aislar los lugares en el código donde se requieren para que el resto del código se puede probar más fácilmente.

La programación funcional le permite manejar los datos y el comportamiento en su aplicación de forma independiente. Crea su aplicación creando un conjunto de funciones independientes que trabajan de forma aislada y no dependen del estado externo. Como resultado, su código se vuelve casi auto-documentado, atando juntos pequeñas funciones claramente definidas que se comportan de manera coherente y comprensible.

La programación funcional contrasta a menudo con la programación imperativa y la programación orientada a objetos. JavaScript puede soportar todas estas técnicas e incluso combinarlas. La programación funcional puede ser una alternativa valiosa para crear secuencias de código imperativo que rastrea el estado de la aplicación a través de múltiples pasos hasta que se devuelve un resultado. O construir su aplicación de interacciones a través de objetos complejos que encapsulan todos los métodos que se aplican a una estructura de datos específica.

Funciones puras

La programación funcional le anima a construir su aplicación de minúsculas, reutilizables, funciones componibles que sólo hacer una cosa específica y devolver el mismo valor para la misma entrada cada vez. Una función como esta se llama una función pura. Las funciones puras son la base de la programación funcional, y todas comparten estas tres cualidades:

  • No confíe en el estado o las variables externas
  • No provocar efectos secundarios ni alterar las variables externas
  • Siempre devuelva el mismo resultado para la misma entrada

Otra ventaja de escribir código funcional es que hace mucho más fácil hacer pruebas de unidad. Cuanto más de su código que puede probar unidad, más cómodamente puede contar con su capacidad de refactorizar el código en el futuro sin romper la funcionalidad esencial.

¿Que hace que el código funcional sea fácil de probar?

Si usted piensa en los conceptos que acabamos de discutir, probablemente ya ve por qué el código funcional es más fácil de probar. Las pruebas de escritura para una función pura son triviales, porque cada entrada tiene una salida consistente. Todo lo que tienes que hacer es establecer las expectativas y ejecutarlos contra el código. No hay ningún contexto que deba establecerse, no hay dependencias inter-funcionales para realizar un seguimiento, no hay ningún estado cambiante fuera de la función que necesita ser simulada y no hay fuentes de datos externas variables que se burlen.




Hay un montón de opciones de pruebas por ahí que van desde los marcos de pleno derecho a las bibliotecas de utilidad y arneses de pruebas simples. Estos incluyen Jasmine, Mocha, Enzyme, Jest, y una multitud de otros. Cada uno tiene diferentes ventajas y desventajas, mejores casos de uso, y un seguimiento leal. Jasmine es un marco robusto que se puede utilizar en una amplia variedad de circunstancias, así que aquí está una demostración rápida de cómo puede utilizar Jasmine y TDD para desarrollar una función pura en el navegador.

Puede crear un documento HTML que extraiga la biblioteca de pruebas de Jasmine localmente o desde un CDN. Un ejemplo de una página que incluya la biblioteca de Jasmine y el corredor de prueba podría ser algo como esto:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Jasmine Test</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.6.1/jasmine.min.css">
  </head>
  <body>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.6.1/jasmine.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.6.1/jasmine-html.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.6.1/boot.min.js"></script>
  </body>
</html>

Esto trae en la biblioteca de Jasmine, junto con la secuencia de comandos de inicio y estilo de Jasmine HTML. En este caso, el cuerpo del documento está vacío, esperando su JavaScript para probar, y sus pruebas de Jasmine.

Prueba JavaScript Funcional — Nuestra Primera Prueba

Para comenzar, escribamos nuestra primera prueba. Podemos hacer esto en un documento aparte, o al incluirlo dentro de un elemento <script> en la página. Vamos a utilizar la función de descripción definida por la biblioteca de Jasmine para describir el comportamiento deseado para una nueva función que aún no hemos escrito.

La nueva función que vamos a escribir se llamará isPalindrome y devolverá true si la cadena pasada es la misma hacia adelante y hacia atrás, y devolverá false de lo contrario. La prueba se verá así:

describe("isPalindrome", () => {
  it("returns true if the string is a palindrome", () => {
    expect(isPalindrome("abba")).toEqual(true);
  });
});

Cuando añadimos esto a un script en nuestra página y lo cargamos en un navegador, obtenemos una página de informe de Jasmine que funciona que muestra un error. Que es lo que queremos en este punto. Queremos saber que la prueba se está ejecutando, y que está fallando. De esa manera nuestro cerebro con aprobación de la conciencia sabe que tenemos algo que arreglar.




Así que vamos a escribir una función sencilla en JavaScript, con la lógica suficiente para pasar nuestra prueba. En este caso, solo va a ser una función que hace que nuestra prueba pase devolviendo el valor esperado.

const isPalindrome = (str) => true;

Sí, en serio. Sé que parece ridículo, pero cuelgue allí conmigo.

Cuando el corredor de prueba de nuevo, pasa. Por supuesto. Pero, obviamente, este código simple no hace lo que podríamos esperar un palindrome probador para hacer. Hemos escrito la cantidad mínima de código que hace pasar la prueba. Pero sabemos que nuestro código no evaluaría eficazmente los palíndromos. Lo que necesitamos en este punto son expectativas adicionales. Así que vamos a añadir otra afirmación a nuestra función de describir:

describe("isPalindrome", () => {
  it("returns true if the string is a palindrome", () => {
    expect(isPalindrome("abba")).toEqual(true);
  });
  it("returns false if the string isn't a palindrome", () => {
    expect(isPalindrome("Bubba")).toEqual(false);
  });
});

Recargar nuestra página ahora hace que la salida de la prueba se vuelva roja y falla. Recibimos mensajes diciendo cuál es el problema y el resultado de la prueba se vuelve rojo.

¡Rojo!

Nuestros cerebros sienten que hay un problema.

Por supuesto que sí. Ahora nuestra sencilla función isPalindrome que sólo devuelve true cada vez se ha demostrado que no funciona de manera efectiva contra esta nueva prueba. Así que vamos a actualizar isPalindrome añadiendo la capacidad de comparar una cadena pasada hacia adelante y hacia atrás.

const isPalindrome = (str) => {
  return str
    .split("")
    .reverse()
    .join("") === str;
};

La prueba es adictiva

Verde de nuevo. Ahora eso es satisfactorio. ¿Recibiste esa pequeña corriente de dopamina cuando volviste a cargar la página?

Con estos cambios en su lugar, nuestra prueba pasa de nuevo. Nuestro nuevo código compara eficazmente la cadena hacia adelante y hacia atrás, y devuelve true cuando la cadena es la misma hacia adelante y hacia atrás, y false de lo contrario.

Este código es una función pura, ya que es sólo hacer una cosa, y hacerlo consistentemente dado un valor de entrada coherente sin crear ningún efecto secundario, haciendo cambios a las variables fuera de sí mismo, o depender del estado de la aplicación. Cada vez que pasa esta función una cadena, hace una comparación entre la cadena hacia adelante y hacia atrás, y devuelve el resultado independientemente de cuándo o cómo se llama.

Usted puede ver lo fácil que ese tipo de consistencia hace que esta función a la unidad de prueba. De hecho, la escritura de prueba de código impulsado puede animar a escribir funciones puras, ya que son mucho más fáciles de probar y modificar.

Y usted quiere la satisfacción de una prueba pasajera. Usted sabe que sí.

Refactorizacion de una función pura

En este punto, es trivial agregar funcionalidad adicional, como manipular la entrada sin cadena, ignorar las diferencias entre las letras mayúsculas y minúsculas, etc. Simplemente pregunte al propietario del producto cómo quieren que se comporte el programa. Puesto que ya tenemos pruebas en su lugar para verificar que las cadenas serán manejadas de forma consistente, ahora podemos agregar la comprobación de errores o la coerción de cadena o cualquier comportamiento que nos guste para los valores que no son de cadena.

Por ejemplo, veamos qué sucede si agregamos una prueba para un número como 1001 que podría interpretarse un palíndromo si fuera una cadena:

describe("isPalindrome", () => {
  it("returns true if the string is a palindrome", () => {
    expect(isPalindrome("abba")).toEqual(true);
  });
  it("returns false if the string isn't a palindrome", () => {
    expect(isPalindrome("Bubba")).toEqual(false);
  });
  it("returns true if a number is a palindrome", () => {
    expect(isPalindrome(1001)).toEqual(true);
  });
});

Hacer esto nos da una pantalla roja y una prueba de fallo de nuevo, porque nuestra actual es isPalindrome función no sabe cómo hacer frente a las entradas no-cadena.

El pánico se pone. Vemos rojo. La prueba está fallando.

Pero ahora podemos actualizarlo de forma segura para manejar entradas que no sean de cadena, forzarlas en cadenas y comprobarlas de esa manera. Podríamos llegar a una función que se ve un poco más como esto:

const isPalindrome = (str) => {
  return str
    .toString()
    .split("")
    .reverse()
    .join("") === str.toString();
};

Y ahora todas nuestras pruebas pasan, estamos viendo verde, y esa dopamina dulce y dulce está inundando nuestros cerebros de prueba.

Al agregar toString () a la cadena de evaluación, podemos acomodar entradas que no son de cadena y convertirlas en cadenas antes de realizar las pruebas. Y lo mejor de todo, debido a que nuestras otras pruebas todavía se están ejecutando cada vez, podemos estar seguros de que no hemos roto la funcionalidad que tenemos antes añadiendo esta nueva capacidad a nuestra función pura. Esto es lo que terminamos

const isPalindrome = (str) => {
  return str
    .toString()
    .split("")
    .reverse()
    .join("") === str.toString();
};