"La calidad no es un acto, es un hábito"
-- ✍️ Aristóteles
La mínima garantía de calidad es que algo funcione conforme a lo esperado; es decir que pase un test funcional. Pero desde antiguo se sabe que esto sólo es válido si es constante. Para que se convierta en hábito nada mejor que hacerlo sencillo, cómodo y agradable.
Las pruebas E2E pueden, y deben, incorporar comprobaciones funcionales. Hasta ahora habíamos visto situaciones muy básica en las que una herramienta libre y gratuita como Puppeteer se defendía muy bien.
Pero, cuando probamos funcionalidades complejas se nos complica el uso continuado de la sintaxis asíncrona. Incluso con el moderno async/await. Tampoco Puppeteer trae de fábrica funciones adaptadas al testing. Se le tienen que añadir ayudas como la Integración con Jest, o con otros frameworks de testing como Mocha o Jasmine.
Nada de esto contribuye a integrar el hábito de la prueba funcional constante. Y sin ese hábito no hay calidad.
Cypress
Cypress es un framework para pruebas funcionales e2e. En este sentido es mucho más potente y cómodo de utilizar que Puppeteer que no deja de ser un automatizador de navegadores. El fallo es que... Cypress es de pago; pero lo bueno es que tiene una buena parte gratis que es más suficiente para las pruebas funcionales.
Y desde luego es de lo más sencillo, cómodo y agradable de usar. Ideal para convertirlo en hábito de uso.
Instalar Cypress
Instalarlo es trivial. Partimos de una aplicación Node y agregamos la dependencia. Puedes hacerlo con npm o con yarn y guardarlo como dependencia principal o para desarrollo.
yarn add cypress
npm i --save cypress
Este proceso tarda un poco porque Cypress es mucho más que una librería. Es un test runner que necesita su propio servidor y controlador del navegador. La espera merecerá la pena porque además nos genera una estructura de carpetas para que empecemos con nuestros tests.
Así que una vez instalado, se habrá creado una carpeta en la ruta cypress\integration
. En ella crearemos nuestras especificaciones en ficheros con nombre tipo mi-prueba.spec.js
Test funcionales
Las pruebas funcionales de aplicaciones web, son un tipo e2e que va más allá de las superficialidad de surfear con un navegador automatizado. También controlan los navegadores y por supuesto que simulan el comportamiento del usuario en ellos: hacer clic, escribir, desplazarse, etc.
Son consideradas como de integración porque evidentemente para su ejecución deben coordinarse distintos servicios. Pero lo diferencial es que:
Las pruebas funcionales aseguran que determinados escenarios realmente funcionen desde el punto de vista de un usuario final.
Probar funcionalidades web con Cypress
Como la mayoría de los frameworks de pruebas, Cypress nos ofrece la conocida sintaxis describe it
. Se trata de dos funciones que reciben un primer argumento de texto y en el segundo una función.
Describe it
describe('Funcionalidad que se pretende probar', () => {
// Código de preparación y actuación pruebas
it('Lo que debería ocurrir', () => {
// Comprobación mediante aserciones
});
});
Es tan importante, o más, prestar atención al texto que reciben en el primer parámetro como al código que se ejecutará. Recuerda que el usuario final de este programa eres tú. Hazte un favor a ti mismo y especifica el texto de la manera más clara posible.
Actuaciones y aserciones
Dentro de las anteriores funciones irá el código propiamente dicho. Ahora ya usaremos Cypress como una librería mas y habrá que familiarizarse con su API. Aunque tu editor puede ayudarte en eso.
/// <reference types="Cypress" />
// Actuaciones como un usuario
cy.visit('https://www.bitademy.com');
// Comprobaciones como un programador
cy.title().should('include', 'bitAdemy');
Ejecutar las pruebas con Cypress
La ejecución es recomendable lanzarla desde el package.json. Os muestro dos alternativas. Podéis instalar Cypress y ejecutar las pruebas desde el mismo repositorio que la aplicación que estáis probando. O podéis crear una aplicación de pruebas independiente.
{
"scripts": {
"test:e2e": "cypress open",
"start": "cypress open"
}
}
En el primer caso, la aplicación arranca en start y las pruebas en algo tipo test:e2e. Pero si lo hacéis en un repo aparte... entonces el test se ejecuta directamente desde start.
En el laboratorio he optado por esta aproximación, un repositorio independiente para la prueba funcional. Adaptadlo a vuestro gusto o situación particular.
Hola mundo con Cypress
Para que lo veas todo junto te propongo que te crees un fichero cypress\integration\examples\0-hello-world\00-basic.spec.js
y escribas esto en él.
/// <reference types="Cypress" />
describe('Visiting the url https://www.bitademy.com', () => {
it('should have _bitAdemy_ on its title', () => {
cy.visit('https://www.bitademy.com');
cy.title().should('include', 'bitAdemy');
});
});
Si no lo habías hecho antes lanza el runner con el comando cypress open
o el script equivalente... y espera que te muestre el panel de administrador con todos los tests disponibles.
Selecciona el 0-hello-world/00-basic.spec
y disfruta.
Más acciones y comprobaciones
Para familiarizarte un poco más con la sintaxis de Cypress te dejo la versión extendida de este Hola Mundo. Incluye las acciones más comunes como el simular clicks, navegar entre páginas, rellenar formularios... y comprobar contenidos.
De los textos que se incluyen en las funciones se deduce claramente la intención del desarrollador. Es un caso dónde la documentación forma parte del programa. No es un comentario, es un dato.
Estos tests son de muy alto nivel y normalmente van conducidos por especificaciones behavior driven como esta
FEATURE: have web site with a title
As a: visitor
I want to: view the title of a site
In order to: be more confident
Scenario:
Given: the url https://www.bitademy.com
When: I visit it
Then: should have BitAdemy on its title
Este es el código que realiza la prueba
describe('Visiting the url https://www.bitademy.com', () => {
it('should have _bitAdemy_ on its title', () => {
cy.visit('https://www.bitademy.com');
cy.title().should('include', 'bitAdemy');
});
});
const sutUrl = 'https://www.bitademy.com';
describe(`GIVEN: the url ${sutUrl}`, () => {
context(`WHEN: I visit it`, () => {
before(() => cy.visit(sutUrl));
const expected = 'bitAdemy';
it(`THEN: should have _${expected}_ on its title`, () => {
cy.title().should('include', expected);
});
});
});
Incidiremos más en esta parte en el siguiente tema especialmente dedicado al comportamiento.
Before and after
Si no tienes experiencia previa con frameworks de test, te habrá sorprendido la función before()
. Esta y sus hermanas beforeAll, after y afterAll ejecutan la función que reciben como callback en los momentos adecuados. Sus nombres no dejan lugar a dudas.
Se usan para establecer un escenario en el que se desarrollarán las pruebas, definiendo o inicializando variables. No reciben textos informativos. Si quieres dejar rastro tienes que escribir en la consola, en un log...