manuales_desarrollo

Manuales de desarrollo de YeboYebo


Project maintained by yeboyebo Hosted on GitHub Pages — Theme by mattgraham

Testing / Tests de código legacy

Estrategia

La estrategia para que el código legacy pueda ser testeado y refactorizado consiste en realizar unos pocos tests funcionales que sirvan como red de seguridad, para luego refactorizar el contenido de cada funcionalidad usando muchos pequeños tests unitarios.

Los pasos para lograr esto son los siguientes:

Localizar casos de uso a testear

Determinaremos funcionalidades completas que tienen sentido de negocio a testear, por ejemplo Aprobar presupuesto, Crear factura.

Ejemplo: Albaranar pedido de cliente

Limpiar los accesos al UI

Refactorizaremos el caso de uso para separar las interacciones de la interfaz de la lógica de negocio pura.

Este es un paso delicado puesto que todavía no podemos probar automáticamente que esta refactorización es correcta.

Ejemplo: Ver refactorización masterpedidoscli.qs / generarAlbaranTrans >

Crear un test funcional para happy path del caso de uso

En el correspondiente archivo test_loquesea_cursor.py crearemos al menos un test que compruebe que el código funciona correctamente a nivel global.

Ejemplo: test_pedidoscli_cursor.py > test_albaranar_pedido_genera_albaran_correcto

Localizar posibles tests unitarios que realizar dentro del caso de uso

Buscaremos lógica con un cierto nivel de complejidad que pueda ser extraida y probada por separado.

Ejemplo: flfacturac.oficial_obtenerEstadoPedidoCliOld

function oficial_obtenerEstadoPedidoCliOld(idPedido)
{
	var _i = this.iface;
	var query = new FLSqlQuery();

	query.setTablesList("lineaspedidoscli");
	query.setSelect("cantidad, totalenalbaran, cerrada");
	query.setFrom("lineaspedidoscli");
	query.setWhere("idpedido = " + idPedido);
	if (!query.exec()) {
		return false;
	}

	var estado = "";
	var totalServidas = 0;
	var parcial = false;
	var totalLineas = 0;
	var totalCerradas = 0;

	var cantidad, cantidadServida;
	var cerrada;
	while (query.next()) {
		cantidad = parseFloat(query.value("cantidad"));
		if (cantidad == 0) {
			continue;
		}
		totalLineas++;
		cantidadServida = parseFloat(query.value("totalenalbaran"));
		cerrada = query.value("cerrada");
		if (cerrada) {
			totalCerradas++;
		} else if (cantidad > 0 && cantidad <= cantidadServida) {			
			totalServidas++;
		} else if (cantidad < 0 && cantidad >= cantidadServida) {			
			totalServidas++;
		} else {
			if(cantidad >= 0) {				
			if (cantidad > cantidadServida && cantidadServida != 0) {
				parcial = true;
			}
			} else {
				if (cantidad < cantidadServida && cantidadServida != 0) {				
					parcial = true;
				}
			}		
		}
	}
	if (totalLineas == 0) {
		return "No";
	}

	var totalAServir = totalLineas - totalCerradas;
	if (parcial) {
		estado = "Parcial";
	} else {
		if (totalServidas == 0 && totalCerradas == 0) {
			estado = "No";
		} else {
			if (totalServidas >= totalAServir) {
				estado = "";
			} else {
				estado = "Parcial";
			}
		}
	}
	return estado;
}

Separar en lo posible acceso a base de datos de lógica de negocio

De esta forma acabaremos con una función que no depende del UI ni de la BD, que únicamente recibe una entrada y produce una salida, que es lo ideal para testear.

Ejemplo:

function oficial_obtenerEstadoPedidoCli(idPedido)
{
	const _i = this.iface;

	const query = new FLSqlQuery();

	query.setTablesList("lineaspedidoscli");
	query.setSelect("cantidad, totalenalbaran, cerrada");
	query.setFrom("lineaspedidoscli");
	query.setWhere("idpedido = " + idPedido);
	const lineas = formSQL.cargaConsulta(query)

	return _i.calculaEstadoServidoPedido(lineas)
}

function oficial_calculaEstadoServidoPedido(lineas)
{
	const _i = this.iface;
	if (lineas.length == 0) {
		return "No"
	}
	
	const f = function(acum, linea) {
		if (acum == "Parcial") {
			return acum
		}
		const estadoLinea = flfacturac.iface.calculaEstadoServidoLineaPedido(linea)
		if (estadoLinea == acum) {
			return acum
		}
		switch (estadoLinea) {
			case "Parcial": {
				return "Parcial"
			}
			case "": {
				return acum
					? "Parcial"
					: ""
			}
			case "No": {
				return acum
					? "Parcial"
					: "No"
			}
			default: {
				return acum
			}
		}
	}
	return formARRAY.reduce(lineas, f, null)
}

function oficial_calculaEstadoServidoLineaPedido(linea)
{
	const cerrada = linea["cerrada"]
	const cantidad = linea["cantidad"]
	const servida = linea["totalenalbaran"]
	if (cerrada) {
		return ""
	}
	if (cantidad > 0) {
		if (cantidad <= servida) {
			return ""
		} else if (servida > 0) {
			return "Parcial"
		} else {
			return "No"
		}
	} else if (cantidad < 0) {
		if (cantidad >= servida) {
			return ""
		} else if (servida < 0) {
			return "Parcial"
		} else {
			return "No"
		}
	} else {
		return null
	}
}

Crear tests unitarios

Crearemos tests unitarios en los ficheros test¿?.py_

def test_calcula_estado_servido_pedido(self):
  flfacturac = script("flfacturac").iface
  lineas = [
  {"cantidad": 1, "totalenalbaran": 1, "cerrada": False},
  {"cantidad": 1, "totalenalbaran": 0, "cerrada": False}
  ]
  self.assertEqual(flfacturac.calculaEstadoServidoPedido(lineas), "Parcial")

Probar de forma manual en Eneboo / Pineboo

Hay que tener en cuenta que los tests se realizan con código traducido qsa y en una BD SQLite, siempre debemos hacer una prueba final de la funcionalidad en un entorno con UI para comprobar que no nos hemos saltado ningún paso y que la cadena de funciones que hemos refactorizado es correcta.

Más