19. - Más rápido que el navegador real
INCONVENIENTES
- Dificultad para observar cambios
dinámicos en el DOM
+ No se pueden tomar capturas
VENTAJAS
DOM VIRTUAL
26. - No requiere apenas esfuerzo, como
habéis podido observar. Y tienes
INCONVENIENTES
- No se pueden definir qué partes de
la snapshot quieres testear
+ No se puede hacer TDD
VENTAJAS
DOM VIRTUAL. SNAPSHOTS
34. CONCLUSIONES
34
Mucho esfuerzo, pero hay recompensa.
Completar con test unitarios y de integración..
Formas de acceder al DOM:
DOM ficticio (con plantillas o con snapshots).
DOM real.
Hola a todos
Antes de nada, me gustaría dar las gracias a la organización de CanariasJsDay por haber creído que mi propuesta merecía formar parte de este evento
Y por supuesto a todos vosotros por venir a estas horas tan tempranas un domingo
El título de mi charla es Test con Dom para Dommies, así que, por si no había quedado claro el chiste, voy a confirmar que, efectivamente es para dummies como yo
¿Os habéis encontrado alguna vez en la situación de querer acceder a datos del DOM para tus test y no saber muy bien cómo hacerlo?
A mí sí me pasó. Yo quería comprobar que después de un click había cambiado algo y no sabía ni por dónde empezar. Afortunadamente, estando ya en Kairós, como tenía cerquita a Carlos Blé, poco a poco fui viendo la luz.
Hay información en internet acerca de esto, pero como formas de hacer test hay tantas como programadores, éstas están desperdigadas y a veces te lían más que ayudarte.
Por eso he querido unir en un mismo documento las principales formas de hacer test contra el DOM para que en cierto modo te ayude a ti y a mí, doomies de la vida y de la informática, a elegir una u otra manera según la situación en la que nos encontremos. Si no eres doomie, no pasa nada, intentaré que lo disfrutes igualmente.
En primer lugar vamos a reflexionar un poco sobre por qué es tan complicado hacer test con DOM.
Y continuaremos exponiendo varias maneras de realizar los test, incluyendo algunos ejemplos de cada caso
Antes de comenzar con los problemas de testear del DOM, vamos a recordar qué es el DOM.
El DOM , de manera simplificada, sería la estructura abstracta que genera el navegador cuando interpreta un documento de HTML o XML. Según las siglas, DOM significa Document Object Model, es decir, es un modelo que representa como objetos los elementos de un documento HTML y la forma en que lo representa es como un árbol de nodos. Lo especial del DOM es que esa estructura abstracta nos va a permitir manipular nuestro HTML, ver su contenido, modificarlo, interactuar con él, etc. Y esto es lo que hacemos con Javascript.
Entonces por qué es tan difícil testear contra el DOM
Bueno, esto puede ser por varias razones. En resumen todas tienen en común que hay que tener en cuenta otros factores ajenos a la propia acción contra el DOM, por ejemplo, hay que tener en cuenta estos tres factores:
La posible asincronía que pueda existir con los datos.
Es decir, puede que necesite esperar a que se haga cierta petición a una API para traer ciertos datos antes de comprobar si éstos se visualizan correctamente en mi UI.
LA FUNCIÓN IMPOSIBLE. Título de peli de terror.
Porque efectivamente, puede que tengas que lidiar con funciones imposibles. Y éstas cuáles son?
Muchas veces a la vez que manipulamos el DOM, en la misma función, le adjudicamos otras responsabilidades que no tienen nada que ver con la visualización o la UI. Este sería el caso por ejemplo de cuando con un click se desencadena una función anónima que me devuelve los datos del formulariio y esos datos a su vez se los manda a la base de datos y además me oculta un contenedor y me muestra otro. También nos puede dar los buenos días, las buenas tardes y las buenas noches y entonces es cuando gritamos desesperadamente: ¡Noooooooo!
En estos casos, el primer paso para poder realizar test contra el DOM es tener las funciones lo más desacopladitas posibles y que todo lo que se dedique a pintar, modificar o leer el DOM no sepa de otras cosas que no sean eso.
Y el factor que más echa para atrás a las empresas y desarrolladores a la hora de ponerse a hacer test de las interfaces de usuario es éste:
Porque supone mucho esfuerzo contemplar todos los escenarios posibles en los que el usuario puede interactuar con la interfaz, unido a los distintos dispositivos, navegadores, etc. a través de los cuales puede acceder a nuestra aplicación: móvil, tablet, etc
A lo mejor te preguntas, ¿y por qué debería tomarme ese esfuerzo sólo para ver cómo se ve mi UI?
Bueno, a lo mejor os suena esta frase…
¿A alguien le suena?
Bueno, a lo mejor os suena de los Simpson, que aparecía en un capítulo…
Pero no se sólo eso… además, forma parte ya del anecdotario de cagadas de aplicaciones web.
Este verano, cientos de clientes de un conocido banco de Argentina recibieron una notificación con este mensaje.
Obviamente se trataba de un error, pero creó gran revuelo en las redes sociales y rápidamente muchos clientes se quejaron de la situación y no parecían muy contentos.
Así que, si te parece que una comprobación de qué texto es el que contiene tu elemento del DOM no merece la pena, a lo mejor este ejemplo puede animarte a empezar a planteártelo, no?
De lo que se trata aquí es de tener una cobertura de test que roce el 100% de tu aplicación, es decir, que ésta esté lo más testeada posible, sobre todo para tener la garantía de que tu interfaz se va a comportar exactamente como tú quieres, y de que no habrá sorpresas ( o al menos habrá menos), cuando subas tu código a producción.
Si ese es tu caso, si quieres saber qué opciones tienes para testear el DOM, acompáñame en el siguiente punto ( ** joke **)
Acompáñame un domingo más.
Un domingo con charlas geniales que ayudan a testear
Entonces vamos al tema, qué formas hay de testear contra el DOM
Antes de mencionar los tipos que se han seleccionado para esta charla, hay que destacar que hay muchas, pero muchas maneras de realizar test, casi tantas como desarrolladores y desde esta variedad han surgido muchos frameworks y librerías para hacerlos. Aquí vamos a ver tres formas de testear el DOM escogiendo para cada forma un framework o librería
Por un lado tenemos los test en los que se genera un DOM virtual, en este sentido vamos a dividir los test con DOM virtual en dos:
Un test generado a partir de la carga de una plantilla de HTML. Para éste tipo usaremos la herramienta de nodejs File System para cargar la plantilla y jsdom de jest para observar los cambios..
Y el otro tipo de test que usa el DOM virtual es el que se realiza a través de snapshots o instantáneas, a través de las cuales vamos a comprobar que nada cambie. También veremos un ejemplo con Jest
Esto con respecto al DOM virtual, pero por otro lado tenemos los tests en los que trabajamos sobre el DOM real, a través de un end2end que va a ejecutar los pasos que le digamos y nos va mostrar el resultado esperado en una imagen, una captura o un video. Para este tipo usaremos Cypress
Vamos a empezar por el más sencillo de los tipos, el DOM Virtual
¿Y qué es esto, Aida? ¿Una atracción de feria de realidad aumentada?
Pues no. No es más que una simulación del DOM como el que encontrarías en el navegador, pero que es “de mentira”.
Esta es una de las maneras que tenemos de trabajar con el DOM en nuestros test.
¿Cómo se hace? Con JSDOM. Éste es una implementación de Javascript que te genera un entorno simulado con el que trabajar con el DOM, como si fuera un navegador, y así permitirnos realizar test de nuestras aplicaciones.
Para el ejemplo que vamos a ver, vamos a usar Jest, que es una librería para realizar test y que viene ya con JSDOM integrado.
El JSDOM se creó para ser usado junto con NodeJs y juntos pueden hacer maravillas
En concreto, hay un módulo de NodeJs que resulta especialmente útil para usar en este tipo de test y es el fs o File System. Vamos a ver un ejemplo
File System es un módulo de NodeJs que nos permite trabajar con nuestro sistema de archivos del ordenador. Se puede usar para varias cosas, como leer, crear, actualizar o eliminar archivos.
¿Y qué tiene que ver eso con los tests? Te preguntarás... Bueno así de primeras… NADA
Pero… Como hemos mencionado ya, vamos a trabajar aquí con JSDOM, un DOM virtual, y para generar este entorno virtual sobre el que hacer pruebas, o bien vamos generando uno a uno nuestros divs, creándolos a mano, o bien usamos esta herramienta, que lo que haría es cargarnos nuestro archivo index.html y éste se lo daríamos como referencia a JSDOM para que genere nuestro entorno.
¿Cómo se hace esto?
Pues lo primero que vamos a hacer es importarlo, junto con otro módulo de nodejs, path, que nos ayuda a trabajar con rutas y archivos.
Después crearemos una función que va a recibir dos parámetros, la ruta de nuestro index.html y la función que va a cargar los datos obtenidos de la misma y que es la que usaremos para decirle a jsdom qué datos usar..
Ahora es cuando vamos a usar el método readFile de file system que va a cargar los datos en el callback que hemos definido como parámetro, si todo va bien, o nos devolverá un error si ha habido problemas con la carga.
Hasta aquí todo bien.
Ahora vamos a aplicarlo
Venga, vamos a comenzar a describir nuestro entorno de los test
Vamos a decirle que para cada caso me use la función que definimos antes y ahora es cuando le digo que me cargue index.html y que esos datos pasen a ser document.body.innerHTML (Que es ya el entorno de jsdom).
Mmmm pero pensad una cosa, estamos cargando un archivo… Cómo podemos asegurarnos de que ese archivo está cargado y disponible antes de ejecutar nuestros test? ¿Cómo manejamos esa especie de asincronía?
Pues muy sencillo, casi todas las librerías para test y en concreto Jest, que es la que nos ocupa, vienen con una herramienta para manejar la asincronía y es “done”.
Done es un callback y lo usamos de la siguiente manera, lo metemos como parámetro y cuando ya se haya cargado nuestro archivo y hayamos hecho lo que queremos hacer antes de ejecutar ningún test, como por ejemplo, inicializar la aplicación, entonces llamamos a “done” y le decimos, ya está hecho, ahora puedes continuar con los test, máquina!
Ahora ya tengo mi entorno del DOM cargado como si de un navegador se tratase y sobre el cual voy a ir observando los cambios.
Y aquí tenemos en verde nuestros tests. Son un poco tontos, pero sobre todo es para que se vea lo mucho que nos ha simplificado el hecho de tener nuestra plantilla de HTML cargada en el jsdom, ya que no hemos tenido que crear ningún elemento a mano.
En el primer test simplemente accedemos a nuestro elemento por el id y comprobamos que existe ese contenedor.
Y en el segundo test, accedemos al elemento con la clase y comprobamos que contiene un determinado texto.
Aquí voy a hacer un inciso para comentar que seguramente sea más acertado para seleccionar los elementos para nuestros test, que lo hagamos con una custom property que definamos nosotros (que lo haríamos con data-lo que queramos), par que así no interfiera con otras funcionalidades que pudiera tener ese elemento en nuestra aplicación.
Ventajas:
Es más rápido que el que usa el navegador de verdad, porque no tienen que cargar dicho navegador
Incovenientes:
Como no es real, no puede observar ciertas cambios que se pueden producir dinámicamente en el DOM, como por ejemplo cuando se crean nodos hijos
Aunque hay algunas librerías que contemplan esto con un MutationObserver, una función que devuelve una promesa con los datos que han cambiado. Por ejemplo dom-testing-library.
No se pueden realizar capturas de pantalla
(En Selenium por ejemplo esto lo observa el mutationObserver)
Como habíamos adelantado anteriormente, hay otra manera de realizar test al DOM VIRTUAL y es a través de snapshots o instantáneas.
¿Y qué supone esto? ¿Que nos coloquemos todos mirando a la cámara para sacar una instantánea y digamos “testing”? … Mmmm no es mala idea. Vamos a hacer un snapshot de este momento…..
Bueno, pues ya la tengo… Ahora si hiciera otra foto, seguramente no quedaría igual, el brazo de *** estaría hacia arriba y antes hacia abajo, otro pondría unos cuernos en la cabeza de otro, que antes no estaba…
Habría diferencias, y esas diferencias las podríamos apreciar gracias a la primera foto que tomamos.
Pues eso es precisamente en lo que se basa este método. Nos toma una instantánea inicial y luego los test posteriores fallarán si eso ha variado y nos ayudará a tomar decisiones de si hay que actualizar la imagen inicial o corregir un error.
Sin embargo Jest no lo hace realizando una captura de la UI, ya que eso supondría tener que cargar nuestro navegador. Y cómo lo hace?
Para entenderlo un poco mejor, vamos a hablar por ejemplo de React, que como sabréis fue creada por Facebook y Facebook a su vez usa Jest para realizar test de todo su código de javascript, incluido React
Porque efectivamente React es una librería enteramente en Javascript y de hecho su forma de mostrarnos los datos en la pantalla no es escribiendo el html como lo harías normalmente, sino que lo hace a través de un método render() y en el cual podemos usar JSX que tiene una sintaxis muy parecida al HTML y nos resulta más fácil de definir.
Aquí hay dos cosas importantes que hay que resaltar, una, que se usa un DOM virtual para generar el HTML y la otra, que nuestra UI viene definida en ese método render del que hemos hablado, que es una función de javacsript.
Y os he contado todo este rollo simplemente para que se entienda qué es lo que hacen las snapshots de Jest,
El snapshot testing tiene una aproximación diferente a otros tipos de test, en vez de escribir código, definir el test y refactorizar, o definir el test, realizar el código, pasar el test y refactorizar, aquí lo que se hace es lo siguiente.
Yo ya tengo mi UI como me gusta y así está perfecta, Por ejemplo aquí veis la visualización de cada una de los personajes de Harry Potter que me traigo de la API.
Eso que habéis visto corresponde a este componente de React, que como veis tiene un método render, el cual me devuelve esta estructura, que es similar al HTML, pero que es JSX y que es la que va a generar el DOM virtual.
Aquí podemos ver que este componente recibe unos datos por props, que son por ejemplo esos que vemos ahí. Los estoy resaltando porque cuando hagamos la snapshot, tendremos que mockearlos para que nos la genere con contenido. Ahora lo vemos
Entonces, como hemos dicho, cuando ya tienes claro cómo quieres que tu aplicación o parte de tu aplicación se renderice, tomarías la snapshot de la siguiente manera:
Importamos renderer, que es una herramienta de renderización para tests creada conjuntamente entre el equipo de Jest y de React.
Y lo que hace efectivamente es eso, renderizar a través del DOM de react nuestro componente y transformarlo en objeto.
Para renderizar correcatmente nuestro componente, como dijimos antes, tendríamos que falsear o mockear los datos que le vienen por props, ya que si no, me generaría una estructura sin contenido, sólo con las etiquetas.
Así pues, le insertamos los datos por ejemplo así.
Eso nos genera el objeto renderizado, y para hacerlo más legible, lo transformamos a JSON, es importante hacer esto, porque el snapshot resultante debe ser lo más legible posible para que cualquiera que abra ese archivo entienda cómo tiene que ser el componente.
Cuando ya tenemos nuestro árbol de nodos del componente, nuestro DOM, entonces usamos el método añadido por Jest para comprobar las snapshots.
Cuando ejecutamos la primera vez el test, en ese momento nos genera la snapshot y la almacena en nuestro proyecto, de manera que puedes acceder a ella y consultarla y tiene una apariencia como ésta.
Como veis, tenemos aquí todo nuestro árbol del DOM, con nuestros divs, nuestros h2, y también el contenido insertado por nosotros.
Ahora ya tenemos una snapshot de referencia almacenada y, si se modificara nuestro componente, entonces nuestro test fallaría
Aquí tenemos un ejemplo de ello
Como veis, vamos a añadir un h3 así como quien no quiere la cosa
Ahora el test falla y nos indica que esa parte de ahí diverge de nuestra snapshot.
En este punto ya tendríamos que decidir si lo que nos interesa es actualizar nuestra snapshot con la nueva información o si corregimos el error en nuestro componente.
Ventajas:
Su simplicidad, no requiere apenas esfuerzo, como habréis podido observar
Inconvenientes:
No puedes definir sobré qué partes de la snapshots quieres realizar el test, porque a lo mejor tienes que hacer un pqueño cambio en el componente, que no afecta a la visualización, como el cambio de una etiqueta, y sin embargo, el test fallará
A los amantes del TDD esto les pondrá de los nervios, porque como podréis inferir, es imposible definir un test antes de definir tu código en este caso, ya que el snapshot se realiza cuando tu código ya está hecho.
Hay que destacar también que los test unitarios son necesarios igualmente, es decir, que esta forma de testar es sólo una y que hay que completarlo. Para React, mucha gente usa Enzime, por ejemplo, que además tiene un monton de asserts muy interesantes para el DOM.
Vale. En este punto quizás haya alguno que me esté mirando con esta cara…
Porque sí estos métodos que usan el DOM virtual están muy bien para realizar tests de manera rápida y efectiva,, pero hay algo que falta, verdad? Lo sé, te entiendo….
…...
Porque ¿qué pasa cuando, como dijimos antes, los elementos se crean dinámicamente con la interacción del usuario? ¿Cómo compruebo que al rellenar un dato en la interfaz de mi aplicación y hacer clic se crea mi elemento de verdad? ¿O cómo puedo realizar test que integren la parte visual de la interfaz de usuario con el resto funcionalidades?
Pues aquí es donde entramos en la segunda forma de hacer test al DOM y es sobre un DOM real.
Aquí entran en escena, por ejemplo, los test end2end.
Aunque en la siguiente charla nos lo va a explicar maravillosamente bien Daniel Ramos, vamos a hablar un poco de lo que es end2end..
Para los que no lo sepan. End2end es una metodología para realizar test, que comprueba que el flujo de la aplicación se comporta como se ha diseñado desde que comienza hasta que acaba. De ahí que se le llame end2end, ya que va de extremo a extremo de la aplicación.
Es decir, comprueba los ejemplos que mencioné antes o por ejemplo si, al abrirse la aplicación, rellenar los campos del formulario correctamente y pulsar un botón, se obtiene lo que se desea.
No sustituye a los test unitarios, que son necesarios igualmente, pero sí que te puede dar una mayor seguridad de que tu aplicación se comporta como tú quieres.
Qué diferencia tienen las herramientas que permiten este tipo de comprobaciones con respecto a los métodos que usamos antes?
La principal es que en este caso, las comprobaciones se realizan sobre el DOM real que genera el navegador. Es decir, no es un DOM virtual copia del que tenemos en el html, es el DOM real del navegador.
En ese sentido, hay multitud de librerías y frameworks que realizan este tipo de tests.
Algunos de ellos te abren el navegador como si de un video se tratase y te ejecutan los test que hayas descrito. Es decir, que vas viendo cómo va haciendo click y rellenando campos.
Otros lo hacen en modo headless, que no es más que un navegador ejecutado en el servidor, por detrás, es decir que tú no lo ves, aunque se ejecuta todo y de hecho te puede proveer de capturas de pantalla de tu web ejecutada. Un ejemplo de éste es Phantom.js
Y por último, muchos de ellos te permiten usar ambos modos, el headless y usando el navegador definido. Algunos ejemplos serían Selenium, Puppeteer o Cypress
Como veis en la transparencia, yo he elegido Cypress.io para esta charla.
¿Y por qué Cypress?
Sí, hay tanta variedad que podría haber elegido cualquier otro: el famoso y completo Selenium, el que está de moda ahora Puppeteer, pero elegí Cypress.io sencillamente porque es ideal para nosotros, developers dummies que amamos javascript. Es fácil de instalar y para alguien que esté acostumbrado a programar con javascript le va a resultar familiar y por lo tanto, facilitar la definición de los test.
Por ejemplo Cypress viene incorporado con Mocha para la estructura de los tests (Describe, it, context, etc) y con Chai para los asserts, con lo que no hay que aprender una manera nueva de definir nuestros test.
Éste sería un ejemplo de un test con cypress.
Como véis la estructura a muchos les resultará familiar, ya tenemos el context, beforeEach, it, etc. A esto nos referíamos cuando decíamos que usa Mocha por dentro.
También podemos ver que tenemos disponibles muchos asserts ya conocidos, usados por Chai, como el should, contains, etc.
Como podréis observar, es muy sencillo acceder a los elementos del DOM y comprobar su contenido, o si es visible o no en la pantalla, a través de un test como éste.
Aquí vemos cómo accedemos a éste elemento, hacemos click, y entonces este otro elemento no debería ser visible. Podríamos haber añadido antes de éste punto que dicho elemento es visible, pero no quería extender mucho el test.
También podemos comprobar que éste elemento, que es un input, tiene este valor después de haberlo rellenado
O este último ejemplo, en el que tras hacer click en el botón “send”, se mostrará éste otro contenedor, cuya tabla contendrá el valor que acabamos de introducir.
Bueno vale, esto es un poco bla blab blabla bla bla. Pero… ahora llega mi parte favorita y con la que llego al testing éxtasis.... Y también a casi el final de la charla
Lo vamos a ejecutar en modo browser para que nos abra el navegador y nos haga todo esto que hemos definido…. Roguemos a los dioses de internet y hagamos que resuenen los tambores...
Veamos la magia
Aquí vemos en primer plano el test que comprobaba que después de rellenar el input con determinado texto, tenía ese valor.
Con este test runner, podemos viajar a cualquier punto de la ejecución del test y ver la captura de ese momento, como aquí, por ejemplo, que seleccioné el momento en el que se rellenaba el input.
Ventajas:
La más importante es que estás haciendo pruebas en el DOM real, el que genera el navegador, con lo que podrás “escuchar” todos los nodos creados dinámicamente
Incovenientes:
Es más lento porque tiene que cargarse el navegador
Y estamos llegando ya al final, simplemente vamos a recordar la idea principal con la que quiero que os vayáis:
Como conclusión principal habría que hablar de que hacer test a la interfaz de usuario, es decir, contra el DOM, supone un coste de esfuerzo y tiempo y, por lo tanto, dinero, pero que te va a permitir tener la garantía de que tu aplicación se va a comportar como tú quieres sin sorpresas. Nadie dijo que definir test era fácil. Pero… Y lo bien que vas a dormir sabiendo que tienes una cobertura de test de casi el 100%???
Por otro lado, conviene recordar que los test de la interfaz de usuario, aunque sean end2end, no son suficientes y que es muy importante completarlos con test unitarios y de integración. Aunque los test end2end comprueben cómo interactúan unas partes de nuestra aplicación con otras, no comprueban por ejemplo, cómo funcionan esas partes por sí solas
Y por último recordar esas dos formas de acceder al DOM para hacer test, el DOM ficticio y el DOM real