QUnit/OPA/Karma – Vol 3 – Test integrados usando OPA

Introducción

Llegamos al último paso, las pruebas integradas. Hasta ahora hemos creado un juego de pruebas unitarias para validar nuestros formatters y controladores. El siguiente paso (aunque debería ser el último) hemos automatizado el test para poder integrar a un flujo de Continous Integration. En esta entrada programaremos pruebas unitarias mediante OPA5.

Mediante OPA podemos programar acciones y automatizar los pasos como si de un usuario se tratase.

Si esta entrada tiene suficientes visitas habrá una cuarta entrada de bonus.

Preparando la aplicación

Utilizaremos la misma aplicación de la entrada qunit opa karma vol 1 test unitarios de ui5, recordemos la aplicación:

  • Una primera pantalla con un campo de input y un formatter que muestra «NO» en caso de informar alguna cosa en el campo input. Y un boton para navegar a la siguiente pantalla
  • Una segunda pantalla con un listado y un boton para añadir filas nuevas.

Preparando nuestros test con OPA

Cuando generamos una aplicación mediante un template de WEBIDE / BAS y tenemos preparado un primer test integrado que nos permite validar si se carga la primera pantalla.

Estructura de los test

La estructura es parecida a la utilizada en Qunit pero con la diferencia que ahora nos movemos entre pantallas y un solo test puede entrelazar distintas pantallas.

En este caso tenemos los siguentes ficheros

  • NavigationJourney.js: donde se implementa el «guión» de nuestros test mediante las instrucciones GIVEN / WHEN / THEN
  • arrangements/Startup.js: este fichero iniciará la aplicación en modo pruebas. Es decir creará y destruirá la aplicación.
  • pages/View1.js: Programaremos las acciones concretas de una vista, por ejemplo pulsar un botón o añadir un valor en un campo de texto.
  • Opatest.qunit.js + opatest.qunit.js: Son los ficheros necesarios para ejecutar los test des del navegador.

Entendiendo un flujo de test en OPA

Los test en OPA se dividen en 3 fases:

  • Inicio del test (GIVEN): es el momento en que se prepara el test, aquí se inicia la aplicación y deja al framework esperando a la carga de librerias
  • Acciones a realizar (WHEN): una vez la aplicación esta en marcha en modo automático, se realizan las acciones pertinentes como pueden ser, apretar botones, añadir valores en campos, etc.
  • Desencadenantes (THEN): una vez está la aplicación arrancada, aquí comparamos el resultado de la ejecución When con la salida esperada.

No hay un punto concreto con el que empezar, así que empezaremos escribiendo nuestro guión de test.

Realizaremos 2 pruebas integradas, por un lado añadiremos un valor al campo de entrada de nuestra pantalla, validamos que el formatter está funcionando y pulsaremos el botón de cambio de página.

Nuestro guión de pruebas (NavigationJourney.js)

En el fichero «NavigationJourney.js» añadiremos el siguiente fichero:

QUnit.module("Test de la Pantalla 1");
opaTest("Qui va el test por ecastella.com", function (Given, When, Then) {
	Given.iStartTheAppByHash({hash: ""});		
	When.onView1.enterText({id: {value: "input",isRegex: false}, actionText: "Hola"});
	Then.onView1.iShouldSeeTheProperty({id: {value: "text",isRegex: false}, attributes: [{text: "NO"}]});
	
	When.onView1.press({controlType: "sap.ui.core.Icon", id: {value: "img",isRegex: false}});
	
	Given.iTeardownTheApp();
			
});

Primero creamos un nuevo módulo de test con QUnit.module, esto es un agrupador de todos los test que se realizarán. Lo siguiente mediante opaTest es la programación del guión de test.

A partir de este punto mediante Given crearemos y destruiremos la aplicación. Los comandos when nos servirán para las acciones, en este caso buscaremos el campo de input con id «input» y le añadiremos el texto «Hola».

Una vez el ejecutado el test para insertar el valor en el campo «Input» validamos que el componente con id «text» tiene el valor «NO»

Con la instrucción «When» buscaremos un icono (o mejor dicho un componente ui5) con el valor «img» y realizaremos la acción de pulsar. Esta no es la mejor manera de proceder pero como estamos en modo pruebas ya nos sirve.

Modulos para crear y destruir la aplicación (Startup.js)

Es momento de iniciar la aplicación. En el punto anterior, al inicio de nuestro test hemos puesto la instrucción «iStartTheAppByHash» y «iTeardownTheApp».

En el fichero Statup.js programaremos el inicio y destrucción de la aplicación.

Primero implementamos el método que hemos añadido anteriormente para iniciar los test:

iStartMyApp: function (oOptionsParameter) {
	var oOptions = oOptionsParameter || {};
	// start the app with a minimal delay to make tests fast but still async to discover basic timing issues
	oOptions.delay = oOptions.delay || 50;
	iStartTheAppByHash: function(oParameters) {
		this.iStartMyUIComponent({
			componentConfig: {
				name: "qunit.qunit",
				async: true
			},
			hash: oParameters.hash
		});
	},
});

Una vez ejecutados los test limpiamos los componentes UI5.

iTeardownTheApp: function() {
	this.iTeardownMyUIComponent();
}

Programando las acciones (View1.js)

Es el momento de automatizar las acciones que hemos añadido en nuestro guión. Para ello, se dividen las acciones a programar en las distintas vistas. en este caso, si os fijais, no hacemos caso de los controladores. Eso se lo dejamos a Qunit.

Para este ejemplo concentramos todos los test en la primera vista, por lo que generamos un fichero (que ya nos viene como ejemplo) llamado View1.js

El fichero contiene los siguientes puntos imporantes: actions donde programamos las acciones y assertions donde programamos el resultado esperado.

Lo primero es importar las librerías necesarias, en este caso librerias para simular el botón press y añadir valores:

sap.ui.define([
	"sap/ui/test/Opa5",
	"sap/ui/test/actions/Press",
	"sap/ui/test/actions/EnterText",
	"sap/ui/test/matchers/PropertyStrictEquals"
], function (Opa5,Press,EnterText,PropertyStrictEquals) {
	"use strict";
	
	var sViewName = "View1";
	
	Opa5.createPageObjects({

Encapsulamos lo que queramos en una variable en este caso onView1. Si os acordáis en el primer fichero hacíamos referencia a un tal onView1, nos sirve por si queremos separar las acciones por módulos

    onView1: {
        viewName: "View1",

Lo siguiente es añadir las actions y assertions, empezamos con actions donde añadiremos el botón y el texto

	actions: {
			
		enterText: function(oActionProperties) {
					
			var actionObject = {};
			if (oActionProperties.id) {actionObject.id = oActionProperties.id.isRegex ? oActionProperties.id.value : new RegExp(oActionProperties.id.value);}
			if (oActionProperties.controlType) {actionObject.controlType = oActionProperties.controlType;}
			actionObject.visible = true;
			actionObject.actions = [new EnterText({text: oActionProperties.actionText})];
			actionObject.success = function() {Opa5.assert.ok(true, "Text: " + oActionProperties.actionText + ", successfully inserted.");};
			actionObject.errorMessage = "Failed to insert " + oActionProperties.actionText;
			actionObject.matchers =
			oActionProperties.attributes ?
						oActionProperties.attributes.map(function(el) {
					return new PropertyStrictEquals({name: Object.keys(el)[0], value: Object.values(el)[0]});
				}) : [];
			return this.waitFor(actionObject);
		},
				
		press: function(oActionProperties) {
			var actionObject = {};
			if (oActionProperties.id) {actionObject.id = oActionProperties.id.isRegex ? oActionProperties.id.value : new RegExp(oActionProperties.id.value);}
			if (oActionProperties.controlType) {actionObject.controlType = oActionProperties.controlType;}
			actionObject.visible = true;
			actionObject.actions = [new Press()];
			actionObject.success = function() { Opa5.assert.ok(true, "Press successful."); };
			actionObject.errorMessage = "Failed to click";
			actionObject.matchers =
			oActionProperties.attributes ?
						oActionProperties.attributes.map(function(el) {
					return new PropertyStrictEquals({name: Object.keys(el)[0], value: Object.values(el)[0]});
				}) : [];
			return this.waitFor(actionObject);
		}
	},

Como vemos, las acciones se basan en parsear el doom en busca de un elemento concreto, crear ese controlador (no creamos toda la aplicación, pero hacemos ver que si 😉 ) y aplicar la acción que toque, las librerías nos ayudan a hacer la simulación más fácil.

Nos quedan las assertions, en este caso, el formatter al añadir un valor en el campo input debería retornar «NO»:

assertions: {
	iShouldSeeTheProperty: function(oMatchProperties) {
		var checkObject = {};
		if (oMatchProperties.id) {checkObject.id = new RegExp(oMatchProperties.id);}
		if (oMatchProperties.controlType) {checkObject.controlType = oMatchProperties.controlType;}
		checkObject.visible = true;
		checkObject.success = function() {Opa5.assert.ok(true,"Found field matching all properties");};
		checkObject.errorMessage = "Won't be able to find field with requirements: " + JSON.stringify(oMatchProperties);
		checkObject.matchers =
		oMatchProperties.attributes ?
						oMatchProperties.attributes.map(function(el) {
				return new PropertyStrictEquals({name: Object.keys(el)[0], value: Object.values(el)[0]});
			}) : [];
		return this.waitFor(checkObject);
	}
}

Ejecutando los test

Ya tenemos todo listo para ejecutar los test. Estos los podemos ejecutar des del navegador o desde la consola de comandos. En este punto te recomiendo ver los dos puntos anteriores para ver como ejecutar estos test, el resultado del test es el siguiente:

También podemos ver que pasa en caso de error, en este caso cambiaremos el resultado esperado del formater de NO a SI.


Con este software podemos crear test que nos permitirán tener controlados el código core de nuestras aplicaciones en cada release.

Puedes consultar más detalles en la página oficial de SAPUI5 https://sapui5.hana.ondemand.com/#/topic/2696ab50faad458f9b4027ec2f9b884d

Si este post tiene interés haré una entrada extra ampliando información.

Como siempre suscribete y comparte en redes para ayudar este blog.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.