sábado, julio 26, 2014

Nueva versión v2.4.22 de las herramientas Visual FoxPro 9 para PlasticSCM (Incluye FoxBin2Prg.exe v1.19.28)

Está liberada la versión v2.4.22 de las herramientas Visual FoxPro 9 para PlasticSCM, con los siguientes cambios:

  • Actualizada la versión de FoxBin2Prg (solo el EXE) a la versión v1.19.28
  • Se ha agregado control y reporte de algunos errores que puedan ocurrir durante la conversión, para ser mostrados en los mensajes de estado de procesamiento.



Estas herramientas son un grupo de scripts vbs y programas Visual FoxPro 9 que se configuran dentro de PlasticSCM para poder invocar a FoxBin2Prg (incluye solo el EXE) desde dentro de la interfaz de Plastic.

El README.txt explica como se configura en Inglés y Español.

Nota: Los fuentes del proyecto FoxBin2Prg y el historial de ambios, están en CodePlex, en este link.


Como actualizar las existentes:
Con descargarlas y reemplazar los archivos en el sitio que los hayan puesto antes es suficiente.


Link de descarga:
https://github.com/fdbozzo/foxpro_plastic_diff_merge


Saludos!

Nueva versión v1.19.28 de FoxBin2Prg (Mejoras - nuevas configuraciones)

Está liberada la versión v1.19.28 de FoxBin2Prg con los siguientes cambios:

  • Mejora: Nuevas opciones en foxbin2prg.cfg (DBF_Conversion_Included, DBF_Conversion_Excluded) y en archivo.dbf.cfg (DBF_Conversion_Order, DBF_Conversion_Condition) para la conversión de DBFs a texto cuando DBF_Conversion_Support es 4 (Edyshor). Recientemente me han solicitado si podía incluir algunas opciones para filtrar las tablas que se exportan, e incluso sus datos, cuando se usa la opción DBF_Conversion_Support: 4, ya que algunos usan FoxBin2Prg para poder comparar tablas de configuraciones y similares (nada grande) y no requieren volver a generar los DBFs, por ello:
    • En foxbin2prg.cfg hay 2 nuevas configuraciones:  DBF_Conversion_Included y DBF_Conversion_Excluded, donde se pueden indicar máscaras de archivos separadas por comas para incluir y/o excluir nombres de tablas del proceso.
    • Se pueden crear archivos de confguración individuales por tabla con archivo_dbf.cfg donde se pueden indicar 2 nuevas opciones: DBF_Conversion_Order y DBF_Conversion_Condition

Aquí se explican las nuevas configuraciones:

DBF_Conversion_Included: <filemask>[ ,<filemask> [ , ... ] ]
DBF_Conversion_Excluded: <filemask>[ ,<filemask> [ , ... ] ]

Ejemplo de las máscaras <filemask>, separadas por coma:

DBF_Conversion_Included: PET*.*, ??ME.DBF, ???.DBF, ?.*


Si se quisieran filtrar los datos de una tabla llamada mitabla.dbf  para solo exportar los registros cuyo campo is_initial = .T. y se desea exportar ordenado por cust_order, habría que crear un archivo cfg en el mismo directorio de la tabla, así:

mitabla.dbf.cfg:
DBF_Conversion_Order: cust_order
DBF_Conversion_Condition: is_initial = .T

* Nota: En DBF_Conversion_Order se puede poner cualquier expresión FoxPro válida para ordenar



Como actualizar el FoxBin2Prg existente:
Con descargar el zip y reemplazar los archivos en el sitio que los hayan puesto antes es suficiente.


Link  de descarga:
https://vfpx.codeplex.com/releases/view/116407


 Saludos!

viernes, julio 18, 2014

Nueva versión v2.4.21 de las herramientas Visual FoxPro 9 para PlasticSCM (Incluye FoxBin2Prg.exe v1.19.27)

Está liberada la versión v2.4.21 de las herramientas Visual FoxPro 9 para PlasticSCM, con los siguientes cambios:




Estas herramientas son un grupo de scripts vbs y programas Visual FoxPro 9 que se configuran dentro de PlasticSCM para poder invocar a FoxBin2Prg (incluye solo el EXE) desde dentro de la interfaz de Plastic.

El README.txt explica como se configura en Inglés y Español.

Nota: Los fuentes del proyecto FoxBin2Prg y el historial de ambios, están en CodePlex, en este link.


Como actualizar las existentes:
Con descargarlas y reemplazar los archivos en el sitio que los hayan puesto antes es suficiente.


Link de descarga:
https://github.com/fdbozzo/foxpro_plastic_diff_merge


Saludos!

Nueva versión v1.19.27 de FoxBin2Prg (Mejoras y nueva configuración)

Está liberada la versión v1.19.27 de FoxBin2Prg con los siguientes cambios:

  • Mejora: Agregado soporte para exportar datos de tablas, solo para comparación (no importación de datos) (Doug Hennig). Esta es una mejora sugerida y programada principalmente por Doug Hennig que que le puede ser útil a quienes quieren poder comparar tablas pequeñas, como tablas de configuración o por el estilo, de una forma rápida. Esta exportación sirve sólo para eso y no está pensada para datos binarios, que de hecho se excluyen esos campos. Esta característica se habilita poniendo DBF_Conversion_Support: 4 en el foxbin2prg.cfg, pero sería conveniente separar esas tablas en un directorio aparte para que solo esas tablas tengan este config y no afecte a las demás, para las que se puede seguir generando la estructura, como siempre. Nota: Esta exportación de datos no tiene operación inversa, así que no hay importación.


Como actualizar el FoxBin2Prg existente:
Con descargar el zip y reemplazar los archivos en el sitio que los hayan puesto antes es suficiente.


Link  de descarga:
https://vfpx.codeplex.com/releases/view/116407


 Saludos!

jueves, julio 10, 2014

Unit Testing: Qué es y cómo se usa en VFP 9

Por: Fernando D. Bozzo

Antes de comenzar, prepárense un café, esto es un poco más largo de lo que creí :-)


A todo buen desarrollador le interesa comprobar que sus programas hacen lo que tienen que hacer, comprobar las funcionalidades, los métodos, el código, las buenas prácticas, todo lo que hace que los programas funcionen bien y sean fáciles de mantener.

El método más común y más conocido por la mayoría para probar los desarrollos, es el de las pruebas manuales, o sea, entrar al programa y comenzar a hacer pruebas ingresando datos, seleccionando opciones, comprobando cálculos, etc. Podríamos decir que las pruebas intentan cubrir aspectos de Interfaz, de reglas de negocio y de datos.

Estas pruebas en general llevan tiempo, a veces bastante tiempo, y, seamos sinceros, a ningún desarrollador le gusta estar haciendo pruebas todo el tiempo, ya que es una tarea muy repetitiva y aburrida. Lo emocionante es programar, comprobar que funciona y que le sea útil a alguien, nosotros incluidos.

Pero la realidad es que todo programa o sistema se construye una vez y se mantiene el 99% del tiempo, tanto para agregar nuevas funcionalidades, como para modificar funcionalidades existentes y corregir errores, también llamadas incidencias, y eso requiere hacer muchas pruebas, y volver a probar lo que ya habíamos probado porque se ha modificado algo que puede afectar a lo que había y que podría causar errores "de regresión", y por eso a estas pruebas se las llama "pruebas de regresión".

No existen los programas libres de errores, y siempre hay alguna condición que no fue tenida en cuenta y que puede provocar un fallo, por no hablar de los fallos propios de la codificación, y por eso se hacen las pruebas.


¿Pero no sería útil poder automatizar esas pruebas? ¿No sería genial que esa tarea repetitiva y aburrida la haga la PC en vez de nosotros? ¿No estaría bien que muchas de esas pruebas que nos pueden llevar horas o días se realicen de alguna forma automatizada en segundos o minutos?

Para eso sirve el Testing Unitario o Unit Testing.



¿Qué es el Testing Unitario?


El Testing Unitario permite programar pruebas del sistema, o de partes del sistema, para que se ejecuten de forma automatizada. Es un programa que comprueba a otros sistemáticamente.

Muchos hemos realizado el típico programa de pruebas para comprobar alguna funcionalidad específica, metiendo en él todo lo necesario para que la prueba funcione, como abrir tablas, cargar objetos, poner valores de prueba, etc, y realmente esa es la base, pero no es suficiente.

Este mecanismo de hacer "un programita" para probar cosas tiene un problema, y es que muchas veces no es reutilizable, y si lo es, seguramente hay más programitas como este dispersos por el sistema y con nombres raros, pero que no permiten ser ejecutados en secuencia, uno tras otro, para automatizar esas comprobaciones.

Es aquí donde radica la principal diferencia entre un Framework de Testing Unitario y esos programas de pruebas que todos hemos hecho alguna vez. Un Framework de Testing Unitario permite hacer estos programas de pruebas con una estructura común a todos los tests, con una lógica que puede ser compartida por todos los tests, ofreciendo métodos comunes ya preparados para comprobar valores, pero principalmente, y aquí está la mayor ventaja, es que permite que todos esos tests se puedan ejecutar uno tras otro con solo pulsar un botón o ejecutar un solo comando.


Alguno ya estará pensando: "Ya, que bien, o sea que hacer tests encima requiere hacer más trabajo y programar más"

...pero antes de contestar eso, preguntémonos esto:
  • ¿Cuánto tiempo usamos en hacer pruebas manuales?
  • ¿Comprobamos realmente todos los cambios que hacemos en el sistema?
  • ¿Cuánto tiempo perdemos en volver a probar una funcionalidad solo por el hecho de haber tenido que modificar alguna función o cálculo?
  • ¿Alguno hace todas las pruebas de regresión que debería hacer?

Dependiendo de la complejidad del sistema, estas pruebas suelen llevar horas, días o meses, y suele ser bastante difícil comprobar todas las combinaciones posibles, por lo que se suelen probar las más importantes. Incluso en el mejor caso donde la funcionalidad sea fácil de probar, hacer manualmente todos los casos de prueba puede llevar mucho tiempo.

Imaginemos un caso típico, una nueva funcionalidad de complejidad media que pide el cliente, donde se requieren 4 horas de pruebas manuales para comprobar esa funcionalidad, que serían realmente 4 horas nuestras al terminar de programar, más 4 horas del cliente para comprobar que funciona como quiere. Ya tenemos 8 horas solo para empezar.

Pero como no todo siempre sale bien a la primera, imaginemos que el cliente descubre un fallo o algo que no va como esperaba, por lo que tenemos que hacer las correcciones y volver a probar todo. Claro que, en este punto, ya no probamos "todo" otra vez, e intentamos optimizar el tiempo probando "solo lo que tocamos" o eso creemos...

En esta segunda prueba, tanto nosotros como el cliente dedicamos algo menos de tiempo para probar, digamos que 2 hs cada uno, pero lo que tocamos antes afectaba a más de lo que creíamos al principio, y hemos introducido un nuevo fallo que puede encontrar el cliente, o con suerte nosotros, y vuelta a empezar...


Para cuando realmente se termina de comprobar todo, puede que hayan sido necesarios 2 ciclos extra de pruebas, lo que fácilmente se pudo llevar, contando los tiempos de feedback del cliente, las modificaciones y las pruebas, otras 8 a 16 hs más... creo que queda claro el problema, y que varios pueden reconocer que les ha pasado, personalmente o a su equipo de desarrollo.

Creando tests automatizados el mayor tiempo se invierte al principio, sobre todo cuando se recién se comienza con ellos, ya que hay una curva de aprendizaje, no sólo de cómo construir los tests en un framework, sino principalmente de cómo hacerlos de la mejor forma posible para que sean fáciles de mantener y útiles en lo que comprueban.

Supongamos que para crear y ajustar los tests dedicamos 8 hs, o 16 hs. Una vez hechos estos tests, cada ejecución de los mismos suele tomar segundos o minutos si son demasiados. En otras palabras, cada vez que necesitemos ejecutar estas pruebas, ya no requeriremos 8 hs o más, sino segundos o minutos...



¿Qué tipos de pruebas se pueden hacer?


La respuesta corta es: Todas las que se puedan automatizar en nuestro sistema, lo que normalmente dependerá de qué tan bien hayamos parametrizado y encapsulado las funcionalidades del mismo.

Hay 3 tipos de pruebas: Unitarias, de Integración y Funcionales. Las pruebas Unitarias suelen comprobar métodos o módulos, lo que hace que sean mucho más rápidas de ejecutar; las pruebas de integración son pruebas que se hacen juntando los desarrollos de varios desarrolladores y probándolos de forma conjunta para comprobar que no hay incompatibilidades entre ellos; y las pruebas Funcionales suelen ser las más complejas, las que más tiempo llevan, suelen ser manuales en las aplicaciones de escritorio y en algunas web y debe participar el usuario en las mismas.

En las pruebas Unitarias, si se verifica el correcto funcionamiento de las partes (métodos, módulos), se puede llegar a controlar buena parte de la funcionalidad de la aplicación, quedando solo exceptuada la Interfaz.

Uno de los beneficios principales de contar con estos tests, es que una vez hechos pasan a ser "pruebas de regresión", por lo que no se tiran, se reutilizan y acumulan indefinidamente hasta que ya no hagan falta (por haber cambiado las reglas de negocio) o que deban ser adaptados para contemplar nuevos casos de prueba.

Otro beneficio es que, si se han hecho buenos casos de prueba, los fallos se detectan antes, mucho antes, en general de forma inmediata, antes de que se entregue al cliente (interno o externo), lo que por sí solo implica un ahorro de tiempo, costes de despliegue y mala imagen.

Un beneficio colateral de los tests automatizados, es que ayudan a programar mejor, permiten ver qué funcionalidades deberíamos separar en capas o en módulos más chicos y testeables, permiten ver que módulos requieren una mejor parametrización y en general nos muestran muchas cosas que no hacemos bien, porque si es difícil de probar con un test, en general (salvo excepciones) es indicativo de que hemos hecho código espagueti o que hemos metido demasiadas funcionalidades o responsabilidades en un módulo.

Por todo esto es que muchos programadores prefieren seguir como estaban, en la comodidad de lo que conocen, ya que no todo el mundo está preparado para asumir que su forma de trabajar puede tener fallos o puede requerir mejoras, y a veces el costo de mejorar puede ser alto. Pero los beneficios son mucho mayores, tanto individualmente como para trabajar en equipo, ya que se potencian las buenas prácticas de programación, la reusabilidad y la mantenibilidad del código, cuestiones que a veces se descuidan bastante.


En general, en las aplicaciones de escritorio (o sea, no web), se suelen comprobar habitualmente 1 ó 2 de las 3 capas del sistema.

Por ejemplo, si consideramos que las 3 capas habituales son Interfaz (pantallas y menús), Negocio (reglas, validaciones) y Datos (accesos a la BDD, consultas, actualizaciones), los tests suelen centrarse en el Negocio y en algunos casos, en los Datos.

Aunque la Interfaz se puede comprobar, es más complejo y lleva más tiempo. Para esto hay, entre otros, un software Open Source llamado Sikuli.

Para la la parte de Reglas de Negocio y Datos, disponemos del Framework FoxUnit para Visual FoxPro 9, con el que podemos:

  • Crear plantillas de código de tests específicas para facilitar la creación de nuevos tests
  • Crear tests basados en plantillas de código prediseñadas
  • Aprovechar los métodos de testeo disponibles para comprobar igualdades, nulos, verdadero/falso, etc
  • Disponer de una interfaz que centraliza la creación y ejecución de todos los tests
  • Elegir entre ejecutar un solo test, todos los tests de una librería o todos los tests del sistema
  • Tener un indicador rojo/verde de cada test
  • Llevar la cuenta de cuántos tests han pasado bien y cuántos han fallado


Percepción de que los tiempos de desarrollo suben notablemente al hacer tests


[Nota: Esta parte la transcribí y adapté del interesante debate que tuvimos en el foro]

Obviamente que los tiempos de desarrollo haciendo tests son superiores a los tiempos de desarrollo sin hacerlos. En mi caso particular, cuando comencé a usarlos me costó bastante aprender cómo hacer un buen caso de prueba, y mis tiempos de desarrollo se duplicaban en algunos casos, dependiendo de la complejidad de lo que quisiera probar.

Pero luego está la parte que nadie cuenta, y que realmente es la que se lleva muchas veces el mayor tiempo, que es la de las pruebas manuales, donde hay de 2 tipos:
  • Pruebas manuales que se pueden automatizar
  • Pruebas manuales que no se pueden automatizar

Dentro de las pruebas manuales que no se pueden automatizar (que suelen ser los tests de integración, o sea donde se prueba como interacciona todo el conjunto), pues no hay salida, deben seguirse haciendo manualmente, aunque programas como el Sikuli, pueden ayudar en cierta medida.

Dentro de las pruebas manuales que se pueden automatizar, es donde se puede sacar mayor provecho, ya que en general toda prueba requiere la comprobación de Interfaz por un lado y de resultados por el otro, aunque al principio no nos demos cuenta y verifiquemos todo como una unidad, y justamente la parte de comprobación de resultados es la que suele poder automatizarse fácilmente, como el ejemplo del cálculo del PVP que puse antes, en este artículo.

Si contamos esta parte de los tiempos de prueba (resultados, cálculos, totales, datos, etc) y los separamos de lo que es estrictamente Interfaz (controles, refresco, enabled/disabled, click aquí o allá) las primeras suelen ser la mayor parte de los tiempos de pruebas totales, que en general suelen ser horas o días (días de 8 hs), dependiendo de la complejidad del programa o sistema, y que es a lo que me refería en el artículo.

Por eso, al automatizar esta parte de las pruebas, que implica programar casos de prueba, es donde cambia la percepción de que el tiempo de desarrollo sube, pero por otro lado el tiempo de pruebas baja bastante, y cuando hay que repetir esas pruebas, la bajada de tiempos ya es abismal, porque los casos de prueba simplemente se vuelven a ejecutar y no hay que programarlos otra vez, en contraste con las pruebas manuales que deben repetirse al completo.
Esta es una de las partes que nadie suele medir, los tiempos de pruebas.

Luego está la otra parte que nadie mide, y es la de los tiempos requeridos en resolver las incidencias. ¿Por qué? Porque nadie cuenta con ellas, y todos creen que si las pruebas manuales fueron bien, ya todo está bien y no hay de qué preocuparse. Pero luego llegan las incidencias inesperadas, esas que no debían ocurrir, y ya no se mide, "se corre" para solucionarlas cuanto antes, lo que suele implicar todas o algunas de estas fases (cada uno adapte a su caso):

  1. El cliente detecta un fallo en sus pruebas y las anota (tiempo de pruebas perdido, porque deberá repetirlas más adelante)
  2. Al terminar las pruebas, el cliente reporta los fallos encontrados (más tiempo, al teléfono o redactando el documento con los detalles)
  3. El gestor recibe los fallos y los comunica al equipo (tiempos de gestión)
  4. El equipo de desarrollo (o el único desarrollador) recibe los fallos y:
    1. Intenta entender lo que quiso decir el cliente (tiempo de "decodificación" e "interpretación" del documento, si lo hay)
    2. Intenta reproducir los fallos en su PC (tiempo de pruebas hasta dar con el caso específico)
    3. Encuentra el fallo y lo soluciona (tiempo de programación y pruebas manuales nuevamente)
    4. Se integra la corrección en la siguiente release o parche (tiempo de integración)
    5. Genera una nueva versión y la vuelve a entregar al cliente (tiempo de entrega y/o despliegue)
  5. El gestor avisa al cliente de que tiene el programa arreglado (tiempo de gestión)
  6. El cliente vuelve a realizar sus pruebas (tiempo de pruebas manuales, nuevamente)
  7. Un punto de mala imagen para el proveedor (coste de imagen y confianza)

Esto, básicamente, es la forma en que suele ocurrir en una empresa grande, en un trato directo con el cliente o PYME seguramente se pueden quitar algunos o varios pasos, pero algunos son inamovibles.

Esta es la segunda parte de los tiempos que nadie suele contar, y por eso queda la sensación de que los tiempos de desarrollo suben, que en parte es cierto porque se invirtió tiempo en hacer tests, pero los tiempos de incidencias y pruebas bajan de manera notable, y agreguemos el tiempo de desarrollo del arreglo de la incidencia, pero como en esta fase todos están corriendo por "solucionar las incidencias" que son muy importantes, nadie tiene tiempo de contar cuánto tiempo perdido se gastó. (Recalco lo de tiempo invertido y tiempo perdido)


Respecto a la complejidad de los casos de prueba, algunos son fáciles y rápidos, otros son entretenidos o creativos, y en otros a veces hay que aplicarse bastante para lograr unos buenos casos de prueba, que pueden ser inherentemente complejos y que se deben intentar simplificar para que luego sean fáciles de mantener. Pero en todo caso, una vez se terminan, se puede descansar mucho más tranquilo.



Por eso, cuando se piense en el aumento de los tiempos de desarrollo, no deben olvidarse todos los otros tiempos antes comentados.



Unit Testing en Visual FoxPro 9



Ejemplo del uso de FoxUnit para las pruebas de FoxBin2Prg (click en la imagen para agrandar y ver los detalles):





¿Cómo se usa FoxUnit? Un caso práctico y rápido


Ya hemos descargado el framework y lo hemos descomprimido en, por ejemplo, c:\desa\FoxUnit, entonces para ejecutarlo, pondríamos:

DO c:\desa\FoxUnit\foxunit.app


Pantalla de ejemplo inicial de FoxUnit:



Supongamos que necesitamos crear un nuevo método "Calcular_Precio_de_Venta" en una clase de cálculos "cl_calculos" dentro de una librería "lib_calculos.prg", para lo que hacemos la estructura del método (sin implementación!):


DEFINE CLASS cl_calculos AS Custom

    PROCEDURE Calcular_Precio_de_Venta
        LPARAMETERS tnCostoDelArticulo, tnMargenDeGanancia
        LOCAL lnPVP
        *-- Cálculo sin implementación todavía
        RETURN lnPVP
    ENDPROC

ENDDEFINE



Ahora vamos a crear un test para este cálculo, sabiendo de antemano el resultado que debe devolver. Ejecutamos el framework desde la ventana de comandos de VFP:


DO c:\desa\FoxUnit\foxunit.app


Elegimos el botón "New Class" y ponemos como nombre de programa "ut__lib_calculos__cl_calculos__Calcular_Precio_de_Venta":


Nota: Sobre la nomenclatura usada, les recomiendo usar esta, que está basada en la práctica y la experiencia. El nombre del programa lo formamos con una simple regla fácil de recordar:

"ut" + doble_guión_bajo + "nombre_libreria" + doble_guión_bajo + "nombre_clase" + doble_guión_bajo + "nombre_método"

Resumiendo: ut__libreria__clase__metodo

Así luego resulta fácil buscar todos los métodos de una clase o todas las clases de una librería con las casillas de filtrado que hay bajo los botones de la barra superior.


En la lista, elegimos "Minimal_FoxUnit_Test_case_template" [1] (el otro lo pueden elegir para ver las explicaciones, que tiene varias en Inglés) y clickeamos el primer botón largo [2]:



Una vez pulsamos el botón (1) se crea automáticamente la estructura de un programa de testeo, con 2 métodos: Setup y TearDown, debiendo nosotros crear los métodos correspondientes a los casos de prueba.

El método Setup y TearDown se ejecutan para cada caso de prueba que creamos, siendo el orden de ejecución este: Setup -> CasoDePrueba -> TearDown. Esto permite poner código para preparar el entorno necesario para que el caso de prueba se ejecute en Setup y luego liberar ese entorno en TearDown.

Para nuestro ejemplo, vamos a crear este programa de prueba, donde quité varias de las líneas de asteriscos que vienen con la plantilla y agregué el caso de prueba:


DEFINE CLASS ut__lib_calculos__cl_calculos__Calcular_Precio_de_Venta as FxuTestCase OF FxuTestCase.prg

    #IF .f.
    LOCAL THIS AS ut__lib_calculos__cl_calculos__Calcular_Precio_de_Venta OF ut__lib_calculos__cl_calculos__Calcular_Precio_de_Venta.PRG
    #ENDIF
    oBus = NULL
   

    ********************************************************************
    FUNCTION Setup
        SET PROCEDURE TO LIB_CALCULOS.PRG ADDITIVE
        THIS.oBus = CREATEOBJECT("CL_CALCULOS")
    ENDFUNC
   

    ********************************************************************
    FUNCTION TearDown
        THIS.oBus = NULL
        RELEASE PROCEDURE LIB_CALCULOS
    ENDFUNC


    *******************************************************************************************************************************************
    PROCEDURE Deberia_CalcularUn_PVP_de_11_ParaUnCostoDe_10_YUnMargeDeGananciaDe_10
        LOCAL lnCostoDelArticulo, lnMargenDeGanancia, lnPVP, lnPVP_Esperado ;
            , loBus as CL_CALCULOS OF LIB_CALCULOS.PRG
       
        *-- Inicialización y Datos de entrada
        STORE 0 TO lnCostoDelArticulo, lnMargenDeGanancia, lnPVP, lnPVP_Esperado
        loBus                = THIS.oBus
        lnCostoDelArticulo    = 10
        lnMargenDeGanancia    = 10
        lnPVP_Esperado        = 11

        *-- Testeo del cálculo
        lnPVP = loBus.calcular_precio_de_venta( lnCostoDelArticulo, lnMargenDeGanancia )
       
        *-- Información de estado
        THIS.messageout( "PVP Esperado:  " + TRANSFORM(lnPVP_Esperado) )
        THIS.messageout( "PVP Calculado: " + TRANSFORM(lnPVP) )

        *-- Validaciones
        THIS.assertequals( lnPVP_Esperado, lnPVP, "PRECIO DE VENTA (PVP)" )
    ENDPROC


ENDDEFINE



Veamos que tenemos:

  • En la cabecera de la clase definimos la propiedad que contendrá la instancia de nuestra clase (oBus = NULL)
  • En el método Setup configuramos nuestra librería de cálculos e instanciamos la clase
  • En el método TearDown hacemos exactamente lo contrario y en el orden opuesto, NULificamos la propiedad oBus y descargamos la libreria
  • Finalmente creamos el caso de prueba: Deberia_CalcularUn_PVP_de_11_ParaUnCostoDe_10_YUnMargeDeGananciaDe_10

Para quien ve esto por primera vez, puede pensar que es una exageración un nombre así de largo para un método, pero no hay que confundirse: el nombre del método se está usando como descripción del caso de prueba. Si lo leen detenidamente, lo primero que se darán cuenta es que el nombre contiene toda la descripción del caso de prueba a realizar, y que no requieren ver su código para saber que esperar.

La sintaxis no es casual: Es conveniente comenzar los casos de prueba siempre por "Deberia_", ya que con esto estamos indicando una intencionalidad de que el caso de prueba "Deberia" hacer lo que indique a continuación, que en este caso es "calcular un PVP de 11 para un costo de 10 y un margen de ganancia de 10%". Si el caso de prueba falla, entonces obviamente no está haciendo lo que debería hacer, y se debe verificar qué ocurre.

Esta nomenclatura es tomada de BDD (Behaviour Driven Development), donde en Inglés se usa "Should" que es el equivalente de "Deberia" en Español.

La importancia de comenzar con "Debería" en vez del típico "Test" es que en el primer caso se está haciendo hincapié en la funcionalidad que se desea conseguir, mientras que en el segundo caso se hace más hincapié en el test en sí, y de cara a un sistema lo más importante es conseguir y verificar una funcionalidad, no lograr que un test funcione. Con el tiempo probablemente se darán cuenta de la diferencia.


Volviendo al ejemplo, una vez creado el programa lo guardamos con CTRL+W, donde ahora podemos ver en la pantalla un registro con el nombre del programa y el caso de prueba que hicimos (hagan click en la imagen para verla mejor):



En la captura anterior se pueden observar varias cosas:
  • El botón All sirve para ejecutar todos los casos de prueba que tengan cargados en la pantalla
  • El botón Class sirve para ejecutar todos los casos de prueba de la clase seleccionada
  • El botón Selected sirve para ejecutar solo el caso de prueba seleccionado
  • El botón New Class lo usamos para crear nuevas clases de tests, como hicimos nosotros al inicio
  • El botón Load Class nos permite cargar clases que ya tengamos hechas y que no estén cargadas
  • El botón Add Test permite crear un nuevo caso de prueba en la clase actual
  • Remove Selected permite descargar los tests de la clase actual (los quita de la lista de tests, no los borra)
  • Reload Selected permite recargar los casos de prueba de la librería de clases actual. Esto es útil cuando por algún caso no se cargaron bien todos los casos.
  • Hay un par de cuadros de texto que permiten filtrar los tests por nombre de programa (izquierdo) o por nombre de caso de prueba (derecho), y en ambos casos se puede escribir solo una parte, ya que la búsqueda es por subcadenas.
  • Debajo está la lista de casos de prueba, donde en la columna izquierda están los programas que contienen los casos de prueba y en la columna derecha se muestran los casos de prueba (métodos) que hayamos creado. Viendo esta lista e imaginando como va a crecer a medida que agreguemos casos de prueba, se puede tener una idea de porqué es tan importante saber rápidamente por el nombre del caso de prueba qué es lo que hace y no tener que entrar a ver el código, ya que con el tiempo pueden haber cientos de casos de prueba.
  • Finalmente, en el panel inferior hay un PageFrame con 2 paneles, el izquierdo muestra los errores y el derecho los mensajes que hayamos hecho.

Vamos a ejecutar nuestro ejemplo y a ver que sucede. Sabemos que debería fallar, porque todavía no está implementado el cálculo, así que clickeamos el botón "Selected" y obtenemos esto:


Como podemos ver, la ventana de mensajes (Messages), donde se muestran los "this.messageout()", muestra que el valor esperado era 11 y el calculado es .F.
Se corresponde con estas líneas:

        *-- Información de estado
        THIS.messageout( "PVP Esperado:  " + TRANSFORM(lnPVP_Esperado) )
        THIS.messageout( "PVP Calculado: " + TRANSFORM(lnPVP) )



Veamos la ventana de errores (Failures and Errors) de la solapa izquierda:


Esta parte muestra las comparaciones y verificaciones que se hayan hecho con "this.assertX", en nuestro caso eran estas líneas:

        *-- Validaciones
        THIS.assertequals( lnPVP_Esperado, lnPVP, "PRECIO DE VENTA (PVP)" )



Como se ve, muestra un error de Data Type Mismatch (error de tipo de datos) y los valores esperado y obtenidos.


Respecto de los Assert, hay pocos y son fáciles de recordar, además que Intellisense facilita ver sus parámetros:

  • AssertEquals( e1, e2, me ): verifica que la expresión e1 y e2 son iguales, y opcionalmente muestra el mensaje de error me si no son iguales
  • AssertNotNull( e, me ): verifica si la expresión e es NULL o no, y opcionalmente muestra el mensaje de error me si el valor es NULL
  •  AssertNotEmpty( e, me ): verifica si la expresión e es EMPTY o no, y opcionalmente muestra el mensaje de error me si el valor es EMPTY
  • AssertNotNullOrEmpty( e, me ): verifica si la expresión e es NULL o EMPTY, y opcionalmente muestra el mensaje de error
  • AssertTrue( e, me ): verifica si la expresión e es Verdadera o no, y opcionalmente muestra el mensaje de error me si el valor es Verdadero
  • AssertFalse( e, me ): verifica si la expresión e es Falso o no, y opcionalmente muestra el mensaje de error me si el valor es Falso

Ahora que hicimos fallar el caso de prueba, vamos a implementar el cálculo que falta, por lo que cerramos la pantalla de FoxUnit, hacemos un CLEAR ALL y abrimos nuestra librería LIB_CALCULOS.PRG, agregando la línea de implementación del cálculo:




DEFINE CLASS cl_calculos AS Custom

    PROCEDURE Calcular_Precio_de_Venta
        LPARAMETERS tnCostoDelArticulo, tnMargenDeGanancia
        LOCAL lnPVP

        *-- Implementación del cálculo
        lnPVP = tnCostoDelArticulo * (1 + tnMargenDeGanancia / 100) 

        RETURN lnPVP
    ENDPROC

ENDDEFINE



Lo guardamos con CTRL+W, volvemos a cargar FoxUnit y ejecutamos el caso de prueba nuevamente:



Esta vez salió todo bien y podemos comprobar 2 cosas:
  1. Que el caso de prueba estaba bien hecho
  2. Que la funcionalidad implementa exactamente lo que indica el caso de prueba



Modificando la funcionalidad y adaptando el caso de prueba


Vamos a suponer que necesitamos crear un caso especial en el cálculo del PVP, en el que, si el coste es de 0,10 no recargaremos nada, independientemente del margen que pasemos de parámetro.

Lo primero que hacemos es crear el nuevo caso de prueba, modificamos el existente, lo copiamos y pegamos debajo con esto (resalto en amarillo los cambios):


    PROCEDURE Deberia_CalcularUn_PVP_de_0_10_ParaUnCostoDe_0_10_YUnMargeDeGananciaCualquiera
        LOCAL lnCostoDelArticulo, lnMargenDeGanancia, lnPVP, lnPVP_Esperado ;
            , loBus as CL_CALCULOS OF LIB_CALCULOS.PRG
       
        *-- Inicialización y Datos de entrada
        STORE 0 TO lnCostoDelArticulo, lnMargenDeGanancia, lnPVP, lnPVP_Esperado
        loBus                = THIS.oBus
        lnCostoDelArticulo    = 0.10
        lnMargenDeGanancia    = 10
        lnPVP_Esperado        = 0.10

        *-- Testeo del cálculo
        lnPVP = loBus.calcular_precio_de_venta( lnCostoDelArticulo, lnMargenDeGanancia )
       
        *-- Información de estado
        THIS.messageout( "PVP Esperado:  " + TRANSFORM(lnPVP_Esperado) )
        THIS.messageout( "PVP Calculado: " + TRANSFORM(lnPVP) )

        *-- Validaciones
        THIS.assertequals( lnPVP_Esperado, lnPVP, "PRECIO DE VENTA (PVP)" )
    ENDPROC



Ejecutamos nuestro nuevo caso de prueba, y obtenemos este fallo:



Pero ya lo esperábamos, porque todavía no está implementado el cambio, así que modificamos la implementación del cálculo em LIB_CALCULOS.PRG:

        *-- Implementación del cálculo
        IF tnCostoDelArticulo == 0.10
            lnPVP    = tnCostoDelArticulo
        ELSE
            lnPVP    = tnCostoDelArticulo * (1 + tnMargenDeGanancia / 100)
        ENDIF



Volvemos a FoxUnit y ejecutamos el nuevo caso de prueba otra vez:



¡Perfecto! Ya tenemos todo funcionando y verificado.



Tips y recomendaciones


Aquí les dejo algunas recomendaciones y cosas a tener en cuenta, que les va a ayudar a hacer mejores casos de prueba y más mantenibles:

  • Pensar y diseñar buenos casos de prueba: Esto es fundamental para que los tests sean útiles y realmente comprueben la funcionalidad lo más a fondo posible
  • No usar nombres inútiles como "deberia_hacer_un_calculo": Esto es más frecuente de lo que se cree, sobre todo al principio, donde muchas veces se ponen nombres que indican algo tan genérico que difícilmente sea útil al momento de validar algo específico, justamente por la falta de precisión en su definición. Si vamos al caso extremo, el sistema "deberia_funcionar" y si falla, no podemos tener una idea clara de qué es lo que falla exactamente como para encontrar el error rápidamente.
  • Ser suficientemente descriptivo con los nombres de los casos de prueba: Como en el ejemplo que puse arriba, la descripción debe ser lo más clara posible, como para que no sea necesario entrar para ver el código del mismo, y en caso de fallar, sabemos exactamente qué falló y dónde buscar.
  • No caer en el síndrome de la luz verde: Cuando se comienzan a hacer tests, es difícil no caer en la falsa seguridad de que si todo sale en verde está todo correcto. Verificar que cada test que se hace vale la pena y aporta algo al proceso. No tiene sentido hacer un test para comprobar un valor constante por ejemplo.
  • Encapsular las funcionalidades comunes de los tests: Esto puede ser útil cuando se quieran usar ciertos seteos comunes a todos los tests para evitar tener que copiar y pegar en cada uno de ellos. En FoxBin2Prg pueden ver un ejemplo de rutinas reutilizables llamadas en Setup y tearDown
  • Parametrizar bien los métodos del sistema: La mayoría de lo que se programe en un sistema o módulo debe estar parametrizado de tal forma que se pueda probar con un simple test sin requerir cargar todo el sistema para ello
  • Refactorizar el código como práctica habitual: Si un método tiene muchos comentarios, es signo de que necesita ser refactorizado en partes más chicas. La refactorización no cambia la funcionalidad de un método, solo permite partirlo en partes más chicas, testeables y mantenibles
  • TDD: Siempre que se pueda, intentar hacer primero los casos de prueba y luego la implementación. Si bien muchas veces hacerlo en este orden es más trabajoso, se logran mejores resultados. Al hacer primero el test y luego la implementación estaremos trabajando con lo que se conoce como TDD (Test Driven Development) o Desarrollo Manejado por Tests.




Nota sobre TDD:

TDD es muy útil, pero no hay que obsesionarse con esta técnica, ya que no siempre es posible aplicarla en tiempo y forma. Por ejemplo, es muy útil usarla cuando hay un diseño claro a seguir y se sabe de antemano exactamente como debe comportarse el sistema o un módulo o método (por ejemplo, al resolver incidencias o al crear o modificar ciertas funcionalidades), pero cuando se va programando sobre la marcha porque se tienen las cosas en la cabeza, se complica bastante usar esta técnica, en cuyo caso puede convenir hacer los tests al final.





Resumiendo


Aunque el ejemplo es muy básico, sirve para entrever algunas de las bondades de los Tests Unitarios. La próxima vez que tengamos que modificar la funcionalidad anterior, si llegamos a tocar alguno de los 2 cálculos que tenemos hechos, cuando ejecutemos los tests saltará el error indicando que algo no va bien, o se tocó lo que no se debía o se olvidó adaptar el caso de prueba antes de tocar, gracias a que los tests que hicimos ya son pruebas de regresión y quedarán ahí para futuras comprobaciones.

En el caso de usar un SCM, al igual que el código que escribimos, los casos de prueba también deben versionarse en el gestor de código fuente que se use junto al código del programa o sistema, en su carpeta Tests, ya que cualquier desarrollador que se baje el proyecto, debe tener estos test a mano para poder ejecutarlos antes de comprometer sus cambios.


Como dato de interés, FoxBin2Prg tiene creados por el momento (v1.19.26) unos 150 casos de prueba, no solo de reglas de negocio y validaciones, sino también de generación de pantallas (comparación por bitmap) y verificación de generación de archivos, y ni siquiera es un sistema. Gracias a esos tests el ahorro de tiempo es de varios días de pruebas por cada ejecución de todos los casos, que al estar automatizados tardan unos 5 minutos en total.

Pueden acceder a los tests disponibles abriendo una sesión de FoxPro en donde hayan bajado y descomprimido el FoxBin2Prg y ejecutando el FoxUnit, y de paso podrán ver algunas técnicas de encapsulación de tests para minimizar el código escrito y facilitar su mantenimiento.


Espero que lo anterior sirva para que comiencen a incursionar en este tema, que no solo es muy interesante, sino sumamente práctico.
Al principio cuesta, se cometen errores y se aprende, pero lo importante es comenzar aunque sea con algo simple y desde ahí seguir. Al menos les dejo algunas recomendaciones de nomenclatura para que no caigan en algunos de los errores que ya cometí al comenzar, y que cometan otros distintos.


Esto no termina aquí, se puede ir al siguiente nivel que es el uso de Integración Continua para la ejecución de los tests y más, pero eso lo dejo ya para otro artículo :-)


Hasta la próxima!

Artículos Relacionados y de interés:
What Makes a Good Programmer?



domingo, julio 06, 2014

PlasticSCM: Opciones de conectividad, transporte del código fuente y backup

Por: Fernando D. Bozzo

Una de las cosas que más me gustan de este SCM (Source Control Manager o Administrador de Código Fuente) es la cantidad de opciones que tiene para conectarse y sincronizarse con otros servidores Plastic y con otros servicios como GitHub, BitBucket y otros.

Este artículo está dedicado a quienes necesitan sincronizar sus repositorios con otras personas o con otros servicios, trabajen en solitario o en grupo. Lo importante es conocer las opciones disponibles para luego elegir.

Quienes vienen trabajando con herramientas de control de código, ya saben que una vez que se comienza a trabajar así, no se puede ni se quiere vivir sin él, y quienes estén comenzando a conocerlo, estarán pudiendo comprobar, al menos, algunas ventajas que aporta, como poder ver y analizar el historial, deshacer o rehacer cambios, hacer un Diff o un Merge (....a qué ahora sí les suena esto :), preparar versiones del sistema, también llamadas Releases, preparar parches, trabajar simultáneamente en más de una versión del mismo componente, etc.

Dicho esto, una de las cosas que nos aportará valor y comodidad es poder disponer del control de código donde sea que estemos trabajando, tanto trabajando en solitario como trabajando en grupo y compartiendo un proyecto, y por eso vamos a ver varias situaciones y alternativas de trabajo disponibles.


Supongamos que disponemos de un PC de escritorio en casa, o de un servidor en la oficina, donde está instalado el componente servidor de Plastic con los repositorios definitivos, y donde realmente se centralizará todo el control del código fuente. Es necesario mantenerlo siempre actualizado, por lo que las vías para hacerlo son:

  • Trabajar directamente en ese PC o en el servidor
  • Trabajar en una red local con acceso a ese PC o servidor
  • Trabajar desde Internet con acceso a ese PC o servidor
  • Trabajar en una ubicación desconectada y sin Plastic, hacer una copia de lo trabajado en zip y sincronizarlo más tarde en el servidor Plastic 
  • Trabajar en una ubicación sin Plastic, pero con acceso a DropBox, Google Drive u otro servicio de guardado en la nube, para más tarde sincronizarlo en el servidor de Plastic

De ahora en más, hablar de una PC de sobremesa, hablar de una Portátil o hablar de un Servidor de Oficina o Empresa, en todos los casos con el componente servidor de Plastic, los consideraré a todos como "el servidor Plastic", ya que es la función que están realizando, desde el momento en que permiten guardar el código fuente en una Base de Datos local.

Es importante entender esto, porque todo lo posterior tratará sobre formas de sincronizar los repositorios entre servidores Plastic, que perfectamente podrían sincronizarse entre una PC de sobremesa y una Portátil, o entre una Portátil y un Servidor de Oficina/Empresa, o entre dos (o más) Portátiles, o usando un repositorio externo como GitHub, BitBucket, etc, o cualquier otra combinación que se les ocurra donde intervengan dos o más servidores Plastic.

Finalmente, y para despejar cualquier duda, "sincronizar" es la acción de actualizar repositorios en ambos sentidos, o sea que se suben los cambios (archivos y directorios nuevos, modificados o eliminados) que el repositorio destino no tiene y se bajan los cambios que nuestro repositorio local no tiene, quedando el historial en ambos casos igualado.


¿Pero sincronizando no se pisarán mis cambios con los de los demás?


Puede ocurrir, si no se trabaja de forma organizada. Para evitar estos problemas fácilmente, la forma de trabajar debe ser tener una rama por desarrollador como mínimo, y a su vez otra rama por tarea, pero fundamentalmente debe haber un proyecto inicial, con su pjx, los archivos de configuración de Plastic y con toda la estructura de directorios en el repositorio, para evitar diferencias innecesarias en nombre de directorio o capitalización, y demás.


Opciones de sincronización entre repositorios


Estas son algunas de las opciones disponibles:
  • Sincronizar un repositorio Plastic local con otro repositorio Plastic remoto. Esta es la opción ideal y la más eficiente. Requiere una conexión de red o por Internet entre ambos servidores Plastic, y que no esté bloqueado el puerto 8087 (u 8088 si se usa seguridad SSL). Ojo, Plastic no soporta Firewalls donde deba autenticarse. En esos casos se deberá usar redirección de puertos.
  • Sincronizar un repositorio Plastic local con otro repositorio remoto como GitHub, BitBucket o cualquier otro soportado. Esta opción implica usar un servicio "intermediario", y si el repositorio no es público, probablemente implique algún costo extra asociado, ya que al menos GitHub, si se quiere usar un repositorio privado, se debe pagar, y supongo que con el resto será lo mismo.
  • Trabajar con cliente Plastic local y servidor Plastic en red o por Internet. Realmente aquí no hay sincronización. Esta opción se suele usar en las empresas que tienen uno a varios equipos de desarrollo internos o externos, pero donde el código fuente se quiere que solamente esté en el servidor corporativo por ser un bien intelectual de la empresa. La única desventaja de este modelo es que si la red no es veloz se pueden producir picos de congestión si los equipos de desarrollo son numerosos o si se usa para procesos de Integración Continua (CI). Requiere una conexión de red o por Internet entre ambos servidores Plastic, y que no esté bloqueado el puerto 8087 (u 8088 si se usa seguridad SSL) y en este escenario ayuda mucho utilizar balanceo de servidores o Servidores Proxy Plastic (esto está mejor explicado en su web)



Supongamos que tenemos un portátil nuestro que usamos para programar, en el que podemos instalar lo que sea necesario, y que llevamos de aquí hacia allá todo el tiempo, sea a lo del cliente, a casa o a un bar. Tenemos un Servidor Plastic instalado en el portátil, y nuestros repositorios, cada uno con su proyecto. Aquí trabajamos normalmente y sin mayor problema, ya que tanto el código como el SCM están en el mismo equipo, por lo que hacemos checkin, merge, ramas y lo que haga falta.

Si tuviéramos que sincronizarnos con un compañero que trabaja en otra ubicación y donde no compartimos una red local, podríamos usar estas opciones:
  • Sincronización Plastic local a Plastic remoto. En este caso los dos nos ponemos de acuerdo en un horario para conectarnos a Internet y la sincronización sería directa de equipo a equipo usando el puerto 8088 SSL (u otro), y para resolver el problema de la IP dinámica se puede usar algún servicio como NoIP, DynDns o similar. El único problema con esto sería ponerse de acuerdo en ese horario, y que, a día de hoy, la versión 5 de Plastic no soporta la conexión a través de Firewalls que requieran autenticación, pero si al Firewall se le configura redirección de puertos, muy probablemente se pueda.
  • Sincronización Plastic local a servidor remoto (GitHub, etc). Este es el caso comentado en Opciones de Sincronización, donde se usa un repositorio intermediario.
  • Sincronización mediante archivos zip por DropBox. GDrive o similar. Esto realmente no es una sincronización, pero al menos permite incorporar los cambios de otra persona al repositorio local. La idea es crear dos ramas, la nuestra y la de nuestro compañero. Nosotros trabajamos en nuestra rama normalmente hasta que nuestro compañero nos envía un zip mediante DropBox, Google Drive o similar. Nos pasamos temporalmente a la rama de nuestro compañero, descomprimimos el zip en el workspace y hacemos el checkin. Ya está en control de código y podemos hacer un merge de su rama a la de integración. Este mecanismo es un poco Espartano porque nos implica estar haciendo un merge de lo nuestro y un merge de lo del otro, y al otro le implica lo mismo, con lo que se duplica el trabajo de integración y la posibilidad de errores, pero al menos nos puede sacar del paso.
  • Sincronización Plastic local a Plastic local. Si, suena raro, pero se pueden crear 2 o más repositorios Plastic locales usando distintos workspaces y distintos nombres de repositorio, para luego, mediante un perfil de conexión (comentado más abajo), sincronizarlos. No sé si tenga alguna utilidad, ya que yo lo usé solo para probar y simular 2 usuarios distintos que sincronizaban sus repositorios (que se pueden modificar por separado), pero es bueno conocer las opciones disponibles.

  • Fast-Export/Fast-Import: Finalmente hay una opción que merece ser conocida, que es la opción Fast-Export y Fast-Import, basada en el formato abierto de Git, que permite exportar un repositorio completo a un archivo en este formato de Git, y también permite importarlo. Esto permite una capa más de compatibilidad, no solo con Git, sino con otros SCM que admitan ese formato.


Dentro de la sección de configuración de Plastic, hay un apartado llamado Perfiles, donde se pueden configurar perfiles de conexión con distintos esquemas de autenticación (usuario/password, LDAP, Directorio Activo, etc).

Este apartado merece ser conocido, porque realmente admite muchas opciones de conectividad.




Como se podrá comprobar en la Instalación, los puertos son configurables, y esta configuración se puede cambiar en cualquier momento

Hay mucha documentación disponible en la web de Plastic, aunque lamentablemente está solo en Inglés. Este es un link al apartado donde explican todo sobre entornos distribuidos.


Hasta la próxima!


Artículos relacionados:
PlasticSCM: Cómo configurar la replicación de repositorios Plastic locales y remotos



Nueva versión v2.4.20 de las herramientas Visual FoxPro 9 para PlasticSCM (Incluye FoxBin2Prg.exe v1.19.26)

Por: Fernando D. Bozzo

Está liberada la versión v2.4.20 de las herramientas Visual FoxPro 9 para PlasticSCM, con los siguientes cambios:




Estas herramientas son un grupo de scripts vbs y programas Visual FoxPro 9 que se configuran dentro de PlasticSCM para poder invocar a FoxBin2Prg (incluye solo el EXE) desde dentro de la interfaz de Plastic.

El README.txt explica como se configura en Inglés y Español.

Nota: Los fuentes del proyecto FoxBin2Prg y el historial de ambios, están en CodePlex, en este link.


Como actualizar las existentes:
Con descargarlas y reemplazar los archivos en el sitio que los hayan puesto antes es suficiente.


Link de descarga:
https://github.com/fdbozzo/foxpro_plastic_diff_merge


Saludos!

Nueva versión v1.19.26 de FoxBin2Prg (Mejoras, nueva configuración y arreglo)

Por: Fernando D. Bozzo

Está liberada la versión v1.19.26 de FoxBin2Prg con los siguientes cambios:

  • Mejora: Quitar los asteriscos que pudieran haber entre los ENDPROC y PROCEDURE, analizados en ese orden, para regenerar binario sin errores. (Daniel Sánchez). Esto es algo que no ocurre en FoxPro, pero que puede ser provocado a propósito por algunos programas de protección de código fuente que agregan ciertos asteriscos fuera de lugar dentro del binario para evitar que se pueda compilar. Esta mejora quita esos asteriscos de algunos sitios donde se sabe que no pueden estar, y así permitir la compilación del binario.
  • Mejora: Agregar opción de configuración l_DropNullCharsFromCode, activada por defecto, para permitir quitar los NULLs del código fuente (Matt Slay). Bajo ciertas condiciones no documentadas, VFP agrega caracteres NULL en el código, lo que probablemente sea un bug, pero que permite compilar igualmente. Esta mejora permite quitar esos caracteres NULL del código, que el editor de Fox no permite escribir y que de existir los muestra como espacios. Principalmente esos NULL afectan a los programas de control de código fuente que detectan al archivo de texto como un binario, no permitiendo ver su contenido o compararlo.
  • Bug Fix cfg: ExtraBackupLevel no se tiene en cuenta cuando se usa multi-configuración. Cuando se usa multi-configuración (archivos CFG por directorio), el seteo ExtraBackupLevel se pone a 0, impidiendo realizar los backup automáticos. Se corrige ese bug.


Como actualizar el FoxBin2Prg existente:
Con descargar el zip y reemplazar los archivos en el sitio que los hayan puesto antes es suficiente.


Link  de descarga:
https://vfpx.codeplex.com/releases/view/116407


 Saludos!